CloudCompare|源码分析ccGLWindowInterface::renderToImage(...)-将当前 3D 渲染窗口内容导出为 `QImage`(屏幕截图)

CloudCompare|源码分析ccGLWindowInterface::renderToImage(...)-将当前 3D 渲染窗口内容导出为 `QImage`(屏幕截图)


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

转载请说明出处内容投诉
CSS教程网 » CloudCompare|源码分析ccGLWindowInterface::renderToImage(...)-将当前 3D 渲染窗口内容导出为 `QImage`(屏幕截图)

发表评论

欢迎 访客 发表评论

一个令你着迷的主题!

查看演示 官网购买