Ortho 和 Persp 正在反转 Z 深度符号?

Posted

技术标签:

【中文标题】Ortho 和 Persp 正在反转 Z 深度符号?【英文标题】:Ortho and Persp are reversing Z depth sign? 【发布时间】:2018-10-18 22:46:16 【问题描述】:

OpenGL 的 NDC 坐标形成一个立方体,它的 -Z 侧压在屏幕上,而它的 +Z 侧距离最远。

当我使用...

// ortho arguments are: left, right,  bottom, top,  near, far
pos = pos * glm::ortho<float>(-1, 1, -1, 1, -1, 1);

...posz 组件被反射; -1 变为 1,10 变为 -10,依此类推。

glm::persp 做了类似的事情,这有点奇怪?如果一个位置的z 等于near,我希望它位于 NDC 立方体的面向屏幕的平面上,但它的符号是任意翻转的;它甚至不会落在最远的一侧。

这是为什么?

【问题讨论】:

Song Ho Ahn 做了一个关于转换的很好的教程:OpenGL Projection Matrix,我会定期重新访问以理清思路。所有的转变有时让我头晕目眩。但这次我很确定,因为我最近做了一个玩具项目,我“手动”制作了所有东西:NoGL3dDemo. 在 GL 1.0 时代,我们使用gluPerspective 作为透视矩阵,它反转了 Z 轴。 GLM 数学是在很久之后创建的,我猜他们想保持兼容性。如果您在+Z-Z 方向查看,这并不重要,因为NDC 中的深度值仍然是[-1,+1],您只需相应地设置正面和深度测试功能......所以如果你想要你只需反转Z 轴通过缩放返回 ... 【参考方案1】:

OpenGL的NDC坐标形成一个立方体,它的-Z侧压在屏幕上,而+Z侧最远。

我查看了有关 OpenGL 转换的 Song Ho Ahns 教程,以确保不会说一些愚蠢的事情。

Perspective Projection

在透视投影中,截头锥体(眼睛坐标)中的 3D 点被映射到立方体 (NDC); x 坐标的范围从 [l, r] 到 [-1, 1],y 坐标从 [b, t] 到 [-1, 1] 和 z 坐标从 [-n, -f]到 [-1, 1]。

请注意,眼睛坐标是在右手坐标系中定义的,但NDC 使用左手坐标系。也就是说,原点处的相机在眼睛空间中是沿-Z轴看,但在NDC中是沿+Z轴看。

(强调是我的。)

他为此提供了以下很好的说明:

所以,我得出的结论是

glm::ortho<float>(-1, 1, -1, 1, -1, 1);

不应该产生一个单位矩阵,而是一个 z 轴被镜像的单位矩阵,例如像

|  1  0  0  0 |
|  0  1  0  0 |
|  0  0 -1  0 |
|  0  0  0  1 |

由于手头没有glm,我从github上的源代码(glm)中获取了相关代码行。在源码里挖了一阵子,终于在orthoLH_ZO()中找到了glm::ortho()的实现:

template<typename T>
GLM_FUNC_QUALIFIER mat<4, 4, T, defaultp> orthoLH_ZO(T left, T right, T bottom, T top, T zNear, T zFar)

    mat<4, 4, T, defaultp> Result(1);
    Result[0][0] = static_cast<T>(2) / (right - left);
    Result[1][1] = static_cast<T>(2) / (top - bottom);
    Result[2][2] = static_cast<T>(1) / (zFar - zNear);
    Result[3][0] = - (right + left) / (right - left);
    Result[3][1] = - (top + bottom) / (top - bottom);
    Result[3][2] = - zNear / (zFar - zNear);
    return Result;

我稍微修改了这段代码以制作以下示例:

#include <iomanip>
#include <iostream>

struct Mat4x4 
  double values[4][4];
  Mat4x4()  
  Mat4x4(double val)
  
    values[0][0] = val; values[0][1] = 0.0; values[0][2] = 0.0; values[0][3] = 0.0;
    values[1][0] = 0.0; values[1][1] = val; values[1][2] = 0.0; values[1][3] = 0.0;
    values[2][0] = 0.0; values[2][1] = 0.0; values[2][2] = val; values[2][3] = 0.0;
    values[3][0] = 0.0; values[3][1] = 0.0; values[3][2] = 0.0; values[3][3] = val;
  
  double* operator[](unsigned i)  return values[i]; 
  const double* operator[](unsigned i) const  return values[i]; 
;

Mat4x4 ortho(
  double left, double right, double bottom, double top, double zNear, double zFar)

  Mat4x4 result(1.0);
  result[0][0] = 2.0 / (right - left);
  result[1][1] = 2.0 / (top - bottom);
  result[2][2] = - 1;
  result[3][0] = - (right + left) / (right - left);
  result[3][1] = - (top + bottom) / (top - bottom);
  return result;


std::ostream& operator<<(std::ostream &out, const Mat4x4 &mat)

  for (unsigned i = 0; i < 4; ++i) 
    for (unsigned j = 0; j < 4; ++j) 
      out << std::fixed << std::setprecision(3) << std::setw(8) << mat[i][j];
    
    out << '\n';
  
  return out;


int main()

  Mat4x4 matO = ortho(-1.0, 1.0, -1.0, 1.0, -1.0, 1.0);
  std::cout << matO;
  return 0;

编译并启动它提供以下输出:

   1.000   0.000   0.000   0.000
   0.000   1.000   0.000   0.000
   0.000   0.000  -1.000   0.000
  -0.000  -0.000   0.000   1.000

Live Demo on coliru

嗯! z 用 -1 缩放,即 z 值在 x-y 平面上镜像(如预期的那样)。

因此,OP的观察是完全正确和合理的:

...反映pos的z分量; -1 变为 1,10 变为 -10,依此类推。


最难的部分:

这是为什么?

我个人的猜测:发明所有这些 GL 东西的 SGI 大师之一在她/他的智慧中做到了这一点。

另一个猜测:在眼睛空间中,x 轴指向右侧,y 轴指向上方。将其转换为屏幕坐标,y 轴应指向下方(因为像素通常/技术上从左上角开始处理)。因此,这引入了另一个镜像轴,它(再次)改变了坐标系的手性。

这有点不满意,因此我用谷歌搜索并找到了这个(重复?):

SO: Why is the Normalized Device Coordinate system left-handed?

【讨论】:

以上是关于Ortho 和 Persp 正在反转 Z 深度符号?的主要内容,如果未能解决你的问题,请参考以下文章

Computer Graphics note:视图变换&投影变换

为啥 z 坐标没有被 glm::ortho() 投影归一化?

Blender 界面操作

OpenGL:切换和反转轴

旋转 persp3d 绘图并将图像另存为 png

将 Ortho 切换到 OpenGL HUD 的透视图