透视投影矩阵

透视投影矩阵

[!Quote]
Reference: LearnWebGL

[!Info]
视锥体定义:

  • 近裁剪面 \(n\)远裁剪面 \(f\):定义可见深度范围;
  • 左右边界 \(l,r\)顶底边界 \(t,b\):定义视锥体在近裁剪面处的水平和垂直范围;

归一化设备坐标 (Normalized Device Coordinates,NDC) 用于表示经过投影变换和裁剪后的坐标。NDC 空间是一个三维或二维的标准化坐标系统,通常将坐标范围限定在 \([−1,1]\) 之间,从而将不同的三维场景转换到统一的二维视图中。


透视投影矩阵将视锥体内的顶点投影到 NDC 空间中,可以分解为:

  1. 将视锥体的顶点平移到原点;
  2. 进行透视计算;
  3. 将视图窗口中的 2D \((x', y')\) 缩放到 \(2 \times 2\) 的单位矩形中 \([-1,-1] \times [1,1]\)
  4. 将深度值 \(z\) 缩放到归一化范围 \([-1,1]\) 中;
  5. 反转 \(z\) 轴的方向以匹配裁剪体积的方向;

1.平移视锥体顶点

透视视锥体 XY 平面的中心可能不在全局坐标系的原点上,因此需要通过下面的变换矩阵将其平移到原点上,深度值 \(z\) 保持不变。

\[\begin{bmatrix} 1 & 0 & 0 & -\frac{l+r}{2} \\ 0 & 1 & 0 & -\frac{b+t}{2} \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} \cdot \begin{bmatrix} x \\ y \\ z \\ 1 \end{bmatrix} = \begin{bmatrix} x' \\ y' \\ z' \\ w' \end{bmatrix} \]

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')\) 需要满足:

\[\begin{align} x' & = \frac{x \cdot n}{z} \\ y' & = \frac{y \cdot n}{z} \end{align} \]

又因为相机朝向的方向被定义为 \(z\) 轴的反方向,所以上面的式子实际应改为:

\[\begin{align} x' & = \frac{x \cdot n}{-z} \\ y' & = \frac{y \cdot n}{-z} \end{align} \]


[!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)\)

为了实现上面的透视计算,我们需要借助齐次坐标和变换矩阵将其拆解为两步:

  1. 先执行线性的矩阵变换;
  2. \(w\) 设置为 \(-z\),进行齐次除法 (homogeneous division);

上述操作可以统一到一个变换矩阵中 (其中齐次除法为后处理步骤):

\[\begin{bmatrix} n & 0 & 0 & 0 \\ 0 & n & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & -1 & 0 \end{bmatrix} \cdot \begin{bmatrix} x \\ y \\ z \\ w \end{bmatrix} = \begin{bmatrix} x' \\ y' \\ z' \\ w' \end{bmatrix} \]

3.缩放视图窗口

\(x\) 坐标从 \([l,r]\) 映射到 \([-1,1]\) 中,将 \(y\) 坐标从 \([b,t]\) 映射到 \([-1,1]\) 中,有下面的变换矩阵:

\[\begin{bmatrix} \frac{2}{r-l} & 0 & 0 & 0 \\ 0 & \frac{2}{t-b} & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} \cdot \begin{bmatrix} x \\ y \\ z \\ w \end{bmatrix} = \begin{bmatrix} x' \\ y' \\ z' \\ w' \end{bmatrix} \]

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\) 可得:

\[\left\{ \begin{align} \frac{c_{1}}{-(-n)} + c_{2} & = -1 \\ \frac{c_{1}}{-(-f)} + c_{2} & = 1 \end{align} \right. \]

解得 \(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\))。

\[\begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & -c_{2} & c_{1} \\ 0 & 0 & -1 & 0 \end{bmatrix} \cdot \begin{bmatrix} x \\ y \\ z \\ 1 \end{bmatrix} = \begin{bmatrix} x' \\ y' \\ z' \\ w' \end{bmatrix} \]

5.反转 \(z\) 轴方向

当把 \(z\) 轴进行非变换时,变换后的范围 \((-1,1)\) 已经实现了 \(z\) 轴的反转,因此不需要额外的操作了。

6.整合所有变换

透视变换和深度值归一化中都将 \(w\) 变换为了 \((-z)\),但是这一操作不需要做两次,因此我们删去透视变换中的 \(-1\)。其中深度值归一化变换被移到了第二项。

\[\underbrace{\begin{bmatrix} \frac{2}{r-l} & 0 & 0 & 0 \\ 0 & \frac{2}{t-b} & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix}}_{\mathrm{缩放视图窗口}} \cdot \underbrace{\begin{bmatrix} n & 0 & 0 & 0 \\ 0 & n & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix}}_{\mathrm{透视计算}} \cdot \underbrace{\begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & -c_{2} & c_{1} \\ 0 & 0 & -1 & 0 \end{bmatrix}}_{\mathrm{深度归一化}} \cdot \underbrace{\begin{bmatrix} 1 & 0 & 0 & - \frac{l+r}{2} \\ 0 & 1 & 0 & - \frac{b+t}{2} \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix}}_{\mathrm{平移视锥体}} \cdot \begin{bmatrix} x \\ y \\ z \\ 1 \end{bmatrix} = \begin{bmatrix} x' \\ y' \\ z' \\ w' \end{bmatrix} \]

将四个变换矩阵相乘并代入 \(c_{1}\)\(c_{2}\) 可得一般形式的三维透视投影矩阵:

\[\begin{bmatrix} \frac{2n}{r-l} & 0 & 0 & -\frac{n(r+l)}{r-l} \\ 0 & \frac{2n}{t-b} & 0 & -\frac{n(b+t)}{t-b} \\ 0 & 0 & -\frac{n+f}{f-n} & \frac{2nf}{n-f} \\ 0 & 0 & -1 & 0 \end{bmatrix} \]

通常情况下视锥体是对称的,因此有 \(l=-r\)\(b = -t\),此时有标准透视投影矩阵:

\[M = \begin{bmatrix} \frac{n}{r} & 0 & 0 & 0 \\ 0 & \frac{n}{t} & 0 & 0 \\ 0 & 0 & \frac{-(f+n)}{f-n} & \frac{-2nf}{f-n} \\ 0 & 0 & -1 & 0 \end{bmatrix} \]

Appendix

线性变换

假设我们需要构造一个线性变换将区间 \([m,n]\) 映射到 \([s,t]\) 中;由于是线性变换,所以可将变换函数设为 \(f(x) = ax + b\);且有 \(f(m) = s\)\(f(n) = t\),联立方程组解得:

\[f(x) = \frac{t-s}{n-m}(x - m) + s. \]

posted @ 2025-06-26 09:44  Ayanami·  阅读(211)  评论(0)    收藏  举报