Deep Read

正交投影矩阵推导过程

这篇是 【空间变换】投影变换【空间变换】投影变换经过 [[【空间变换】观察变换]],顶点已经来到以相机为原点的观察空间。投影变换要做的,是把观察空间里相机能看到的那块区域,映射到一个规整的立方体里,方便后续裁剪。这一步的矩阵叫裁剪矩阵,也叫投影矩阵。本篇沿用 Games101 的思路推导。 先把结论列在前面,推完再回看会更清楚: - 把顶点变换到裁剪空间的目的,是方便做裁剪。 - 正交相机的裁剪空间,就是一个标准立方体(CVV)。 - 推导透视投影时,我们会乘上一个观察空间的 $z$ 值($z_{view}$),这个 $z$ 最终成为裁剪空间的 $w$ 分量。 - 透视相机的裁剪,是判断坐标 $(x, y, z)$ 是否落在 $(-w, w)$ 之内。 裁剪空间 顶点从观察空间变换到裁剪空间(Clip Space,也叫齐次裁剪空间),用的就是投影矩阵。之所以要这块空间,是为了方便对图元做裁剪: - 完全落在这块空间内部的图元,保留; - 完全落在外部的,剔除; - 与边界相交的,裁剪掉超出的部分。 这块空间由视锥体(View Frustum) 决定——它是空间中一块由六个裁剪平面(Clip Planes)围成的区域,界定 里正交投影缩放矩阵的详细推导。核心问题很简单:观察空间里一段区间 [l,r][l, r],要线性映射到裁剪空间的 [−1,1][-1, 1],这个缩放系数怎么来。xx、yy、zz 三个方向各推一遍,其中 zz 还要区分 OpenGL 和 DirectX 的平台差异。

缩放矩阵的目标

image.png

要找一个矩阵,把观察空间坐标 (xview,yview,zview,1)(x_{view}, y_{view}, z_{view}, 1) 变换到裁剪空间 (xp,yp,zp,1)(x_p, y_p, z_p, 1):

[????????????0001][xviewyviewzview1]=(xp, yp, zp, 1)\begin{bmatrix} ? & ? & ? & ? \\ ? & ? & ? & ? \\ ? & ? & ? & ? \\ 0 & 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} x_{view} \\ y_{view} \\ z_{view} \\ 1 \end{bmatrix} = (x_p,\ y_p,\ z_p,\ 1)

已知 xx 在相机空间的范围是 (l,r)(l, r),变换后要落到裁剪空间的 (−1,1)(-1, 1)。下面用一串不等式变形把系数逼出来。

x 方向推导

从 l<x<rl < x < r 出发,目标是凑出 −1<(⋯ )<1-1 < (\cdots) < 1 的形式:

l<x<r0<x−l<r−l0<(x−l)⋅2r−l<20<2x−2lr−l<2−1<2x−2lr−l−1<1−1<2x−2lr−l−r−lr−l<1−1<2x−l−rr−l<1\begin{aligned} & l < x < r \\ & 0 < x - l < r - l \\ & 0 < (x - l)\cdot\frac{2}{r-l} < 2 \\ & 0 < \frac{2x - 2l}{r-l} < 2 \\ & -1 < \frac{2x - 2l}{r-l} - 1 < 1 \\ & -1 < \frac{2x - 2l}{r-l} - \frac{r-l}{r-l} < 1 \\ & -1 < \frac{2x - l - r}{r-l} < 1 \end{aligned}

在对称视锥体(r=−lr = -l,即 l+r=0l + r = 0)下,分子的 −l−r-l - r 项消去:

−1<2xr−l<1⟹−1<x⋅2r−l<1-1 < \frac{2x}{r-l} < 1 \quad\Longrightarrow\quad -1 < x\cdot\frac{2}{r-l} < 1

于是 xx 方向的缩放系数就是 2r−l\dfrac{2}{r-l}。

y 方向推导

和 xx 完全一样,把 rr 换成 tt、ll 换成 bb,得到系数 2t−b\dfrac{2}{t-b}。

z 方向推导

zz 要特别小心两点:相机空间是右手坐标系、看向 −z-z;而不同图形 API 对裁剪空间 zz 的目标范围约定不同:

  • OpenGL:(−1,1)(-1, 1)
  • DirectX:(0,1)(0, 1),若反转 zz 值则为 (1,0)(1, 0)

OpenGL:映射到 (-1, 1)

−n≤z≤−f0≤z+n≤n−f0≤(z+n)⋅2n−f≤20≤2z+2nn−f≤2−1≤2z+2nn−f−n−fn−f≤1−1≤2z+2n−n+fn−f≤1−1≤2z+n+fn−f≤1−1≤2zn−f+n+fn−f≤1\begin{aligned} & -n \leq z \leq -f \\ & 0 \leq z + n \leq n - f \\ & 0 \leq (z + n)\cdot\frac{2}{n-f} \leq 2 \\ & 0 \leq \frac{2z + 2n}{n-f} \leq 2 \\ & -1 \leq \frac{2z + 2n}{n-f} - \frac{n-f}{n-f} \leq 1 \\ & -1 \leq \frac{2z + 2n - n + f}{n-f} \leq 1 \\ & -1 \leq \frac{2z + n + f}{n-f} \leq 1 \\ & -1 \leq \frac{2z}{n-f} + \frac{n+f}{n-f} \leq 1 \end{aligned}

DirectX:反转 z 值,映射到 (1, 0)

−n≤z≤−ff−n≤z+f≤01≤(z+f)⋅1f−n≤01≤z+ff−n≤01≤zf−n+ff−n≤0\begin{aligned} & -n \leq z \leq -f \\ & f - n \leq z + f \leq 0 \\ & 1 \leq (z + f)\cdot\frac{1}{f-n} \leq 0 \\ & 1 \leq \frac{z + f}{f-n} \leq 0 \\ & 1 \leq \frac{z}{f-n} + \frac{f}{f-n} \leq 0 \end{aligned}

DirectX:不反转 z 值,映射到 (0, 1)

−n≤z≤−f0≤z+n≤n−f0≤(z+n)⋅1n−f≤10≤z+nn−f≤10≤zn−f+nn−f≤1\begin{aligned} & -n \leq z \leq -f \\ & 0 \leq z + n \leq n - f \\ & 0 \leq (z + n)\cdot\frac{1}{n-f} \leq 1 \\ & 0 \leq \frac{z + n}{n-f} \leq 1 \\ & 0 \leq \frac{z}{n-f} + \frac{n}{n-f} \leq 1 \end{aligned}

三个平台的缩放矩阵

把上面各方向的系数填回矩阵,就得到三种约定下的正交缩放矩阵。

OpenGL:

[2r−l00002t−b00002n−fn+fn−f0001]\begin{bmatrix} \frac{2}{r-l} & 0 & 0 & 0 \\ 0 & \frac{2}{t-b} & 0 & 0 \\ 0 & 0 & \frac{2}{n-f} & \frac{n+f}{n-f} \\ 0 & 0 & 0 & 1 \end{bmatrix}

DirectX,反转 z 值:

[2r−l00002t−b00001f−nff−n0001]\begin{bmatrix} \frac{2}{r-l} & 0 & 0 & 0 \\ 0 & \frac{2}{t-b} & 0 & 0 \\ 0 & 0 & \frac{1}{f-n} & \frac{f}{f-n} \\ 0 & 0 & 0 & 1 \end{bmatrix}

DirectX,不反转 z 值:

[2r−l00002t−b00001n−fnn−f0001]\begin{bmatrix} \frac{2}{r-l} & 0 & 0 & 0 \\ 0 & \frac{2}{t-b} & 0 & 0 \\ 0 & 0 & \frac{1}{n-f} & \frac{n}{n-f} \\ 0 & 0 & 0 & 1 \end{bmatrix}

zz 那一行的常数项(第四列),正是上面不等式变形里那个与 zz 无关的偏移量——它把缩放后的区间平移到目标范围的正确位置。