opengl Ortho和Persp颠倒了Z深度符号?

jm81lzqq  于 2023-01-20  发布在  其他
关注(0)|答案(1)|浏览(172)

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,以此类推。
persp做了类似的事情,这有点奇怪。如果一个位置的z等于near,我希望它停留在NDC立方体面向屏幕的平面上,但相反,它的符号被任意翻转;它甚至不会落在最远的那一面。
这是为什么呢?

zphenhs4

zphenhs41#

OpenGL的NDC坐标形成了一个立方体,它的-Z面压在屏幕上,而它的+Z面离屏幕最远。
我看了一下Song Ho Ahns关于OpenGL转换的教程,以确保不会说一些愚蠢的事情。

    • 透视投影**

在透视投影中,截棱锥体中的3D点(视点坐标)被Map到立方体(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)上的源代码中取出了相关代码行,在源代码中挖掘了一会儿,终于找到了glm::ortho()orthoLH_ZO()中的实现:

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
Huh! z按-1缩放,即z值在x-y平面上镜像(如预期)。
因此,OP的观察是完全正确和合理的:
......反映pos的z分量;-1变为1,10变为-10,以此类推。
最难的部分:
这是为什么呢?
我个人猜测:一个SGI大师发明了所有这些GL的东西,他/她很聪明。
另一种猜测:在眼睛空间中,x轴指向右侧,y轴指向上方。将其转换为屏幕坐标,y轴应该指向下方(因为像素通常/技术上从左上角开始寻址)。因此,这引入了另一个镜像轴,它(再次)改变了坐标系的手性。
这是一个有点不满意,因此我谷歌,发现这(重复?):
SO: Why is the Normalized Device Coordinate system left-handed?

相关问题