透视变换矩阵不能仅分解为缩放、平移、旋转和剪切操作。这些操作都是仿射的,而透视变换是投影的(不是仿射的;特别地,透视不会保留线的平行性)。
“目标体积”是规范化设备空间中的轴对齐立方体。在标准的OpenGL中,这个立方体在所有维度上从-1到1(Direct3D和Vulkan也使用稍微不同的约定:[-1,1]用于x和y,但[0,1]用于z。这意味着矩阵的第三行看起来有点不同,但概念相同)。
投影矩阵是这样构建的,使金字塔锥体被转换为规范化体积。OpenGL还使用了这样的约定,在眼睛空间中,摄像机朝向-z。要创建透视效果,只需通过将投影中心与所讨论点连接的射线与实际观察平面相交来投影每个点。
上述透视矩阵假设图像平面平行于xy平面(如果要不同,则可以应用一些仿射旋转)。在OpenGL眼睛空间中,投影中心始终位于原点。当你进行数学运算时,你会发现透视归结为截距定理的一个简单实例。如果要将所有点投影到距中心(“相机”)1个单位的平面上,则最终需要将x和y除以-z。
这可以用矩阵形式写成。
( 1 0 0 0 )
( 0 1 0 0 )
( 0 0 1 0 )
( 0 0 -1 0 )
它的工作原理是通过设置w_clip = -z_eye
,因此当执行裁剪空间w
除法时,我们会得到:
x_ndc = x_clip / w_clip = - x_eye / z_eye
y_ndc = y_clip / w_clip = - y_eye / z_eye
请注意,这也适用于 z:。
z_ndc = z_clip / w_clip = - z_eye / z_eye = 1
通常情况下,这种矩阵不用于渲染,因为深度信息丢失了——所有点实际上都投影到一个平面上。通常,我们希望保留深度(可能以某种非线性偏差的方式)。为了做到这一点,我们可以调整z的公式(第三行)。由于我们不希望z依赖于x和y,因此只有最后一个元素可以进行调整。通过使用形如
(0 0 A B)
的行,我们得到以下方程:
z_ndc = - A * z_eye / z_eye - B / z_eye = -A - B / z_eye
这只是眼空间z值的双曲变换,深度仍然保持不变,矩阵可逆。我们只需要计算A和B。
让我们将函数
z_ndc(z_eye) = -A - B / z_eye
称为
Z(z_eye)
。由于观察体积被
z_ndc = -1
(前平面)和
z_ndc = 1
所限制,并且近平面和远平面在眼空间的距离以参数给出,我们必须将近平面
z_eye=-n
映射到-1,将远平面
z_eye=-f
映射到1。为了选择A和B,我们必须解决一个2个非线性方程组的系统:
Z(-n) = -1
Z(-f) = 1
这将得到您在矩阵的第三行找到的两个系数。
对于x和y,我们想要控制两件事:视场角度和棱台的不对称性(类似于投影仪中所知道的“镜头移位”)。视场角由映射到NDC中的[-1,1]的图像平面上的x和y范围定义。因此,您可以想象一个与图像平面平行的任意平面上的轴对齐矩形。该矩形描述了从摄像机选择的距离映射到可见视口的场景的一部分。改变视场角只是在x和y中缩放该矩形。
概念上,镜头移位只是一个平移,因此您可能认为它应该放在最后一列。但是,由于除法w=-z
将在矩阵乘法之后进行,我们必须首先将该平移乘以-z
。这意味着平移部分现在在第三列中,我们有一个形式的矩阵:
( C 0 D 0 )
( 0 E F 0 )
( 0 0 A B )
( 0 0 -1 0 )
对于x,这给出了:
x_clip = (x_eye * C + D * z_eye ) / (-z_eye) = -x_eye / z_eye - D
现在我们只需要找到正确的系数C和D,将x_eye=l映射到x_ndc=-1,将x_eye=r映射到x_ndc=1。请注意,经典的GL截锥函数将此处的l和r值解释为近平面上的距离,因此我们必须针对z_eye=-n计算所有内容。解决这个新的2元方程组,您将得到在截锥体矩阵中看到的那些系数。
z_eye=-n
和z_eye=-f
中,有点难以确定您是希望n
和f
小于0还是-n
和-f
小于0?也就是说,n
和f
是作为正数给出还是负数给出的?我的理解是它们应该是负数,但我很难将其与在https://dev59.com/3F8e5IYBdhLWcg3wlbPx中给出的正数解释答案相一致。 - WoodMathn
和f
被赋予为视线方向中近平面和远平面的_距离_,因此它们始终为正值。由于视线方向通常为-z_eye
,这些平面位于负的z_eye
值处。 - derhassn
和f
被赋予正数时,n
比f
更为正向(即n
是否大于f
)?我认为由于我们是沿着负_z_轴看,n
确实会更为正向。然而,在Songho.ca的图表中,n
和f
从它们的正值取反了,因此-f
是最负的,这意味着原始的正值f
大于n
(即f
大于n
)。这似乎是违反直觉的。 - WoodMathf > n
。你只需要设置f,n > 0
并且f != n
。如果你设置0 < f < n
,那么只需沿着 z 轴进行翻转即可。 - derhass