QImage ***GLWindowInterface::renderToImage( float zoomFactor/*=1.0f*/,
bool dontScaleFeatures/*=false*/,
bool renderOverlayItems/*=false*/,
bool silent/*=false*/)
{
QImage outputImage;
if (!m_glExtFuncSupported) //no FBO support?!
{
if (isStereo())
{
if (!silent)
{
***Log::Error("Direct screen capture without FBO is not supported anymore!");
}
return QImage();
}
else
{
//if no shader or fbo --> we grab the screen directly
if (m_activeShader)
{
if (!silent)
***Log::Error("Direct screen capture with shader is not supported!");
}
else
{
outputImage = doGrabFramebuffer();
if (outputImage.isNull())
{
if (!silent)
***Log::Error("Direct screen capture failed! (not enough memory?)");
}
}
return outputImage;
}
}
//otherwise FBOs are supported
if (!silent)
{
***Log::Print("[Render screen via FBO]");
}
doMakeCurrent();
//current window size (in pixels)
int Wp = static_cast<int>(width() * zoomFactor);
int Hp = static_cast<int>(height() * zoomFactor);
if (zoomFactor != 1.0f)
{
setGLViewport(0, 0, Wp, Hp); //warning: this will modify m_glViewport
}
//try to reserve memory for the output image
outputImage = QImage(m_glViewport.size(), QImage::Format_ARGB32);
GLubyte* data = outputImage.bits();
if (!data)
{
//failure :(
if (!silent)
{
***Log::Error("Not enough memory!");
}
if (zoomFactor != 1.0f)
{
setGLViewport(0, 0, width(), height()); //restore m_glViewport
}
return QImage();
}
//we activate 'capture' mode
m_captureMode.enabled = true;
m_captureMode.zoomFactor = zoomFactor;
m_captureMode.renderOverlayItems = renderOverlayItems;
//current viewport parameters backup
float _defaultPointSize = m_viewportParams.defaultPointSize;
float _defaultLineWidth = m_viewportParams.defaultLineWidth;
if (!dontScaleFeatures)
{
//we update point size (for point clouds)
setPointSize(_defaultPointSize * zoomFactor, true);
//we update line width (for bounding-boxes, etc.)
setLineWidth(_defaultLineWidth * zoomFactor);
}
***FrameBufferObject* fbo = nullptr;
***GlFilter* glFilter = nullptr;
if (m_fbo && zoomFactor == 1.0f)
{
//we can use the existing FBO
fbo = m_fbo;
//and the existing GL filter
glFilter = m_activeGLFilter;
}
else
{
//otherwise we create a new temporary one
fbo = new ***FrameBufferObject();
bool su***ess = (fbo->init(glWidth(), glHeight())
&& fbo->initColor()
&& fbo->initDepth());
if (!su***ess)
{
delete fbo;
fbo = nullptr;
if (!silent)
{
***Log::Error("[FBO] Initialization failed! (not enough memory?)");
}
if (zoomFactor != 1.0f)
{
setGLViewport(0, 0, width(), height()); //restore m_glViewport
}
return QImage();
}
//and we change the current GL filter size (temporarily)
if (m_activeGLFilter)
{
QString error;
if (!m_activeGLFilter->init(glWidth(), glHeight(), GetShaderPath(), error))
{
if (!silent)
{
***Log::Warning(QString("[GL Filter] GL filter can't be used for rendering: %1").arg(error));
}
}
else
{
glFilter = m_activeGLFilter;
}
}
}
assert(fbo);
***QOpenGLFunctions* glFunc = functions();
assert(glFunc);
***_DRAW_CONTEXT CONTEXT;
getContext(CONTEXT);
CONTEXT.renderZoom = zoomFactor;
//just to be sure
stopLODCycle();
RenderingParams renderingParams;
renderingParams.drawForeground = false;
renderingParams.useFBO = false; //DGM: make sure that no FBO is used internally!
bool stereoModeWasEnabled = m_stereoModeEnabled;
if (m_stereoModeEnabled && !m_stereoParams.isAnaglyph())
{
// Screen capture doesn't work with real stereo rendering
m_stereoModeEnabled = false;
}
//disable LOD!
bool wasLODEnabled = isLODEnabled();
setLODEnabled(false);
//enable the FBO
bindFBO(fbo);
logGLError("***GLWindow::renderToFile/FBO start");
fullRenderingPass(CONTEXT, renderingParams);
if (m_stereoModeEnabled) //2 nd pass for single display 'stereo' rendering (= anaglyphs)
{
renderingParams.pass = RIGHT_RENDERING_PASS;
fullRenderingPass(CONTEXT, renderingParams);
}
//disable the FBO
logGLError("***GLWindow::renderToFile/FBO stop");
bindFBO(nullptr);
setLODEnabled(wasLODEnabled);
m_stereoModeEnabled = stereoModeWasEnabled;
CONTEXT.drawingFlags = ***_DRAW_2D | ***_DRAW_FOREGROUND;
if (m_interactionFlags == INTERACT_TRANSFORM_ENTITIES)
{
CONTEXT.drawingFlags |= ***_VIRTUAL_TRANS_ENABLED;
}
glFunc->glPushAttrib(GL_DEPTH_BUFFER_BIT);
glFunc->glDisable(GL_DEPTH_TEST);
if (glFilter)
{
//we process GL filter
GLuint depthTex = fbo->getDepthTexture();
GLuint colorTex = fbo->getColorTexture();
//minimal set of viewport parameters necessary for GL filters
***GlFilter::ViewportParameters parameters;
{
parameters.perspectiveMode = m_viewportParams.perspectiveView;
parameters.zFar = m_viewportParams.zFar;
parameters.zNear = m_viewportParams.zNear;
parameters.zoomFactor = zoomFactor;
}
//apply shader
glFilter->shade(depthTex, colorTex, parameters);
logGLError("***GLWindow::renderToFile/glFilter shade");
//in render mode we only want to capture it, not to display it
bindFBO(fbo);
setStandardOrthoCorner();
***GLUtils::DisplayTexture2DPosition(glFilter->getTexture(), 0, 0, CONTEXT.glW, CONTEXT.glH);
bindFBO(nullptr);
}
bindFBO(fbo);
setStandardOrthoCenter();
//we draw 2D entities (mainly for the color ramp!)
if (m_globalDBRoot)
m_globalDBRoot->draw(CONTEXT);
if (m_winDBRoot)
m_winDBRoot->draw(CONTEXT);
//current displayed scalar field color ramp (if any)
***RenderingTools::DrawColorRamp(CONTEXT);
if (m_captureMode.renderOverlayItems)
{
//scale: only in ortho mode
if (!m_viewportParams.perspectiveView)
{
//DGM FIXME: with a zoom > 1, the renderText call inside drawScale will result in the wrong FBO being used?!
drawScale(getDisplayParameters().textDefaultCol);
}
if (m_showTrihedron)
{
//trihedron
drawTrihedron();
}
}
glFunc->glFlush();
//read from fbo
glFunc->glReadBuffer(GL_COLOR_ATTACHMENT0_EXT);
//to avoid memory issues, we read line by line
for (int i = 0; i < glHeight(); ++i)
{
glFunc->glReadPixels(0, i, glWidth(), 1, GL_BGRA, GL_UNSIGNED_BYTE, data + (glHeight() - 1 - i) * glWidth() * 4);
}
glFunc->glReadBuffer(GL_NONE);
//restore the default FBO
bindFBO(nullptr);
glFunc->glPopAttrib(); //GL_DEPTH_BUFFER_BIT
logGLError("***GLWindow::renderToFile");
if (m_fbo != fbo)
{
delete fbo;
}
fbo = nullptr;
if (zoomFactor != 1.0f)
{
setGLViewport(0, 0, width(), height()); //restore m_glViewport
}
if (glFilter && zoomFactor != 1.0f)
{
QString error;
m_activeGLFilter->init(glWidth(), glHeight(), GetShaderPath(), error);
}
//we restore viewport parameters
setPointSize(_defaultPointSize, true);
setLineWidth(_defaultLineWidth);
m_captureMode.enabled = false;
m_captureMode.zoomFactor = 1.0f;
invalidateViewport();
invalidateVisualization();
redraw(true);
return outputImage;
}
这个 ***GLWindowInterface::renderToImage(...) 函数是 Cloud***pare 项目中的一个重要方法,作用是:
将当前 3D 渲染窗口内容导出为 QImage(屏幕截图),可选缩放和图层控制,并尽可能使用 OpenGL 的 Framebuffer Object(FBO) 来提高精度和效率。
✅ 函数用途总结:
QImage renderToImage(float zoomFactor = 1.0f, bool dontScaleFeatures = false, bool renderOverlayItems = false, bool silent = false);
| 参数 | 说明 |
|---|---|
zoomFactor |
图像缩放倍数(>1 表示放大,<1 表示缩小) |
dontScaleFeatures |
如果为 true,点大小/线宽等不会随缩放而变 |
renderOverlayItems |
是否绘制比例尺、坐标系等覆盖项(2D HUD) |
silent |
是否静默处理错误信息(不弹日志) |
📦 函数执行流程总览:
1. 是否支持 FBO?
└─ 否 → 截图失败或降级为屏幕读取。
└─ 是 → 进入完整 FBO 渲染流程。
2. 创建 FBO 或使用已有 FBO。
3. 设置渲染参数与视图缩放。
4. 开始 FBO 渲染:
└─ 渲染 3D 模型
└─ 应用 Shader 滤镜(如果有)
└─ 绘制 2D 图层(如比例尺/三维坐标轴)
5. 用 glReadPixels 读取像素数据 → 存入 QImage。
6. 清理资源,恢复状态。
🔍 关键步骤详细分析:
1. 判断是否支持 FBO
if (!m_glExtFuncSupported) { ... }
- 如果不支持 FBO(如旧显卡或 OpenGL 环境),则尝试使用屏幕抓图
doGrabFramebuffer()。 - 如果启用了 Shader 或立体视图(stereo),将报错。
2. 计算图像大小(按缩放因子)
int Wp = static_cast<int>(width() * zoomFactor);
int Hp = static_cast<int>(height() * zoomFactor);
并根据此设置临时 GL 视口:
if (zoomFactor != 1.0f)
setGLViewport(0, 0, Wp, Hp);
3. 尝试分配 QImage 缓存空间
outputImage = QImage(m_glViewport.size(), QImage::Format_ARGB32);
GLubyte* data = outputImage.bits();
分配失败则立即返回。
4. 进入“捕获模式”并处理尺寸缩放
m_captureMode.enabled = true;
m_captureMode.zoomFactor = zoomFactor;
setPointSize(defaultPointSize * zoomFactor);
如果 dontScaleFeatures = true,则保留原始大小。
5. 创建临时 FBO(或使用已有)
if (m_fbo && zoomFactor == 1.0f)
fbo = m_fbo;
else
fbo = new ***FrameBufferObject();
还会为 FBO 初始化颜色缓冲区和深度缓冲区:
fbo->initColor();
fbo->initDepth();
6. 开始渲染到 FBO
bindFBO(fbo);
fullRenderingPass(CONTEXT, renderingParams);
⚠️ 如果启用立体视图且是 anaglyph(红蓝眼镜)模式,还会渲染第二帧(右眼):
renderingParams.pass = RIGHT_RENDERING_PASS;
7. 执行 Shader 滤镜(如果存在)
if (glFilter) {
glFilter->shade(depthTex, colorTex, parameters);
}
并使用 DisplayTexture2DPosition(...) 显示滤镜输出纹理。
8. 绘制 2D 覆盖层(比例尺、坐标轴、HUD)
if (renderOverlayItems) {
drawScale(...);
drawTrihedron();
}
这部分在 FBO 渲染结束后绘制(因为是 2D 元素)。
9. 读取 FBO 像素数据
for (int i = 0; i < glHeight(); ++i)
{
glReadPixels(..., data + (glHeight() - 1 - i) * glWidth() * 4);
}
由于 OpenGL 原点在左下角,读取时需要行反转。
10. 清理和状态恢复
delete fbo; // 如果是新建的
setGLViewport(...); // 恢复原始视口
restore pointSize / lineWidth / zoomFactor
✅ 函数功能性总结
| 功能点 | 是否支持 |
|---|---|
| 放大缩小截图 | ✅ 支持 zoomFactor
|
| 高质量截图(FBO) | ✅ |
| 屏幕抓图(无FBO时) | ✅ 降级模式 |
| 支持 HUD(比例尺、坐标轴) | ✅ 可开关 |
| Shader 后处理支持 | ✅ 可选 |
| Stereo 视图 | ⚠️ 仅 Anaglyph 支持,其他 stereo 模式禁止 |
| 像素级图像输出 | ✅ QImage,BGRA 格式 |
✅ 应用场景建议
-
保存高分辨率截图:传入
zoomFactor > 1.0f。 -
导出用于报告、PPT 的图像:开启
renderOverlayItems = true。 -
生成无干扰模型图:关闭 overlay,并设置
dontScaleFeatures = true。