CloudCompare-源码分析-绘制与 3D 场景分离的“前景”元素
文章目录
- ⭐ 核心功能概述
- 🧩 详细分析
- 1. 设置 2D 模式
- 2. 配置绘制上下文
- 3. 绘制主数据库中的 2D 实体
- 4. 绘制颜色条(标量场)
- 5. 绘制比例尺 & 三维坐标系(Trihedron)
- 6. 绘制 GL 滤波器横幅
- 7. 绘制消息文本
- 8. 绘制 Clickable 区域
- 9. LOD 进度图标动画
- 10. 错误检查
- 🔚 小结:函数作用定位
void ccGLWindowInterface::drawForeground(CC_DRAW_CONTEXT& CONTEXT, RenderingParams& renderingParams)
{ccQOpenGLFunctions* glFunc = functions();assert(glFunc);/****************************************//**** PASS: 2D/FOREGROUND/NO LIGHT ****//****************************************/setStandardOrthoCenter();glFunc->glDisable(GL_DEPTH_TEST);CONTEXT.drawingFlags = CC_DRAW_2D | CC_DRAW_FOREGROUND;if (m_interactionFlags & INTERACT_TRANSFORM_ENTITIES){CONTEXT.drawingFlags |= CC_VIRTUAL_TRANS_ENABLED;}//we draw 2D entitiesif (m_globalDBRoot)m_globalDBRoot->draw(CONTEXT);if (m_winDBRoot)m_winDBRoot->draw(CONTEXT);//current displayed scalar field color ramp (if any)ccRenderingTools::DrawColorRamp(CONTEXT);m_clickableItems.clear();/*** overlay entities ***/{//default overlay colorconst ccColor::Rgbub& textCol = getDisplayParameters().textDefaultCol;if (!m_captureMode.enabled || m_captureMode.renderOverlayItems){//scale: only in ortho modeif (m_showScale && !m_viewportParams.perspectiveView){drawScale(textCol);}if (m_showTrihedron){//trihedrondrawTrihedron();}}if (!m_captureMode.enabled){int yStart = 0;//transparent border at the top of the screenbool showGLFilterRibbon = renderingParams.useFBO && m_activeGLFilter;showGLFilterRibbon &= !exclusiveFullScreen(); //we hide it in fullscreen mode!if (showGLFilterRibbon){const float w = glWidth() / 2.0f;const float h = glHeight() / 2.0f;const int borderHeight = getGlFilterBannerHeight();glFunc->glPushAttrib(GL_COLOR_BUFFER_BIT);glFunc->glEnable(GL_BLEND);glFunc->glColor4f(1.0f, 1.0f, 0.0f, 0.6f);glFunc->glBegin(GL_QUADS);glFunc->glVertex2f(w, h);glFunc->glVertex2f(-w, h);glFunc->glVertex2f(-w, h - borderHeight);glFunc->glVertex2f(w, h - borderHeight);glFunc->glEnd();glFunc->glPopAttrib(); //GL_COLOR_BUFFER_BITauto devicePixelRatio = getDevicePixelRatio();QFont newFont;newFont.setPointSize(static_cast<int>(newFont.pointSizeF() * getDevicePixelRatio()));glColor4ubv_safe<ccQOpenGLFunctions>(glFunc, ccColor::black);renderText( static_cast<int>(10 * devicePixelRatio),borderHeight - static_cast<int>((CC_GL_FILTER_BANNER_MARGIN - CC_GL_FILTER_BANNER_MARGIN / 2) * devicePixelRatio),QString("[GL filter] ") + m_activeGLFilter->getDescription(),static_cast<uint16_t>(RenderTextReservedIDs::GLFilterLabel),newFont);yStart += borderHeight;}//current messages (if valid)if (!m_messagesToDisplay.empty()){glColor3ubv_safe<ccQOpenGLFunctions>(glFunc, textCol);int ll_currentHeight = glHeight() - 10; //lower leftint uc_currentHeight = 10; //upper centerQFont font = getTextDisplayFont();for (const auto& message : m_messagesToDisplay){uint16_t textureID = 0;if (message.type != CUSTOM_MESSAGE){textureID = static_cast<uint16_t>(RenderTextReservedIDs::StandardMessagePrefix) + static_cast<uint16_t>(message.type);}switch (message.position){case LOWER_LEFT_MESSAGE:{renderText(10, ll_currentHeight, message.message, textureID, font);int messageHeight = QFontMetrics(font).height();ll_currentHeight -= (messageHeight * 5) / 4; //add a 25% margin}break;case UPPER_CENTER_MESSAGE:{QRect rect = QFontMetrics(font).boundingRect(message.message);//take the GL filter banner into account!int x = (glWidth() - rect.width()) / 2;int y = uc_currentHeight + rect.height();if (showGLFilterRibbon){y += getGlFilterBannerHeight();}renderText(x, y, message.message, textureID, font);uc_currentHeight += (rect.height() * 5) / 4; //add a 25% margin}break;case SCREEN_CENTER_MESSAGE:{QFont newFont(font); //no need to take zoom into account!newFont.setPointSize(static_cast<int>(12 * getDevicePixelRatio()));QRect rect = QFontMetrics(newFont).boundingRect(message.message);//only one message supported in the screen center (for the moment ;)renderText((glWidth() - rect.width()) / 2, (glHeight() - rect.height()) / 2, message.message, textureID, newFont);}break;}}}//hot-zone{drawClickableItems(0, yStart);}if (renderingParams.nextLODState.inProgress){renderingParams.nextLODState.progressIndicator++;//draw LOD in progress 'icon'static const int lodIconSize = 32;static const int margin = 6;static const unsigned lodIconParts = 12;static const float lodPartsRadius = 3.0f;int x = margin;yStart += margin;static const float radius = lodIconSize / 2.0f - lodPartsRadius;static const float alpha = static_cast<float>((2 * M_PI) / lodIconParts);int cx = x + lodIconSize / 2 - glWidth() / 2;int cy = glHeight() / 2 - (yStart + lodIconSize / 2);glFunc->glPushAttrib(GL_POINT_BIT | GL_DEPTH_BUFFER_BIT);glFunc->glPointSize(lodPartsRadius);glFunc->glEnable(GL_POINT_SMOOTH);glFunc->glDisable(GL_DEPTH_TEST);//draw spinning circlesglFunc->glBegin(GL_POINTS);for (unsigned i = 0; i < lodIconParts; ++i){float intensity = static_cast<float>((i + renderingParams.nextLODState.progressIndicator) % lodIconParts) / (lodIconParts - 1);intensity /= ccColor::MAX;float col[3] = { textCol.rgb[0] * intensity,textCol.rgb[1] * intensity,textCol.rgb[2] * intensity };glFunc->glColor3fv(col);glFunc->glVertex3f(cx + radius * std::cos(i*alpha), cy + radius * std::sin(i*alpha), 0);}glFunc->glEnd();glFunc->glPopAttrib(); //GL_POINT_BIT | GL_DEPTH_BUFFER_BITyStart += lodIconSize + margin;}}}logGLError("ccGLWindow::drawForeground");
}
这个函数 ccGLWindowInterface::drawForeground
是 CloudCompare 中用于在 OpenGL 窗口前景层绘制 2D 图形、提示、比例尺、轴、进度指示器等 UI 元素的重要函数之一。它通常在每帧渲染后执行,用于绘制与 3D 场景分离的“前景”元素,如 HUD 信息。
⭐ 核心功能概述
该函数的主要职责是:
-
设置 2D 正交投影和禁用深度测试。
-
绘制前景中的 2D 实体(如数据库实体、颜色条、点击区域等)。
-
绘制辅助 UI 元素:
- 比例尺
drawScale
- 三维坐标系
drawTrihedron
- 消息提示文字(底部左侧/顶部中间/屏幕中心)
- 当前激活的 GL 滤波器横幅
- LOD(多层细节)加载图标动画
- Clickable 热区
- 比例尺
🧩 详细分析
1. 设置 2D 模式
setStandardOrthoCenter();
glFunc->glDisable(GL_DEPTH_TEST);
- 设置正交投影,使后续绘制在屏幕空间(2D 层)进行。
- 关闭深度测试,避免 UI 被 3D 对象遮挡。
2. 配置绘制上下文
CONTEXT.drawingFlags = CC_DRAW_2D | CC_DRAW_FOREGROUND;
if (m_interactionFlags & INTERACT_TRANSFORM_ENTITIES)
{CONTEXT.drawingFlags |= CC_VIRTUAL_TRANS_ENABLED;
}
- 设置绘制标志,指示当前是“前景层 2D 绘制”。
- 如果当前处于实体变换交互模式,则启用虚拟变换标志。
3. 绘制主数据库中的 2D 实体
if (m_globalDBRoot)m_globalDBRoot->draw(CONTEXT);
if (m_winDBRoot)m_winDBRoot->draw(CONTEXT);
- 对全局和窗口级别的数据库根节点调用
draw
,让其以 2D 模式绘制相关 UI(如注释、文本等)。
4. 绘制颜色条(标量场)
ccRenderingTools::DrawColorRamp(CONTEXT);
- 如果启用了标量场(Scalar Field),则绘制颜色条 UI。
5. 绘制比例尺 & 三维坐标系(Trihedron)
if (m_showScale && !m_viewportParams.perspectiveView)drawScale(textCol);if (m_showTrihedron)drawTrihedron();
- 比例尺只在正交视图下绘制。
- 三维坐标系三棱锥在所有模式下都可以绘制。
6. 绘制 GL 滤波器横幅
if (showGLFilterRibbon)
{// 黄色半透明矩形横幅 + 黑色文字renderText(..., "[GL filter] " + m_activeGLFilter->getDescription());
}
- 用于提示当前启用的 OpenGL 滤波器,如 SSAO、模糊等。
- 支持 Retina/高分屏的像素比处理(
getDevicePixelRatio
)。
7. 绘制消息文本
for (const auto& message : m_messagesToDisplay)
{switch (message.position){case LOWER_LEFT_MESSAGE:case UPPER_CENTER_MESSAGE:case SCREEN_CENTER_MESSAGE:}
}
-
支持三类位置:
- 左下角:多条消息垂直堆叠。
- 顶部居中:考虑 GL 滤波器横幅的高度。
- 屏幕中央:一般只允许显示一条提示,字体更大。
8. 绘制 Clickable 区域
drawClickableItems(0, yStart);
- 为 UI 热区做准备,例如右键菜单、拾取区域、图标等。
9. LOD 进度图标动画
if (renderingParams.nextLODState.inProgress)
{// 动态圆圈指示器(转圈)
}
- 当 LOD 加载进行中,绘制一个 12 个点组成的动态转圈效果。
- 每次调用自增
progressIndicator
实现动画帧切换。 - 使用
glBegin(GL_POINTS)
绘制。
10. 错误检查
logGLError("ccGLWindow::drawForeground");
- 输出 OpenGL 错误(如有),便于调试。
🔚 小结:函数作用定位
这个函数是 CloudCompare 渲染架构中一个典型的“前景层渲染”函数:
类别 | 内容 |
---|---|
📌 作用 | 绘制与主渲染内容(3D)分离的 2D UI 元素 |
🔁 调用频率 | 每帧调用 |
🔧 接口类型 | 内部私有/受控调用(非直接外部接口) |
🧱 渲染内容 | 比例尺、坐标系、标量条、状态消息、交互提示、LOD 图标等 |
📐 投影模式 | 正交(2D) |