透视矩阵背后的数学

The math behind the perspective matrix

我想创建自己的 mvp 矩阵作为练习。

auto lookAt(Vec)(const Vec eye, const Vec center, const Vec up)
if(isVector!Vec && Vec.dimension is 3){
    auto z = (eye - center).unit;
    auto x = cross(up.unit, z);
    auto y = cross(z, x);
    alias Mat = Matrix!(Vec.Type, 4, 4);
    alias Vec4 = Vector!(Vec.Type, 4);
    return Mat(Vec4(x, 0),
               Vec4(y, 0),
               Vec4(z, 0),
               Vec4(0, 0, 0, 1)) * translate(-eye);
}

视图转换相当简单,我将其设置为现在沿着负 Z 轴看。

但是我在透视矩阵方面遇到了一些麻烦。

y' = 1 / (tan(fov/2) * z
x' = 1 / (ar * tan(fov/2) * z

其中 x'y' 是新的扭曲值。所以我想出了这个矩阵

Mat4f projection(float fov, float ar, float near, float far){
  import std.math: tan;
  auto tanHalfAngle = tan(fov/2.0f);
  return Mat4f(
      Vec4f(1.0f / (ar * tanHalfAngle) , 0, 0, 0),
      Vec4f(0, 1.0f / tanHalfAngle, 0, 0),
      Vec4f(0, 0, 1, 0),
      Vec4f(0, 0, -1, 0));
}

我目前只是将其设置为 Z 值始终为 -1。但是我犯了一个小错误,因为现在我除以 -Z

问题是上面的 Matrix 有效。我目前正在渲染一个旋转的立方体,除了深度值之外,一切看起来都是正确的。

但我真的很想要这个矩阵

Mat4f projection(float fov, float ar, float near, float far){
  import std.math: tan;
  auto tanHalfAngle = tan(fov/2.0f);
  return Mat4f(
      Vec4f(1.0f / (ar * tanHalfAngle) , 0, 0, 0),
      Vec4f(0, 1.0f / tanHalfAngle, 0, 0),
      Vec4f(0, 0, -1, 0),
      Vec4f(0, 0, 1, 0));
}

但是立方体不再出现在屏幕上,我不明白为什么。

auto view = lookAt(Vec3f(0, 0, -5), Vec3f(0, 0, 0), Vec3f(0, 1, 0));
auto proj = projection(PI/2, 4.0f/3.0f, 0.1f, 100);

我设置测试值

auto v = proj * view * Vec4f(0.5,0.5,0.5,1);

这是我用第一个矩阵除以 -Z

得到的输出
Vec(-0.375, 0.5, -5.5, 5.5)
Vec(-0.0681818, 0.0909091, -1) // divided by w

这是我用第二个矩阵除以 Z

得到的输出
Vec(-0.375, 0.5, 5.5, -5.5)
Vec(0.0681818, -0.0909091, -1) //divided by w

唯一改变的是标志。

但为什么我能看到第一个矩阵的立方体,而第二个矩阵却什么都没有?

OpenGL 的默认裁剪规则是在裁剪 space 中制定的,因为满足以下不等式的所有点都位于视锥体内:

-w <= x, y, z <= w

请注意,这很简单地排除了 w < 0 的任何点,因为那时 -w 将 > w,因此无法满足。

The view transformation was rather simple, I set it up so that I now look along the negative Z axis.

事实并非如此。视图矩阵单独定义视图方向,投影矩阵也有影响。当使用标准透视投影时,投影矩阵的最后一行可以简单地解释为投影主轴的方向向量 - 因此它定义了眼睛中的观察方向向量 space。经典 GL 投影使用 (0 0 -1 0),因此 -z 将是观察方向。

视图矩阵的工作现在是放置相机并旋转场景,以便将目标世界 space 观察方向映射到眼睛 space 投影矩阵正在使用的观察方向.

当您翻转投影矩阵以除以 z 而不是 -z 时,这当然意味着它会朝那个方向看。由于您没有调整您的视图矩阵以遵循该惯例,您的相机现在正以您的视图矩阵设置的相反方向观看。位于 z_eye < 0 相机 "front" 中的点都具有负值 w_clip,并被近平面裁剪。