本节说明
GAMES101以及虎书(Fundamentals of Computer Graphics, Fourth Edition )中使用的是列向量,使用的坐标系为右手系。本系列文章遵循课程的习惯,以避免不必要的混乱。
图形学应用中左右手系都很常用,比如OpenGL使用右手系而Direct3D使用左手系,常见游戏引擎如UE和Unity中使用左手系。但是其本质是完全一样的,无非是使用列向量与行向量、矩阵转置、矩阵右乘左乘的差别,左手系的具体论述可参考Further Reading 中的3D Math Primer for Graphics and Game Development 。
Homogeneous Coordinates-齐次坐标
通常我们可以使用二维矩阵表示二维线性变换,但是如平移变换这样的变换无法使用二维矩阵表示为线性形式
[ x ′ y ′ ] = [ 1 0 0 1 ] [ x y ] + [ t x t y ] \begin{bmatrix}
x^{\prime} \\
y^{\prime}
\end{bmatrix}=
\begin{bmatrix}
1 & 0 \\
0 & 1
\end{bmatrix}
\begin{bmatrix}
x \\
y
\end{bmatrix}+
\begin{bmatrix}
t_x \\
t_y
\end{bmatrix}
[ x ′ y ′ ] = [ 1 0 0 1 ] [ x y ] + [ t x t y ]
因此我们引入齐次坐标,在二维点或向量中多加入一个维度w w w (点的第三维为1,向量的第三维为0),使其用齐次坐标表示,如下所示
2 D p o i n t = ( x , y , 1 ) T 2 D v e c t o r = ( x , y , 0 ) T 2D\,point=(x,y,\textcolor{Orange}{1})^T \\
2D\,vector=(x,y,\textcolor{Orange}{0})^T
2 D p o i n t = ( x , y , 1 ) T 2 D v e c t o r = ( x , y , 0 ) T
此时,点平移可表示为
[ x ′ y ′ w ′ ] = [ 1 0 t x 0 1 t y 0 0 1 ] [ x y 1 ] = [ x + t x y + t y 1 ] \begin{bmatrix}
x^{\prime} \\
y^{\prime} \\
w^{\prime}
\end{bmatrix}=
\begin{bmatrix}
1 & 0 & t_x \\
0 & 1 & t_y \\
0 & 0 & 1
\end{bmatrix}
\begin{bmatrix}
x \\
y \\
1
\end{bmatrix}=
\begin{bmatrix}
x + t_x\\
y + t_y\\
1
\end{bmatrix}
⎣ ⎢ ⎡ x ′ y ′ w ′ ⎦ ⎥ ⎤ = ⎣ ⎢ ⎡ 1 0 0 0 1 0 t x t y 1 ⎦ ⎥ ⎤ ⎣ ⎢ ⎡ x y 1 ⎦ ⎥ ⎤ = ⎣ ⎢ ⎡ x + t x y + t y 1 ⎦ ⎥ ⎤
此时,向量平移可表示为
[ x ′ y ′ w ′ ] = [ 1 0 t x 0 1 t y 0 0 1 ] [ x y 0 ] = [ x y 0 ] \begin{bmatrix}
x^{\prime} \\
y^{\prime} \\
w^{\prime}
\end{bmatrix}=
\begin{bmatrix}
1 & 0 & t_x \\
0 & 1 & t_y \\
0 & 0 & 1
\end{bmatrix}
\begin{bmatrix}
x \\
y \\
0
\end{bmatrix}=
\begin{bmatrix}
x \\
y \\
0
\end{bmatrix}
⎣ ⎢ ⎡ x ′ y ′ w ′ ⎦ ⎥ ⎤ = ⎣ ⎢ ⎡ 1 0 0 0 1 0 t x t y 1 ⎦ ⎥ ⎤ ⎣ ⎢ ⎡ x y 0 ⎦ ⎥ ⎤ = ⎣ ⎢ ⎡ x y 0 ⎦ ⎥ ⎤
Scaling-缩放
缩放矩阵
S ( s x , s y ) = [ s x 0 0 0 s y 0 0 0 1 ] S(s_x,s_y)=
\begin{bmatrix}
s_x & 0 & 0 \\
0 & s_y & 0 \\
0 & 0 & 1
\end{bmatrix}
S ( s x , s y ) = ⎣ ⎢ ⎡ s x 0 0 0 s y 0 0 0 1 ⎦ ⎥ ⎤
缩放变换
[ x ′ y ′ w ′ ] = [ s x 0 0 0 s y 0 0 0 1 ] [ x y 1 ] = [ s x x s y y 1 ] \begin{bmatrix}
x^{\prime} \\
y^{\prime} \\
w^{\prime}
\end{bmatrix}=
\begin{bmatrix}
s_x & 0 & 0 \\
0 & s_y & 0 \\
0 & 0 & 1
\end{bmatrix}
\begin{bmatrix}
x \\
y \\
1
\end{bmatrix}=
\begin{bmatrix}
s_xx \\
s_yy \\
1
\end{bmatrix}
⎣ ⎢ ⎡ x ′ y ′ w ′ ⎦ ⎥ ⎤ = ⎣ ⎢ ⎡ s x 0 0 0 s y 0 0 0 1 ⎦ ⎥ ⎤ ⎣ ⎢ ⎡ x y 1 ⎦ ⎥ ⎤ = ⎣ ⎢ ⎡ s x x s y y 1 ⎦ ⎥ ⎤
Shearing-切变
切变矩阵
S h e a r x ( s ) = [ 1 s 0 0 1 0 0 0 1 ] , S h e a r y ( s ) = [ 1 0 0 s 1 0 0 0 1 ] Shear_x(s)=
\begin{bmatrix}
1 & s & 0 \\
0 & 1 & 0 \\
0 & 0 & 1
\end{bmatrix},
Shear_y(s)=\begin{bmatrix}
1 & 0 & 0 \\
s & 1 & 0 \\
0 & 0 & 1
\end{bmatrix}
S h e a r x ( s ) = ⎣ ⎢ ⎡ 1 0 0 s 1 0 0 0 1 ⎦ ⎥ ⎤ , S h e a r y ( s ) = ⎣ ⎢ ⎡ 1 s 0 0 1 0 0 0 1 ⎦ ⎥ ⎤
切变变换
[ x ′ y ′ w ′ ] = [ 1 s 0 0 1 0 0 0 1 ] [ x y 1 ] = [ x + s y y 1 ] \begin{bmatrix}
x^{\prime} \\
y^{\prime} \\
w^{\prime}
\end{bmatrix}=
\begin{bmatrix}
1 & s & 0 \\
0 & 1 & 0 \\
0 & 0 & 1
\end{bmatrix}
\begin{bmatrix}
x \\
y \\
1
\end{bmatrix}=
\begin{bmatrix}
x+sy \\
y \\
1
\end{bmatrix}
⎣ ⎢ ⎡ x ′ y ′ w ′ ⎦ ⎥ ⎤ = ⎣ ⎢ ⎡ 1 0 0 s 1 0 0 0 1 ⎦ ⎥ ⎤ ⎣ ⎢ ⎡ x y 1 ⎦ ⎥ ⎤ = ⎣ ⎢ ⎡ x + s y y 1 ⎦ ⎥ ⎤
[ x ′ y ′ w ′ ] = [ 1 0 0 s 1 0 0 0 1 ] [ x y 1 ] = [ x s x + y 1 ] \begin{bmatrix}
x^{\prime} \\
y^{\prime} \\
w^{\prime}
\end{bmatrix}=
\begin{bmatrix}
1 & 0 & 0 \\
s & 1 & 0 \\
0 & 0 & 1
\end{bmatrix}
\begin{bmatrix}
x \\
y \\
1
\end{bmatrix}=
\begin{bmatrix}
x \\
sx+y \\
1
\end{bmatrix}
⎣ ⎢ ⎡ x ′ y ′ w ′ ⎦ ⎥ ⎤ = ⎣ ⎢ ⎡ 1 s 0 0 1 0 0 0 1 ⎦ ⎥ ⎤ ⎣ ⎢ ⎡ x y 1 ⎦ ⎥ ⎤ = ⎣ ⎢ ⎡ x s x + y 1 ⎦ ⎥ ⎤
Rotation-旋转
旋转矩阵
R ( α ) = [ cos α − sin α 0 sin α cos α 0 0 0 1 ] R(\alpha)=
\begin{bmatrix}
\cos\alpha & -\sin\alpha & 0 \\
\sin\alpha & \cos\alpha & 0 \\
0 & 0 & 1
\end{bmatrix}
R ( α ) = ⎣ ⎢ ⎡ cos α sin α 0 − sin α cos α 0 0 0 1 ⎦ ⎥ ⎤
旋转变换
[ x ′ y ′ w ′ ] = [ cos α − sin α 0 sin α cos α 0 0 0 1 ] [ x y 1 ] = [ x cos α − y sin α x sin α + y cos α 1 ] \begin{bmatrix}
x^{\prime} \\
y^{\prime} \\
w^{\prime}
\end{bmatrix}=
\begin{bmatrix}
\cos\alpha & -\sin\alpha & 0 \\
\sin\alpha & \cos\alpha & 0 \\
0 & 0 & 1
\end{bmatrix}
\begin{bmatrix}
x \\
y \\
1
\end{bmatrix}=
\begin{bmatrix}
x\cos\alpha-y\sin\alpha \\
x\sin\alpha+y\cos\alpha \\
1
\end{bmatrix}
⎣ ⎢ ⎡ x ′ y ′ w ′ ⎦ ⎥ ⎤ = ⎣ ⎢ ⎡ cos α sin α 0 − sin α cos α 0 0 0 1 ⎦ ⎥ ⎤ ⎣ ⎢ ⎡ x y 1 ⎦ ⎥ ⎤ = ⎣ ⎢ ⎡ x cos α − y sin α x sin α + y cos α 1 ⎦ ⎥ ⎤
Reflection-对称
对称变换矩阵
R e f y = [ − 1 0 0 0 1 0 0 0 1 ] , R e f x = [ 1 0 0 0 − 1 0 0 0 1 ] Ref_y=
\begin{bmatrix}
-1 & 0 & 0 \\
0 & 1 & 0 \\
0 & 0 & 1
\end{bmatrix},
Ref_x=
\begin{bmatrix}
1 & 0 & 0 \\
0 & -1 & 0 \\
0 & 0 & 1
\end{bmatrix}
R e f y = ⎣ ⎢ ⎡ − 1 0 0 0 1 0 0 0 1 ⎦ ⎥ ⎤ , R e f x = ⎣ ⎢ ⎡ 1 0 0 0 − 1 0 0 0 1 ⎦ ⎥ ⎤
对称变换
[ x ′ y ′ w ′ ] = [ − 1 0 0 0 1 0 0 0 1 ] [ x y 1 ] = [ − x y 1 ] \begin{bmatrix}
x^{\prime} \\
y^{\prime} \\
w^{\prime}
\end{bmatrix}=
\begin{bmatrix}
-1 & 0 & 0 \\
0 & 1 & 0 \\
0 & 0 & 1
\end{bmatrix}
\begin{bmatrix}
x \\
y \\
1
\end{bmatrix}=
\begin{bmatrix}
-x \\
y \\
1
\end{bmatrix}
⎣ ⎢ ⎡ x ′ y ′ w ′ ⎦ ⎥ ⎤ = ⎣ ⎢ ⎡ − 1 0 0 0 1 0 0 0 1 ⎦ ⎥ ⎤ ⎣ ⎢ ⎡ x y 1 ⎦ ⎥ ⎤ = ⎣ ⎢ ⎡ − x y 1 ⎦ ⎥ ⎤
[ x ′ y ′ w ′ ] = [ 1 0 0 0 − 1 0 0 0 1 ] [ x y 1 ] = [ x − y 1 ] \begin{bmatrix}
x^{\prime} \\
y^{\prime} \\
w^{\prime}
\end{bmatrix}=
\begin{bmatrix}
1 & 0 & 0 \\
0 & -1 & 0 \\
0 & 0 & 1
\end{bmatrix}
\begin{bmatrix}
x \\
y \\
1
\end{bmatrix}=
\begin{bmatrix}
x \\
-y \\
1
\end{bmatrix}
⎣ ⎢ ⎡ x ′ y ′ w ′ ⎦ ⎥ ⎤ = ⎣ ⎢ ⎡ 1 0 0 0 − 1 0 0 0 1 ⎦ ⎥ ⎤ ⎣ ⎢ ⎡ x y 1 ⎦ ⎥ ⎤ = ⎣ ⎢ ⎡ x − y 1 ⎦ ⎥ ⎤
Translation-平移
平移变换矩阵
T ( t x , t y ) = [ 1 0 t x 0 1 t y 0 0 1 ] T(t_x,t_y)=
\begin{bmatrix}
1 & 0 & t_x \\
0 & 1 & t_y \\
0 & 0 & 1
\end{bmatrix}
T ( t x , t y ) = ⎣ ⎢ ⎡ 1 0 0 0 1 0 t x t y 1 ⎦ ⎥ ⎤
平移变换
[ x ′ y ′ w ′ ] = [ 1 0 t x 0 1 t y 0 0 1 ] [ x y 1 ] = [ x + t x y + t y 1 ] \begin{bmatrix}
x^{\prime} \\
y^{\prime} \\
w^{\prime}
\end{bmatrix}=
\begin{bmatrix}
1 & 0 & t_x \\
0 & 1 & t_y \\
0 & 0 & 1
\end{bmatrix}
\begin{bmatrix}
x \\
y \\
1
\end{bmatrix}=
\begin{bmatrix}
x+t_x \\
y+t_y \\
1
\end{bmatrix}
⎣ ⎢ ⎡ x ′ y ′ w ′ ⎦ ⎥ ⎤ = ⎣ ⎢ ⎡ 1 0 0 0 1 0 t x t y 1 ⎦ ⎥ ⎤ ⎣ ⎢ ⎡ x y 1 ⎦ ⎥ ⎤ = ⎣ ⎢ ⎡ x + t x y + t y 1 ⎦ ⎥ ⎤
Affine-仿射
仿射变换为线性变换加平移变换。
仿射变换形如
[ x ′ y ′ w ′ ] = [ a b t x c d t y 0 0 1 ] [ x y 1 ] \begin{bmatrix}
x^{\prime} \\
y^{\prime} \\
w^{\prime}
\end{bmatrix}=
\begin{bmatrix}
a & b & t_x \\
c & d & t_y \\
0 & 0 & 1
\end{bmatrix}
\begin{bmatrix}
x \\
y \\
1
\end{bmatrix}
⎣ ⎢ ⎡ x ′ y ′ w ′ ⎦ ⎥ ⎤ = ⎣ ⎢ ⎡ a c 0 b d 0 t x t y 1 ⎦ ⎥ ⎤ ⎣ ⎢ ⎡ x y 1 ⎦ ⎥ ⎤
逆变换就是乘以原变换的逆矩阵
M = M 1 M 2 ⋯ M n M − 1 = M n − 1 ⋯ M 2 − 1 M 1 − 1 M=M_1M_2\cdots M_n\\
M^{-1}=M_n^{-1}\cdots M_2^{-1}M_1^{-1}
M = M 1 M 2 ⋯ M n M − 1 = M n − 1 ⋯ M 2 − 1 M 1 − 1
特别地,对于旋转矩阵R θ R_\theta R θ ,由于其正交性,其逆变换R θ − 1 R_\theta^{-1} R θ − 1 (即旋转− θ -\theta − θ 的变换R − θ R_{-\theta} R − θ )等于其转置矩阵
R − θ = R θ − 1 = R θ T R_{-\theta}=R_\theta^{-1}=R_\theta^T
R − θ = R θ − 1 = R θ T
上述各基本线性变换可组合变成复合变换。相反,复合变换也可进行变换分解,变成几种基本变换类型的组合。
变换组合
即便变换矩阵相同,执行变换顺序改变也可能带来不同结果,如下所示
tip :矩阵没有交换律,但是有结合律。
变换分解
T ( c ) ⋅ R ( α ) ⋅ T ( − c ) T(c){\cdot}R(\alpha){\cdot}T(-c)
T ( c ) ⋅ R ( α ) ⋅ T ( − c )
与二维变换类似,使用齐次坐标表示点与向量
3 D p o i n t = ( x , y , z , 1 ) T 3 D v e c t o r = ( x , y , z , 0 ) T 3D\,point=(x,y,z,\textcolor{Orange}{1})^T \\
3D\,vector=(x,y,z,\textcolor{Orange}{0})^T
3 D p o i n t = ( x , y , z , 1 ) T 3 D v e c t o r = ( x , y , z , 0 ) T
三维缩放
S ( s x , s y , s z ) = [ s x 0 0 0 0 s y 0 0 0 0 s z 0 0 0 0 1 ] S(s_x,s_y,s_z)=
\begin{bmatrix}
s_x & 0 & 0 & 0 \\
0 & s_y & 0 & 0 \\
0 & 0 & s_z & 0 \\
0 & 0 & 0 & 1
\end{bmatrix}
S ( s x , s y , s z ) = ⎣ ⎢ ⎢ ⎢ ⎡ s x 0 0 0 0 s y 0 0 0 0 s z 0 0 0 0 1 ⎦ ⎥ ⎥ ⎥ ⎤
三维平移
T ( t x , t y , t z ) = [ 1 0 0 t x 0 1 0 t y 0 0 1 t z 0 0 0 1 ] T(t_x,t_y,t_z)=
\begin{bmatrix}
1 & 0 & 0 & t_x \\
0 & 1 & 0 & t_y \\
0 & 0 & 1 & t_z \\
0 & 0 & 0 & 1
\end{bmatrix}
T ( t x , t y , t z ) = ⎣ ⎢ ⎢ ⎢ ⎡ 1 0 0 0 0 1 0 0 0 0 1 0 t x t y t z 1 ⎦ ⎥ ⎥ ⎥ ⎤
三维旋转
R x ( α ) = [ 1 0 0 0 0 cos α − sin α 0 0 sin α cos α 0 0 0 0 1 ] R_x(\alpha)=
\begin{bmatrix}
1 & 0 & 0 & 0 \\
0 & \cos\alpha & -\sin\alpha & 0 \\
0 & \sin\alpha & \cos\alpha & 0 \\
0 & 0 & 0 & 1
\end{bmatrix}
R x ( α ) = ⎣ ⎢ ⎢ ⎢ ⎡ 1 0 0 0 0 cos α sin α 0 0 − sin α cos α 0 0 0 0 1 ⎦ ⎥ ⎥ ⎥ ⎤
R y ( α ) = [ cos α 0 sin α 0 0 1 0 0 − sin α 0 cos α 0 0 0 0 1 ] R_y(\alpha)=
\begin{bmatrix}
\cos\alpha & 0 & \sin\alpha & 0 \\
0 & 1& 0 & 0 \\
-\sin\alpha & 0 & \cos\alpha & 0 \\
0 & 0 & 0 & 1
\end{bmatrix}
R y ( α ) = ⎣ ⎢ ⎢ ⎢ ⎡ cos α 0 − sin α 0 0 1 0 0 sin α 0 cos α 0 0 0 0 1 ⎦ ⎥ ⎥ ⎥ ⎤
R z ( α ) = [ cos α − sin α 0 0 sin α cos α 0 0 0 0 1 0 0 0 0 1 ] R_z(\alpha)=
\begin{bmatrix}
\cos\alpha & -\sin\alpha & 0 & 0 \\
\sin\alpha & \cos\alpha & 0 & 0 \\
0 & 0 & 1 & 0 \\
0 & 0 & 0 & 1
\end{bmatrix}
R z ( α ) = ⎣ ⎢ ⎢ ⎢ ⎡ cos α sin α 0 0 − sin α cos α 0 0 0 0 1 0 0 0 0 1 ⎦ ⎥ ⎥ ⎥ ⎤
所有三维旋转都可以分解成绕x , y , z x,y,z x , y , z 轴分别旋转的组合,即用欧拉角表示。
绕任意轴的三维旋转可以用R o d r i g u e s ′ R o t a t i o n F o r m u l a Rodrigues' Rotation Formula R o d r i g u e s ′ R o t a t i o n F o r m u l a (罗德里格斯旋转公式)表示
(公式推导参考:3D Math Primer for Graphics and Game Development, 2nd Edition - Ch5.1.3)
R ( n , α ) = cos ( α ) I + ( 1 − cos ( α ) ) n n T + sin ( α ) [ 0 − n z n y n z 0 − n x − n y n x 0 ] ⏟ N R(\bm{n},\alpha)=\cos(\alpha)\bold{I}+(1-\cos(\alpha))\bm{n}\bm{n^T}+\sin(\alpha)\underbrace{
\begin{bmatrix}
0 & -n_z & n_y \\
n_z & 0 & -n_x \\
-n_y & n_x & 0
\end{bmatrix}
}_{\text{N}}
R ( n , α ) = cos ( α ) I + ( 1 − cos ( α ) ) n n T + sin ( α ) N ⎣ ⎢ ⎡ 0 n z − n y − n z 0 n x n y − n x 0 ⎦ ⎥ ⎤
其中,n n n 为旋转轴(单位向量),α \alpha α 为正方向旋转角度,I I I 为单位矩阵。
公式推导可参考《3D数学基础:图形和游戏开发》第二版的5.1.3节,其中对此有十分具体的推导(该书使用的是左手系)。
四元数在GAMES101中仅由闫老师口头提及。
四元数在旋转的主要作用是可以避免欧拉角万向节死锁问题,也可以很好地表示旋转角度的插值。
关于四元数的具体论述可参考《3D数学基础:图形和游戏开发》第二版的8.5节。
同上节所述模型变换一致,对相机以及物体本身进行缩放、旋转、平移等变换。简而言之就是搭建场景,摆放相机。
我们定义的相机位置与方向、物体位置等都是对于世界坐标系而言的。
为便于计算,在进行透视变换之前,我们先将相机变换至原点,相机上方为+Y方向,看向-Z方向。为了保证相机看到场景与变换前相同,需要将物体/整个场景 做相同变换。
相机并非场景中我们观测的实体,因此我们可以认为相机永远处在上述变换后的原点位置而无需进行计算。于是我们只需根据相机所需要的变换,对场景物体进行相同操作,便可得到场景物体最终的位置。
换言之,视图变换实际上就是把原来用世界坐标系描述位置的场景变换为使用相机坐标系描述。
相机若从初始位置变换到最终位置需要先进行平移,再旋转到标准方向。因此视图变化就是对物体进行这两步变换
M v i e w = R v i e w T v i e w M_{view}=R_{view}T_{view}
M v i e w = R v i e w T v i e w
注意,对于列向量是矩阵左乘,先平移、后旋转。
平移变换矩阵计算十分容易,即使用坐标的相反数作为平移参数,如下所示
T v i e w = [ 1 0 0 − x e 0 1 0 − y e 0 0 1 − z e 0 0 0 1 ] T_{view}=
\begin{bmatrix}
1 & 0 & 0 & -x_e \\
0 & 1 & 0 & -y_e \\
0 & 0 & 1 & -z_e \\
0 & 0 & 0 & 1
\end{bmatrix}
T v i e w = ⎣ ⎢ ⎢ ⎢ ⎡ 1 0 0 0 0 1 0 0 0 0 1 0 − x e − y e − z e 1 ⎦ ⎥ ⎥ ⎥ ⎤
旋转变换到坐标轴方向比较难表示,但是将坐标轴方向旋转到初始方向很容易,因此使用逆变换的方法,如下
R v i e w − 1 = [ x g ^ × t ^ x t x − g 0 y g ^ × t ^ y t y − g 0 z g ^ × t ^ z t z − g 0 0 0 0 1 ] ⇒ R v i e w = [ x g ^ × t ^ y g ^ × t ^ z g ^ × t ^ 0 x t y t z t 0 x − g y − g z − g 0 0 0 0 1 ] R_{view}^{-1}=
\begin{bmatrix}
x_{\hat{g}\times\hat{t}} & x_t & x_{-g} & 0 \\
y_{\hat{g}\times\hat{t}} & y_t & y_{-g} & 0 \\
z_{\hat{g}\times\hat{t}} & z_t & z_{-g} & 0 \\
0 & 0 & 0 & 1
\end{bmatrix}
\Rightarrow
R_{view}=
\begin{bmatrix}
x_{\hat{g}\times\hat{t}} & y_{\hat{g}\times\hat{t}} & z_{\hat{g}\times\hat{t}} & 0 \\
x_t & y_t & z_t & 0 \\
x_{-g} & y_{-g} & z_{-g} & 0 \\
0 & 0 & 0 & 1
\end{bmatrix}
R v i e w − 1 = ⎣ ⎢ ⎢ ⎢ ⎡ x g ^ × t ^ y g ^ × t ^ z g ^ × t ^ 0 x t y t z t 0 x − g y − g z − g 0 0 0 0 1 ⎦ ⎥ ⎥ ⎥ ⎤ ⇒ R v i e w = ⎣ ⎢ ⎢ ⎢ ⎡ x g ^ × t ^ x t x − g 0 y g ^ × t ^ y t y − g 0 z g ^ × t ^ z t z − g 0 0 0 0 1 ⎦ ⎥ ⎥ ⎥ ⎤
其中,g ^ \hat{g} g ^ 为相机朝向,t ^ \hat{t} t ^ 为相机正上方向。
Orthographic Projection-正交投影
正交投影会将整个空间变换成一个Canonical Cube(标准化坐标空间),这个空间也被称为NDC(Normalized Device Coordinates,归一化设备坐标),这个空间XYZ轴方向上的范围都是[ − 1 , 1 ] [-1,1] [ − 1 , 1 ] 。
如图所示,原始空间范围被定义为[ l , r ] × [ b , t ] × [ f , n ] [l,r]\times[b,t]\times[f,n] [ l , r ] × [ b , t ] × [ f , n ] (左,右,底,顶,远,近),注意此处f < n f<n f < n (相机朝向-Z)。变换后整个空间被压缩/拉伸为范围是[ − 1 , 1 ] 3 [-1,1]^3 [ − 1 , 1 ] 3 的空间。
说明 :
上述NDC中,物体在NDC中的坐标Z值越大离相机越近,因为我们定义的相机朝向为-Z;
OpenGL一般使用右手系,但是在NDC中使用了左手系,所以OpenGL的NDC空间中相机朝向为Z方向;
不同图形API的NDC范围可能有所不同,如DirectX的NDC在Z轴上的范围是[ 0 , 1 ] [0,1] [ 0 , 1 ] 。
上述变换过程使用矩阵表示为
M o r t h o = [ 2 / ( r − l ) 0 0 0 0 2 / ( t − b ) 0 0 0 0 2 / ( n − f ) 0 0 0 0 1 ] [ 1 0 0 − ( r + l ) / 2 0 1 0 − ( t + b ) / 2 0 0 1 − ( n + f ) / 2 0 0 0 1 ] M_{ortho}=
\begin{bmatrix}
\footnotesize{2/(r-l)} & 0 & 0 & 0 \\
0 & \footnotesize{2/(t-b)} & 0 & 0 \\
0 & 0 & \footnotesize{2/(n-f)} & 0 \\
0 & 0 & 0 & 1
\end{bmatrix}
\begin{bmatrix}
1 & 0 & 0 & \footnotesize{-(r+l)/2} \\
0 & 1 & 0 & \footnotesize{-(t+b)/2} \\
0 & 0 & 1 & \footnotesize{-(n+f)/2} \\
0 & 0 & 0 & 1
\end{bmatrix}
M o r t h o = ⎣ ⎢ ⎢ ⎢ ⎡ 2 / ( r − l ) 0 0 0 0 2 / ( t − b ) 0 0 0 0 2 / ( n − f ) 0 0 0 0 1 ⎦ ⎥ ⎥ ⎥ ⎤ ⎣ ⎢ ⎢ ⎢ ⎡ 1 0 0 0 0 1 0 0 0 0 1 0 − ( r + l ) / 2 − ( t + b ) / 2 − ( n + f ) / 2 1 ⎦ ⎥ ⎥ ⎥ ⎤
先将空间中心平移到原点处,然后将其沿坐标轴方向的范围缩放为[ − 1 , 1 ] [-1,1] [ − 1 , 1 ] 。
Perspective Projection-透视投影
透视投影是将视锥体空间挤压成一个长方体空间,再由正交投影变成归一化标准空间。
这个挤压的过程中近裁剪面(Front Clipping Plane)不变,远裁剪面(Back Clipping Plane)位置的Z值不变,即仅在当前平面内进行收缩。
说明 :对于介于两个面之间的点,进行视锥体挤压后,其坐标Z的值会变小,向远处移动
具体做法:
根据简单的相似三角形关系
可得远裁剪面上的点经过挤压后的新的X,Y坐标(Z不变)
y ′ = y ⋅ n / z x ′ = x ⋅ n / z y^{\prime}=y \cdot n/z \qquad x^{\prime}=x \cdot n/z
y ′ = y ⋅ n / z x ′ = x ⋅ n / z
在齐次坐标下表示点的变换,填出变换矩阵,并利用近裁剪面点不变、远裁剪面点Z值不变的性质,解出变换矩阵(具体求解过程见GAMES101 Lecture 4,1:05:00左右)
M p e r s p − > o r t h o = [ n 0 0 0 0 n 0 0 0 0 n + f − f n 0 0 1 0 ] M_{persp->ortho}=
\begin{bmatrix}
n & 0 & 0 & 0 \\
0 & n & 0 & 0 \\
0 & 0 & n+f & -fn \\
0 & 0 & 1 & 0
\end{bmatrix}
M p e r s p − > o r t h o = ⎣ ⎢ ⎢ ⎢ ⎡ n 0 0 0 0 n 0 0 0 0 n + f 1 0 0 − f n 0 ⎦ ⎥ ⎥ ⎥ ⎤
Field-of-View
已知n , f n,f n , f ,由fovY和屏幕宽高比计算正交投影长方体的l , r , b , t l,r,b,t l , r , b , t
也就是说,根据实际显示画面的屏幕宽高比、fov、远近裁剪面距离,我们可以计算出能被我们看到的空间范围,这个空间范围就是上一部分中我们需要进行透视投影变换的视锥体。
视口变换
视口变换简而言之就是将归一化标准空间的标准平面(即透视投影变换后的正方体坐标Z=0的截面)的范围对应到实际显示屏幕的分辨率范围,即
[ − 1 , 1 ] 2 − > [ 0 , W i d t h ] × [ 0 , H e i g h t ] [-1,1]^2->[0,Width]\times[0,Height]
[ − 1 , 1 ] 2 − > [ 0 , W i d t h ] × [ 0 , H e i g h t ]
其中,Width和Height为屏幕分辨率的宽和高。
视口变换先将标准平面拉伸至屏幕分辨率大小,然后再移动到屏幕位置,变换矩阵如下
M v i e w p o r t = [ w i d t h / 2 0 0 w i d t h / 2 0 h e i g h t / 2 0 h e i g h t / 2 0 0 1 0 0 0 0 1 ] M_{viewport}=
\begin{bmatrix}
\footnotesize{width/2} & 0 & 0 & \footnotesize{width/2} \\
0 & \footnotesize{height/2} & 0 & \footnotesize{height/2} \\
0 & 0 & 1 & 0 \\
0 & 0 & 0 & 1
\end{bmatrix}
M v i e w p o r t = ⎣ ⎢ ⎢ ⎢ ⎡ w i d t h / 2 0 0 0 0 h e i g h t / 2 0 0 0 0 1 0 w i d t h / 2 h e i g h t / 2 0 1 ⎦ ⎥ ⎥ ⎥ ⎤
至此,所有变换都执行完毕,我们已经可以由此将一个3D场景变换到屏幕空间了。
但是,不难看出我们在视口变换时实际上忽略了Z方向上的信息,换言之,我们所做的一切并不能让我们分清楚场景中物体的前后,它们只是被全都挤到了屏幕平面上面。
这便是光栅化过程中要解决的问题之一。
Further Reading
Fundamentals of Computer Graphics, 5th Edition , Steve Marschner, Peter Shirley, et.(只有第二版有中译版,其他暂无,不过有大佬在网上发布的个人翻译可参考)。
3D Math Primer for Graphics and Game Development, 2nd Edition , Fletcher Dunn. Ian Parberry. (中译版:3D数学基础:图形和游戏开发(第二版) ,翻译略差,有能力尽量以英文版为主)。