透视投影矩阵
透视投影矩阵
[!Quote]
Reference: LearnWebGL
[!Info]
视锥体定义:
- 近裁剪面 \(n\) 和远裁剪面 \(f\):定义可见深度范围;
- 左右边界 \(l,r\) 和顶底边界 \(t,b\):定义视锥体在近裁剪面处的水平和垂直范围;
归一化设备坐标 (Normalized Device Coordinates,NDC) 用于表示经过投影变换和裁剪后的坐标。NDC 空间是一个三维或二维的标准化坐标系统,通常将坐标范围限定在 \([−1,1]\) 之间,从而将不同的三维场景转换到统一的二维视图中。

透视投影矩阵将视锥体内的顶点投影到 NDC 空间中,可以分解为:
- 将视锥体的顶点平移到原点;
- 进行透视计算;
- 将视图窗口中的 2D \((x', y')\) 缩放到 \(2 \times 2\) 的单位矩形中 \([-1,-1] \times [1,1]\);
- 将深度值 \(z\) 缩放到归一化范围 \([-1,1]\) 中;
- 反转 \(z\) 轴的方向以匹配裁剪体积的方向;
1.平移视锥体顶点
透视视锥体 XY 平面的中心可能不在全局坐标系的原点上,因此需要通过下面的变换矩阵将其平移到原点上,深度值 \(z\) 保持不变。
2.透视计算
我们需要将场景中的每个顶点投影到二维视图窗口 (viewing window) 中(二维视图窗口与视锥体的近裁剪面相同)。空间顶点 \((x, y, z)\) 投影到视图窗口上的像素点的渲染位置为 \((x', y', n)\)。从下图可以看出,\(y\) 和 \(y'\) 的值通过相似直角三角形相关联,可得 \(y' = (y \cdot n) / z\),同理可得 \(x' = (x \cdot n) / z\)(对于某个特定场景,\(n\) 是一个常数,而场景中每个顶点的 \(y\) 和 \(z\) 值则不同)。

由上图可知三维空间中的顶点 \((x,y,z)\) 投影到二维视图窗口上 \((x',y',z')\) 需要满足:
又因为相机朝向的方向被定义为 \(z\) 轴的反方向,所以上面的式子实际应改为:
[!Tip]
矩阵运算是线性的,无法进行形式 \(\frac{1}{z}\) 的运算,因此我们需要借助齐次坐标来实现这一运算。齐次坐标 \((x,y,z,w)\) 对应于三维空间中坐标为 \(\left( \frac{x}{w}, \frac{y}{w}, \frac{z}{w} \right)\) 的一个顶点;通常情况下 \(w\) 取 \(1\) 使得 \((x,y,z,w) \mapsto (x,y,z)\)。
为了实现上面的透视计算,我们需要借助齐次坐标和变换矩阵将其拆解为两步:
- 先执行线性的矩阵变换;
- 将 \(w\) 设置为 \(-z\),进行齐次除法 (homogeneous division);
上述操作可以统一到一个变换矩阵中 (其中齐次除法为后处理步骤):
3.缩放视图窗口
将 \(x\) 坐标从 \([l,r]\) 映射到 \([-1,1]\) 中,将 \(y\) 坐标从 \([b,t]\) 映射到 \([-1,1]\) 中,有下面的变换矩阵:
4.深度值归一化
经过上面的变换,我们已经计算出了顶点在二维视图窗口中的正确位置,但我们还需将 \(z\) 值映射到 \([-1,1]\) 中。将深度值 \(z\) 从 \((-n, -f)\) 线性映射到 \((-1,1)\) 是一种方法,但是这种方法有一些问题:浮点数在运算时会因舍入误差而受到影响,在图形应用程序中 \(0.1234568\) 和 \(0.1234567\) 之间的差异会对渲染产生视觉影响;因此我们希望对靠近相机的值使用更高的精度,而对远离相机的顶点使用较低的精度。这意味着我们需要在 \((-n, -f)\) 和 \((-1,1)\) 之间进行非线性映射。
通常使用非线性函数 \(f(z) = c_{1} / (-z) + c_{2}\) 来实现上述的映射,代入 \(f(-n) = -1\) 和 \(f(-f) = 1\) 可得:
解得 \(c_{1} = 2 nf / (n-f)\),\(c_{2} = (n+f) / (f-n)\)。
[!Question]
非线性函数 \(f(z) = \frac{c_{1}}{-z} + c_{2}\) 的一个问题是无法用矩阵运算表示,因此需要借助 [[#2.透视计算]] 中的方法,使用齐次坐标来处理 \(\frac{1}{-z}\) 的操作;为此,需要将 \(\frac{c_{1}}{-z} + c_{2}\) 变形为 \((-c_{2} \cdot z + c_{1}) / (-z)\)。
将上述非线性变换的上半部分 \((-c_{2} \cdot z + c_{1})\) 用矩阵变换实现,将下半部分 \(1 / (-z)\) 交给齐次坐标处理 (注:作用此变换时需要保证 \(w=1\))。
5.反转 \(z\) 轴方向
当把 \(z\) 轴进行非变换时,变换后的范围 \((-1,1)\) 已经实现了 \(z\) 轴的反转,因此不需要额外的操作了。
6.整合所有变换
透视变换和深度值归一化中都将 \(w\) 变换为了 \((-z)\),但是这一操作不需要做两次,因此我们删去透视变换中的 \(-1\)。其中深度值归一化变换被移到了第二项。
将四个变换矩阵相乘并代入 \(c_{1}\) 和 \(c_{2}\) 可得一般形式的三维透视投影矩阵:
通常情况下视锥体是对称的,因此有 \(l=-r\) 和 \(b = -t\),此时有标准透视投影矩阵:
Appendix
线性变换
假设我们需要构造一个线性变换将区间 \([m,n]\) 映射到 \([s,t]\) 中;由于是线性变换,所以可将变换函数设为 \(f(x) = ax + b\);且有 \(f(m) = s\) 和 \(f(n) = t\),联立方程组解得:


浙公网安备 33010602011771号