4.变换进阶
4.1 概述

主要内容:
- 三维变换(3D Transformations)
- 观测变换(Viewing Transformation)
- 视图 / 相机变换(View / Camera Transformation)
- 投影变换(Projection Transformation)
- 正交投影(Orthographic Projection)
- 透视投影(Perspective Projection)
从模型到最终画面,通常要经过三个主要变换:
- 模型变换(Model Transformation):利用平移、旋转、缩放等变换,把模型从自己的局部坐标系放到世界坐标系中。
- 视图变换(View Transformation):把世界变换到以相机为中心的坐标系。
- 投影变换(Projection Transformation):把相机看到的三维空间进一步投影到二维画面,分为正交投影和透视投影。
因此整个过程可以概括为:
Model → View → Projection
也就是常见的 MVP 变换。
4.2 三维变换
旋转矩阵的逆
二维旋转矩阵有一个很实用的性质:
$$
R(-\theta)=R(\theta)^{-1}=R(\theta)^T
$$
也就是说,旋转矩阵的逆等于它的转置。
满足 $R^{-1}=R^T$ 的矩阵称为正交矩阵(Orthogonal Matrix)。后面的视图变换会直接用到这个性质。
三维齐次坐标
三维空间继续使用齐次坐标:
- 三维点:$(x,y,z,1)^T$
- 三维向量:$(x,y,z,0)^T$
对于 $(x,y,z,w)^T$,只要 $w\neq0$,它表示三维空间中的点:
$$
\left(\frac{x}{w},\frac{y}{w},\frac{z}{w}\right)
$$

三维仿射变换使用 $4\times4$ 矩阵,左上角 $3\times3$ 描述线性变换,最后一列描述平移。
缩放与平移
三维缩放矩阵:
$$
S(s_x,s_y,s_z)=
\begin{pmatrix}
s_x&0&0&0\\
0&s_y&0&0\\
0&0&s_z&0\\
0&0&0&1
\end{pmatrix}
$$
三维平移矩阵:
$$
T(t_x,t_y,t_z)=
\begin{pmatrix}
1&0&0&t_x\\
0&1&0&t_y\\
0&0&1&t_z\\
0&0&0&1
\end{pmatrix}
$$
绕坐标轴旋转

绕 $x$ 轴旋转时,$x$ 坐标不变,本质上是在 $yz$ 平面内做二维旋转:
$$
R_x(\alpha)=
\begin{pmatrix}
1&0&0&0\\
0&\cos\alpha&-\sin\alpha&0\\
0&\sin\alpha&\cos\alpha&0\\
0&0&0&1
\end{pmatrix}
$$
绕 $y$ 轴:
$$
R_y(\alpha)=
\begin{pmatrix}
\cos\alpha&0&\sin\alpha&0\\
0&1&0&0\\
-\sin\alpha&0&\cos\alpha&0\\
0&0&0&1
\end{pmatrix}
$$
绕 $z$ 轴:
$$
R_z(\alpha)=
\begin{pmatrix}
\cos\alpha&-\sin\alpha&0&0\\
\sin\alpha&\cos\alpha&0&0\\
0&0&1&0\\
0&0&0&1
\end{pmatrix}
$$
$R_y$ 的符号看起来和另外两个不太一样,原因是右手系下满足循环关系,叉乘的时候 $x\times y=z$、$y\times z=x$、$z\times x=y$。
欧拉角(Euler Angles)

一般的三维旋转可以组合三个坐标轴方向的旋转:
$$
R_{xyz}(\alpha,\beta,\gamma)
=R_x(\alpha)R_y(\beta)R_z(\gamma)
$$
三个旋转角称为欧拉角(Euler Angles)。在飞行中经常对应 Roll(翻滚)、Pitch(俯仰)、Yaw(偏航)。
罗德里格斯旋转公式

绕经过原点的单位轴 $\mathbf n$ 旋转 $\alpha$ 时:
$$
R(\mathbf n,\alpha)
=\cos\alpha I
+(1-\cos\alpha)\mathbf n\mathbf n^T
+\sin\alpha N
$$
其中矩阵 $N$ 满足:
$$
N\mathbf x=\mathbf n\times\mathbf x
$$
补充材料给出的推导思路很直观:把待旋转向量分成平行于旋转轴和垂直于旋转轴的两部分。平行分量保持不变,垂直分量在垂直平面内做二维旋转,最后再合并。

如果旋转轴不经过原点,可以和二维绕任意点旋转一样处理:先把旋转轴平移到原点,再旋转,最后平移回去。
4.3 观测变换(Viewing Transformation)
视图 / 相机变换(View / Camera Transformation)

拍一张照片可以拆成三步:
- 把人物和场景摆好 —— 模型变换(Model Transformation)。
- 把相机放到合适的位置和角度 —— 视图变换(View Transformation)。
- 按下快门,把三维世界变成二维图像 —— 投影变换(Projection Transformation)。
定义相机

一个相机需要三个量:
- 位置:$\mathbf e$
- 观察方向:$\hat{\mathbf g}$
- 向上方向:$\hat{\mathbf t}$
其中观察方向和向上方向通常取单位向量,并假设彼此垂直。
只知道相机位置和观察方向还不够,因为相机还可以绕观察方向自身旋转;向上方向用来确定这个自由度。
把相机变换到标准位置

关键是相对运动:如果相机和场景中的所有物体一起做相同的变换,最终拍出的图像不会改变。
因此可以统一约定相机始终:
- 位于原点 $(0,0,0)$
- 向上方向为 $+Y$
- 观察方向为 $-Z$
我们理解真正变化的是整个场景。相机就是在原点一直不动的观察者。这样做是因为把相机放到这样一个标准位置有一些好处的。
视图变换矩阵

视图变换的目标,是把任意位置、任意朝向的相机变换到标准位置:
- 相机位置移动到原点。
- 观察方向 $\hat{\mathbf g}$ 对齐 $-Z$。
- 向上方向 $\hat{\mathbf t}$ 对齐 $+Y$。
- 右方向 $\hat{\mathbf g}\times\hat{\mathbf t}$ 对齐 $+X$。
因此视图矩阵可以拆成平移 + 旋转:
$$
M_{view}=R_{view}T_{view}
$$
使用列向量时,矩阵从右向左作用,所以这里表示:先平移,再旋转。
平移到原点
假设相机位置为:
$$
\mathbf e=(x_e,y_e,z_e)
$$
要把相机移动到原点,只需要对整个场景平移 $-\mathbf e$:
$$
T_{view}=
\begin{pmatrix}
1&0&0&-x_e\\
0&1&0&-y_e\\
0&0&1&-z_e\\
0&0&0&1
\end{pmatrix}
$$
例如相机位于 $(3,2,5)$,那么整个场景都平移 $(-3,-2,-5)$。
这样从相机看来画面不会发生变化,但相机的位置变成了原点。
旋转到标准方向

平移之后,相机虽然已经位于原点,但朝向仍然可能是任意的。
接下来需要把相机的三个方向:
$$
\hat{\mathbf g}\times\hat{\mathbf t},
\qquad
\hat{\mathbf t},
\qquad
-\hat{\mathbf g}
$$
分别旋转到标准坐标轴:
$$
X,\qquad Y,\qquad Z
$$
也就是:
- $\hat{\mathbf g}\times\hat{\mathbf t}\rightarrow X$
- $\hat{\mathbf t}\rightarrow Y$
- $-\hat{\mathbf g}\rightarrow Z$
直接写这个旋转矩阵不太好写,但它的逆变换很好写。
逆变换做的事情正好相反:
$$
X\rightarrow\hat{\mathbf g}\times\hat{\mathbf t}
$$
$$
Y\rightarrow\hat{\mathbf t}
$$
$$
Z\rightarrow-\hat{\mathbf g}
$$
矩阵的每一列就是一个标准基向量变换后的结果,因此:
$$
R_{view}^{-1} =
\begin{pmatrix}
x_{\hat g\times\hat t}&x_{\hat t}&x_{-\hat g}&0\\
y_{\hat g\times\hat t}&y_{\hat t}&y_{-\hat g}&0\\
z_{\hat g\times\hat t}&z_{\hat t}&z_{-\hat g}&0\\
0&0&0&1
\end{pmatrix}
$$
而旋转矩阵是正交矩阵,满足:
$$
R_{view}^{-1}=R_{view}^{T}
$$
所以只需要把上面的逆矩阵转置,就得到真正的视图旋转矩阵:
$$
R_{view} =
\begin{pmatrix}
x_{\hat g\times\hat t}&y_{\hat g\times\hat t}&z_{\hat g\times\hat t}&0\\
x_{\hat t}&y_{\hat t}&z_{\hat t}&0\\
x_{-\hat g}&y_{-\hat g}&z_{-\hat g}&0\\
0&0&0&1
\end{pmatrix}
$$
最终:
$$
M_{view}=R_{view}T_{view}
$$
核心思路就是:先把相机位置搬到原点,再把相机自身的三个坐标轴对齐到标准的 $X$、$Y$、$Z$ 方向。
实际计算时并不是把相机真的移动了,而是对整个场景施加相反的变换,使相机看起来固定在标准位置。
模型变换和视图变换通常连续进行,因此经常合称为 ModelView Transformation。
小结

投影变换(Projection Transformation)

投影负责把三维空间变成二维图像,主要有两种:
- 正交投影(Orthographic Projection)
- 透视投影(Perspective Projection)

两者最直观的区别是:
- 正交投影没有明显的近大远小。
- 透视投影符合人眼和相机的成像直觉,物体越远看起来越小,平行线也可能在图像中汇聚。
正交投影(Orthographic Projection)
直观理解

相机位于原点、观察 $-Z$、$+Y$ 向上时,一个最简单的理解方式是:
- 丢掉 $Z$ 坐标。
- 将剩下的二维区域平移、缩放到 $[-1,1]^2$。
真正的矩阵推导会保留深度信息,而不是简单把 $Z$ 丢掉。
映射到标准立方体


把空间中的长方体:
$$
[l,r]\times[b,t]\times[f,n]
$$
映射到标准立方体:
$$
[-1,1]^3
$$
只需要两步:
- 平移:把长方体中心移动到原点。
- 缩放:让三个方向的长度都变成 $2$。
正交投影矩阵

$$
M_{ortho}=
\begin{pmatrix}
\frac{2}{r-l}&0&0&0\\
0&\frac{2}{t-b}&0&0\\
0&0&\frac{2}{n-f}&0\\
0&0&0&1
\end{pmatrix}
\begin{pmatrix}
1&0&0&-\frac{r+l}{2}\\
0&1&0&-\frac{t+b}{2}\\
0&0&1&-\frac{n+f}{2}\\
0&0&0&1
\end{pmatrix}
$$
使用列向量时,右边的平移矩阵先执行,然后再缩放。

由于相机观察 $-Z$,课件直接用空间中的 $z$ 坐标表示 near 和 far,因此数值上有:
$$
n>f
$$
这个地方很容易因为不同坐标系约定而写反。
透视投影(Perspective Projection)
特点

透视投影具有两个非常直观的现象:
- 近大远小。
- 三维空间中的平行线,投影到图像后可能汇聚到同一个消失点。

回顾

先挤压,再做正交投影

透视投影可以拆成两步:
- 用 $M_{persp\to ortho}$ (从透视到正交的矩阵)把视锥体挤压成长方体,并保持近平面 $n$ 和远平面 $f$ 的 $z$ 值不变。
- 对得到的长方体做已经推导过的正交投影 $M_{ortho}$。
最终:
$$
M_{persp}=M_{ortho}M_{persp\to ortho}
$$
相似三角形得到 x、y

从侧面看视锥,通过相似三角形:
$$
y^{\prime}=\frac{n}{z}y
$$
同理:
$$
x^{\prime}=\frac{n}{z}x
$$
也就是说,深度 $z$ 越远,$x$、$y$ 被压缩得越多,这就是近大远小在数学上的来源。
利用齐次坐标写成矩阵

前面通过相似三角形已经得到:
\[
x^{\prime}=\frac{nx}{z},
\qquad
y^{\prime}=\frac{ny}{z}
\]
所以挤压后的点可以先写成:
\[
\begin{pmatrix}
\frac{nx}{z}\\
\frac{ny}{z}\\
z^{\prime}\\
1
\end{pmatrix}
\]
问题在于,矩阵乘法只能做线性组合,没法直接表示 \(x/z\) 这样的除法。
这里利用齐次坐标:
\[
(x,y,z,1)^T
\equiv
(kx,ky,kz,k)^T
\]
也就是说,齐次坐标整体乘上同一个非零数,表示的仍然是同一个点。
因此把上面的结果整体乘以 \(z\):
\[
\begin{pmatrix}
\frac{nx}{z}\\
\frac{ny}{z}\\
z^{\prime}\\
1
\end{pmatrix}
\equiv
\begin{pmatrix}
nx\\
ny\\
zz^{\prime}\\
z
\end{pmatrix}
\]
第三项还没有求出来,先记成 unknown:
\[
\begin{pmatrix}
nx\\
ny\\
\text{unknown}\\
z
\end{pmatrix}
\]
这样原来的除法就暂时消失了,可以开始写矩阵。
希望:
\[
M_{persp\to ortho}
\begin{pmatrix}
x\\
y\\
z\\
1
\end{pmatrix} =
\begin{pmatrix}
nx\\
ny\\
\text{unknown}\\
z
\end{pmatrix}
\]
第一项要得到 \(nx\),所以第一行是:
\[
(n,0,0,0)
\]
第二项要得到 \(ny\),第二行是:
\[
(0,n,0,0)
\]
第四项要得到 \(z\),第四行是:
\[
(0,0,1,0)
\]
于是先得到:

\[
M_{persp\to ortho}=
\begin{pmatrix}
n&0&0&0\\
0&n&0&0\\
?&?&?&?\\
0&0&1&0
\end{pmatrix}
\]
这里只剩第三行还没有确定。
求第三行

前面已经确定了挤压矩阵的第一、第二和第四行:
\[
M_{persp\to ortho}=
\begin{pmatrix}
n&0&0&0\\
0&n&0&0\\
?&?&?&?\\
0&0&1&0
\end{pmatrix}
\]
现在只剩第三行,也就是深度怎么变。
深度只和原来的 \(z\) 有关。如果两个点的 \(z\) 一样,只是 \(x、y\) 不同,它们投影后的深度也应该一样。
因此第三行可以写成:
\[
(0,0,A,B)
\]
矩阵暂时变成:
\[
M_{persp\to ortho}=
\begin{pmatrix}
n&0&0&0\\
0&n&0&0\\
0&0&A&B\\
0&0&1&0
\end{pmatrix}
\]
拿一个普通的点:
\[
P=
\begin{pmatrix}
x\\
y\\
z\\
1
\end{pmatrix}
\]
真正乘一次:
\[
M_{persp\to ortho}P =
\begin{pmatrix}
n&0&0&0\\
0&n&0&0\\
0&0&A&B\\
0&0&1&0
\end{pmatrix}
\begin{pmatrix}
x\\
y\\
z\\
1
\end{pmatrix} =
\begin{pmatrix}
nx\\
ny\\
Az+B\\
z
\end{pmatrix}
\]
所以矩阵乘完以后:
第三项:Az + B
第四项:z
注意这时还是齐次坐标。
第三项 \(Az+B\) 还不能直接当成最终的深度,因为第四项已经变成:
\[
w^{\prime}=z
\]
后面还会进行透视除法,也就是整个向量除以 \(w^{\prime}\):
\[
\begin{pmatrix}
nx\\
ny\\
Az+B\\
z
\end{pmatrix}
\longrightarrow
\begin{pmatrix}
\frac{nx}{z}\\
\frac{ny}{z}\\
\frac{Az+B}{z}\\
1
\end{pmatrix}
\]
所以真正的深度是:
\[
z^{\prime}=\frac{Az+B}{z}
\]
现在第三行只剩 \(A、B\) 两个未知数,可以用近平面和远平面两个条件求出来。

先看近平面。
近平面上的点满足:
\[
z=n
\]
挤压以后,近平面仍然要留在原来的深度 \(n\),所以要求:
\[
z^{\prime}=n
\]
把 \(z=n\) 代入刚才的式子:
\[
z^{\prime} =
\frac{An+B}{n}
\]
又因为希望最后结果就是 \(n\):
\[
\frac{An+B}{n}=n
\]
两边乘以 \(n\):
\[
An+B=n^2
\]
为什么右边会变成 \(n^2\)?
因为 \(An+B\) 是透视除法之前的第三项,后面还要除以第四项 \(w^{\prime}=n\)。
想让最终结果是 \(n\),除法前第三项就需要是 \(n^2\):
\[
\frac{n^2}{n}=n
\]
所以近平面给出的条件就是:
\[
An+B=n^2
\]
远平面也是同样的过程。

远平面满足:
\[
z=f
\]
挤压以后,远平面的深度仍然希望保持为:
\[
z^{\prime}=f
\]
把 \(z=f\) 代入:
\[
z^{\prime} =
\frac{Af+B}{f}
\]
要求它等于 \(f\):
\[
\frac{Af+B}{f}=f
\]
两边乘以 \(f\):
\[
Af+B=f^2
\]
原因和近平面一样。
此时第四项:
\[
w^{\prime}=f
\]
所以除法前的第三项需要先变成 \(f^2\),这样透视除法以后才会得到:
\[
\frac{f^2}{f}=f
\]
现在有两个方程:
\[
An+B=n^2
\]
\[
Af+B=f^2
\]
两式相减:
\[
A(n-f)=n^2-f^2
\]
右边使用平方差:
\[
n^2-f^2=(n-f)(n+f)
\]
所以:
\[
A(n-f)=(n-f)(n+f)
\]
由于近平面和远平面不同,\(n\neq f\),约掉 \(n-f\):
\[
A=n+f
\]
再代回:
\[
An+B=n^2
\]
得到:
\[
(n+f)n+B=n^2
\]
展开:
\[
n^2+nf+B=n^2
\]
因此:
\[
B=-nf
\]
第三行就确定了:
\[
(0,0,n+f,-nf)
\]
最后得到完整的挤压矩阵:
\[
M_{persp\to ortho}=
\begin{pmatrix}
n&0&0&0\\
0&n&0&0\\
0&0&n+f&-nf\\
0&0&1&0
\end{pmatrix}
\]
这个矩阵会把透视视锥挤压成长方体,同时让近平面仍然对应 \(n\),远平面仍然对应 \(f\)。
接下来再做正交投影,把这个长方体平移、缩放到 \([-1,1]^3\):
\[
M_{persp} =
M_{ortho}M_{persp\to ortho}
\]
4.4 总结
- 三维变换是二维变换向 $4\times4$ 齐次矩阵的自然推广。
- 三维旋转可以由绕三个坐标轴的旋转组合,也可以用罗德里格斯公式描述绕任意过原点轴的旋转。
- 视图变换利用相对运动,把相机统一变换到原点、观察 -Z、Y 轴向上的标准位置。
- 正交投影把指定长方体平移、缩放到标准立方体 $[-1,1]^3$。
- 透视投影先把视锥体挤压成长方体,再做正交投影;近大远小来自 $x^{\prime}=nx/z$、$y^{\prime}=ny/z$。
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com