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],这个缩放系数怎么来。xxyyzz 三个方向各推一遍,其中 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<xl<rl0<(xl)2rl<20<2x2lrl<21<2x2lrl1<11<2x2lrlrlrl<11<2xlrrl<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)下,分子的 lr-l - r 项消去:

1<2xrl<11<x2rl<1-1 < \frac{2x}{r-l} < 1 \quad\Longrightarrow\quad -1 < x\cdot\frac{2}{r-l} < 1

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

y 方向推导

xx 完全一样,把 rr 换成 ttll 换成 bb,得到系数 2tb\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)

nzf0z+nnf0(z+n)2nf202z+2nnf212z+2nnfnfnf112z+2nn+fnf112z+n+fnf112znf+n+fnf1\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)

nzffnz+f01(z+f)1fn01z+ffn01zfn+ffn0\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)

nzf0z+nnf0(z+n)1nf10z+nnf10znf+nnf1\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:

[2rl00002tb00002nfn+fnf0001]\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 值:

[2rl00002tb00001fnffn0001]\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 值:

[2rl00002tb00001nfnnf0001]\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 无关的偏移量——它把缩放后的区间平移到目标范围的正确位置。