You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多
文档控制台
免费开始使用

现代OpenGL中透视投影下物体ortho axes绘制及尺寸恒定问题求解

嘿,刚好之前做过类似的需求,来帮你搞定这两个OpenGL问题!

一、在透视投影场景中绘制正交轴的实现思路

核心思路是分阶段渲染:先正常渲染你的透视3D场景,再切换到正交投影来单独绘制坐标轴,这样轴就不会受到透视变形的影响。具体步骤如下:

  1. 先完成透视场景的渲染,注意不要清除深度缓冲(或者调整深度测试规则,让轴能显示在物体上方,比如临时关闭深度测试,或者设置glDepthFunc(GL_ALWAYS))。
  2. 把选中物体的世界空间坐标转换为屏幕窗口坐标
    • 用透视投影矩阵 + 视图矩阵把世界坐标转换为NDC(标准化设备坐标),再通过视口参数转换为屏幕像素坐标(你也可以用gluProject函数快速完成这个转换,现代OpenGL里需要自己实现的话,公式也不难)。
  3. 切换到正交投影模式:
    • 创建一个覆盖整个屏幕的正交投影矩阵,比如ortho(0, windowWidth, 0, windowHeight, -1, 1)(注意OpenGL默认Y轴原点在左下角,如果你习惯左上角的话可以调整矩阵)。
  4. 在屏幕坐标对应的位置绘制正交轴:
    • 直接用屏幕像素长度定义轴的大小(比如X轴从(screenX, screenY)画到(screenX+50, screenY)),这样轴就是完全正交的,不会有透视变形。
二、让轴的尺寸不受相机位置影响的两种方案

这个问题本质是要让轴在屏幕上的大小恒定,不管物体离相机多远,都保持同样的视觉尺寸,有两种常用方案:

方案1:屏幕空间绘制(最直接)

就是上面第一个问题里用到的方式——直接在屏幕像素坐标系下绘制轴。因为是基于屏幕像素定义长度,不管物体离相机多远,轴的像素大小都是固定的,完全不受透视缩放的影响。

现代OpenGL中的伪代码片段供你参考:

// 先渲染透视场景...

// 切换到轴的渲染
axisShader.use();

// 计算选中物体的屏幕坐标
glm::vec3 worldPos = selectedObj->getWorldPos();
glm::vec4 ndc = perspectiveMat * viewMat * glm::vec4(worldPos, 1.0f);
ndc /= ndc.w; // 透视除法
glm::vec2 screenPos = glm::vec2(
    (ndc.x + 1.0f) / 2.0f * windowWidth,
    (1.0f - ndc.y) / 2.0f * windowHeight
);

// 设置正交投影和视图矩阵
glm::mat4 orthoProj = glm::ortho(0.0f, (float)windowWidth, 0.0f, (float)windowHeight, -1.0f, 1.0f);
axisShader.setMat4("projection", orthoProj);
axisShader.setMat4("view", glm::mat4(1.0f)); // 视图矩阵设为单位矩阵,因为在屏幕空间

// 模型矩阵:把轴移动到屏幕位置,设置固定像素长度
glm::mat4 model = glm::translate(glm::mat4(1.0f), glm::vec3(screenPos.x, screenPos.y, 0.0f));
model = glm::scale(model, glm::vec3(50.0f, 50.0f, 50.0f)); // 轴长50像素
axisShader.setMat4("model", model);

// 绘制轴的三条线段(X红、Y绿、Z蓝)
glBindVertexArray(axisVAO);
axisShader.setVec3("color", glm::vec3(1.0f, 0.0f, 0.0f));
glDrawArrays(GL_LINES, 0, 2);
axisShader.setVec3("color", glm::vec3(0.0f, 1.0f, 0.0f));
glDrawArrays(GL_LINES, 2, 2);
axisShader.setVec3("color", glm::vec3(0.0f, 0.0f, 1.0f));
glDrawArrays(GL_LINES, 4, 2);

方案2:3D空间动态缩放(保留3D位置感)

如果你希望轴是在3D空间中(比如和物体的局部坐标系绑定,看起来是物体的一部分),但视觉大小恒定,那需要根据相机到物体的距离动态计算缩放因子:

  1. 计算相机(eye)到选中物体世界坐标的直线距离distance
  2. 根据屏幕的垂直视场角fovY(弧度)、屏幕高度screenHeight,以及你想要的轴在屏幕上的目标像素长度targetPixelLength,计算缩放因子:
    float scale = (targetPixelLength / screenHeight) * distance * tan(fovY / 2.0f) * 2.0f;
    
    这个公式的原理是利用相似三角形,把屏幕上的像素长度转换为3D空间中的长度,距离越远,缩放比例越大,刚好抵消透视带来的缩小效果。
  3. 绘制轴的时候,把这个缩放因子应用到轴的模型矩阵上,这样轴在3D空间中会随距离放大,最终在屏幕上的大小保持恒定。

内容的提问来源于stack exchange,提问作者Alex Gardner

火山引擎 最新活动