0 说明
以下所有内容均为课程 GAMES101 的笔记,建议结合视频和 PPT 使用
1 Model Transformation
这一步就是把物体顶点的坐标从模型坐标系转化成世界坐标,通俗地讲,就是拍照前要把物体摆放到合适的位置。例如在模型坐标中, ( ± 0.5 , ± 0.5 , ± 0.5 ) (\pm 0.5,\pm0.5,\pm0.5) (±0.5,±0.5,±0.5) 是一个立方体的八个顶点坐标。但是这个立方体可以被我们通过平移、旋转、缩放,以各种姿势出现在世界坐标系中的任意位置,而这一步是通过三维仿射变换实现的。
假设有齐次坐标下的顶点 V V V,依次用仿射变换矩阵 P 0 , P 1 , . . . P n P_0,P_1,...P_n P0,P1,...Pn 去左乘 V V V,那么 Model 变换矩阵 M m o d e l = P n P n − 1 ⋯ P 0 M_{model}=P_nP_{n-1}\cdots P_0 Mmodel=PnPn−1⋯P0(因为变换矩阵是不断左乘上去的,注意顺序)
2 View / Camera Transformation
现在物体已经摆放完毕,第二步就是确定摄像机的状态,并且从摄像机的视角确定物体的坐标。
假设在左手坐标系下(GAMES101 是右手系,区别不大),我们首先用一个点坐标 e → \overrightarrow{e} e 表示相机的位置(eye position),同时用两个向量来确定相机的旋转:
- g ^ \hat{g} g^,表示相机看向的方向(gaze at)
- t ^ \hat{t} t^,表示相机向上的朝向(top)
同时,通过叉乘可以确定相机坐标系的最后一个基向量 g ^ × t ^ \hat{g}\times \hat{t} g^×t^
我们设定一个理想的(或者说默认的)状态,相机位于坐标原点,并且看向 z 轴正方向,向上朝向 y 轴正方向,这样 g ^ × t ^ \hat{g}\times \hat{t} g^×t^ 就指向 x 轴正方向
对于一般的相机,我们需要找到一个变换矩阵 M v i e w M_{view} Mview 将一个顶点从世界坐标系变换到相机坐标系中,因为相机和顶点的相对位置和旋转是不变的,所以也可以认为 M v i e w M_{view} Mview 是将一般的相机变换到前文所述的理想状态。这个步骤可以拆解成两步:先把相机平移到原点,再把相机旋转成理想状态。
平移矩阵很好写:
T
v
i
e
w
=
[
1
0
0
−
x
e
0
1
0
−
y
e
0
0
1
−
z
e
0
0
0
1
]
\begin{gathered} 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} \quad \end{gathered}
Tview=⎣⎢⎢⎡100001000010−xe−ye−ze1⎦⎥⎥⎤
考虑旋转矩阵的逆矩阵:
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
]
\begin{gathered} R^{-1}_{view}=\begin {bmatrix} 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{bmatrix} \quad \end{gathered}
Rview−1=⎣⎢⎢⎡xg^×t^yg^×t^zg^×t^0xt^yt^zt^0xg^yg^zg^00001⎦⎥⎥⎤
推导过程:
R
v
i
e
w
−
1
R^{-1}_{view}
Rview−1 描述的是世界坐标系到相机坐标系的旋转过程(此时相机已经位于原点)。考虑世界坐标系下 x 轴方向的基向量
a
→
=
[
1
,
0
,
0
,
0
]
T
\overrightarrow{a}=[1,0,0,0]^T
a=[1,0,0,0]T,旋转过后应该等于相机坐标系下的基向量
[
x
g
^
×
t
^
,
y
g
^
×
t
^
,
z
g
^
×
t
^
,
0
]
T
[x_{\hat{g}\times\hat{t}},y_{\hat{g}\times\hat{t}},z_{\hat{g}\times\hat{t}},0]^T
[xg^×t^,yg^×t^,zg^×t^,0]T
也就是 R v i e w − 1 [ 1 , 0 , 0 , 0 ] T = [ x g ^ × t ^ , y g ^ × t ^ , z g ^ × t ^ , 0 ] T R^{-1}_{view}[1,0,0,0]^T=[x_{\hat{g}\times\hat{t}},y_{\hat{g}\times\hat{t}},z_{\hat{g}\times\hat{t}},0]^T Rview−1[1,0,0,0]T=[xg^×t^,yg^×t^,zg^×t^,0]T,代入后可以算出 R v i e w − 1 R^{-1}_{view} Rview−1 的第一列。对于第二列和第三列可以通过 y 轴和 z 轴的旋转结果得出
并且,旋转矩阵是正交矩阵,它的逆矩阵等于它的转置,所以
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
]
\begin{gathered} R_{view}=\begin {bmatrix} 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{bmatrix} \quad \end{gathered}
Rview=⎣⎢⎢⎡xg^×t^xt^xg^0yg^×t^yt^yg^0zg^×t^zt^zg^00001⎦⎥⎥⎤
那么就得到这个阶段的变换矩阵 M v i e w = R v i e w T v i e w M_{view}=R_{view}T_{view} Mview=RviewTview
3 Project Transformation
现在我们已经站在相机的视角看物体了,还差最后的按下快门。投影一般有两种方法:正交投影和透视投影,区别在于透视投影下的物体近大远小(所以鸽子为什么那么大?)。我们这一步的目的是把视野中的所有顶点摆放到 [ − 1 , 1 ] 3 [-1,1]^3 [−1,1]3 的标准立方体中,方便渲染管线的后续操作。
先推导较为简单的正交投影,透视投影可以转化为正交投影。通过上述相机的参数可以确定视锥体(在正交投影下是一个长方体)在坐标系中的范围,定义在 x 轴上范围是
[
l
,
r
]
[l,r]
[l,r],在 y 轴上范围是
[
b
,
t
]
[b,t]
[b,t],在 z 轴上范围是
[
n
,
f
]
[n,f]
[n,f]。我们要把这个长方体变换成标准立方体,需要先把长方体中心平移到原点,然后在 x,y,z 三个方向上进行放缩即可。于是有:
T
o
r
t
h
o
=
[
1
0
0
−
l
+
r
2
0
1
0
−
b
+
t
2
0
0
1
−
n
+
f
2
0
0
0
1
]
S
o
r
t
h
o
=
[
2
r
−
l
0
0
0
0
2
t
−
b
0
0
0
0
2
f
−
n
0
0
0
0
1
]
M
o
r
t
h
o
=
S
o
r
t
h
o
T
o
r
t
h
o
\begin{gathered} T_{ortho}=\begin {bmatrix} 1&0&0&-\frac{l+r}{2} \\ 0&1&0&-\frac{b+t}{2} \\ 0&0&1&-\frac{n+f}{2} \\ 0&0&0&1 \\\end{bmatrix} \quad \end{gathered} \begin{gathered} S_{ortho}=\begin {bmatrix} \frac{2}{r-l}&0&0&0 \\ 0&\frac{2}{t-b}&0&0 \\ 0&0&\frac{2}{f-n}&0 \\ 0&0&0&1 \\\end{bmatrix} \quad \end{gathered}\\ M_{ortho}=S_{ortho}T_{ortho}
Tortho=⎣⎢⎢⎡100001000010−2l+r−2b+t−2n+f1⎦⎥⎥⎤Sortho=⎣⎢⎢⎡r−l20000t−b20000f−n200001⎦⎥⎥⎤Mortho=SorthoTortho
接下来进行透视投影到正交投影的变换,就是把透视投影的视锥体“压缩”成正交变换的长方体。过程中
n
n
n 和
f
f
f 不变,最终长方体的宽和高等于近平面的宽和高。为了得到(只是猜出这个矩阵长什么样,不涉及证明)这个变换矩阵
M
p
e
r
s
p
→
o
r
t
h
o
M_{persp\rightarrow ortho}
Mpersp→ortho,考虑远平面上沿的任意点
[
x
,
y
,
z
,
1
]
T
[x,y,z,1]^T
[x,y,z,1]T,假设其压缩后的坐标是
[
x
′
,
y
′
,
z
′
,
1
]
T
[x',y',z',1]^T
[x′,y′,z′,1]T,根据我们压缩的规则,最终远平面上沿的高度等于近平面上沿的高度,根据相似三角形的关系,z 轴分量上的比例是
n
z
\frac{n}{z}
zn,那么有
x
′
=
n
x
z
x'=\frac{nx}{z}
x′=znx,
y
′
=
n
y
z
y'=\frac{ny}{z}
y′=zny,压缩后的坐标可以写成
[
n
x
z
,
n
y
z
,
?
,
1
]
T
[\frac{nx}{z},\frac{ny}{z},?,1]^T
[znx,zny,?,1]T(暂时不管第三个分量),对齐次坐标乘以实数不改变其位置,所以压缩后的坐标可以写成
[
n
x
,
n
y
,
?
,
z
]
T
[nx,ny,?,z]^T
[nx,ny,?,z]T。于是根据
M
p
e
r
s
p
→
o
r
t
h
o
[
x
,
y
,
z
,
1
]
T
=
[
n
x
,
n
y
,
?
,
z
]
T
M_{persp\rightarrow ortho}[x,y,z,1]^T=[nx,ny,?,z]^T
Mpersp→ortho[x,y,z,1]T=[nx,ny,?,z]T 可以得到以下信息:
M
p
e
r
s
p
→
o
r
t
h
o
=
[
n
0
0
0
0
n
0
0
?
?
?
?
0
0
1
0
]
\begin{gathered} M_{persp\rightarrow ortho}=\begin {bmatrix} n&0&0&0 \\ 0&n&0&0 \\ ?&?&?&? \\ 0&0&1&0 \\\end{bmatrix} \quad \end{gathered}
Mpersp→ortho=⎣⎢⎢⎡n0?00n?000?100?0⎦⎥⎥⎤
并且,近平面上的任意点
[
x
,
y
,
n
,
1
]
T
[x,y,n,1]^T
[x,y,n,1]T 在压缩后坐标不应该发生变化,有:
M
p
e
r
s
p
→
o
r
t
h
o
[
x
,
y
,
n
,
1
]
T
=
[
n
x
,
n
y
,
n
2
,
n
]
T
M_{persp\rightarrow ortho}[x,y,n,1]^T=[nx,ny,n^2,n]^T
Mpersp→ortho[x,y,n,1]T=[nx,ny,n2,n]T,计算后可以得到以下结果。我们只剩下第三行的两个未知数
A
A
A 和
B
B
B 没有确定了:
M
p
e
r
s
p
→
o
r
t
h
o
=
[
n
0
0
0
0
n
0
0
0
0
A
B
0
0
1
0
]
\begin{gathered} M_{persp\rightarrow ortho}=\begin {bmatrix} n&0&0&0 \\ 0&n&0&0 \\ 0&0&A&B \\ 0&0&1&0 \\\end{bmatrix} \quad \end{gathered}
Mpersp→ortho=⎣⎢⎢⎡n0000n0000A100B0⎦⎥⎥⎤
如果只考虑第三行,那么根据
M
p
e
r
s
p
→
o
r
t
h
o
[
x
,
y
,
n
,
1
]
T
=
[
n
x
,
n
y
,
n
2
,
n
]
T
M_{persp\rightarrow ortho}[x,y,n,1]^T=[nx,ny,n^2,n]^T
Mpersp→ortho[x,y,n,1]T=[nx,ny,n2,n]T 还可以得到
[
0
,
0
,
A
,
B
]
⋅
[
x
,
y
,
n
,
1
]
T
=
n
2
[0,0,A,B]\cdot [x,y,n,1]^T=n^2
[0,0,A,B]⋅[x,y,n,1]T=n2。远平面上任意点
[
x
,
y
,
f
,
1
]
[x,y,f,1]
[x,y,f,1] 压缩后的 z 分量也不会变化,有
[
0
,
0
,
A
,
B
]
⋅
[
x
,
y
,
f
,
1
]
T
=
f
2
[0,0,A,B]\cdot [x,y,f,1]^T=f^2
[0,0,A,B]⋅[x,y,f,1]T=f2,联立上述两个式子可以解出:
{
A
=
n
+
f
B
=
−
n
f
M
p
e
r
s
p
→
o
r
t
h
o
=
[
n
0
0
0
0
n
0
0
0
0
n
+
f
−
n
f
0
0
1
0
]
M
p
r
o
j
e
c
t
=
M
o
r
t
h
o
M
p
e
r
s
p
→
o
r
t
h
o
\begin{cases} A=n+f\\ B=-nf \end{cases}\\ \\[10pt] \begin{gathered} M_{persp\rightarrow ortho}=\begin {bmatrix} n&0&0&0 \\ 0&n&0&0 \\ 0&0&n+f&-nf \\ 0&0&1&0 \\\end{bmatrix} \\[10pt] \quad\\ M_{project}=M_{ortho}M_{persp\rightarrow ortho} \end{gathered}
{A=n+fB=−nfMpersp→ortho=⎣⎢⎢⎡n0000n0000n+f100−nf0⎦⎥⎥⎤Mproject=MorthoMpersp→ortho
此外,补充一点,在实际写软渲染器的时候,透视投影需要先确定相机的几个关键参数:
- n e a r near near 和 f a r far far:决定了相机在 z 轴上的可视距离。因为我们是左手系,相机看向 z 轴正方向,所以 n e a r near near 和 f a r far far 都是正的并且 n e a r < f a r near<far near<far
- f o v y fov_y fovy:在 y 轴上的视场角,决定了在 y 轴方向上的可视角度
- r a t i o ratio ratio:近平面上的宽高比
通过上述参数可以确定压缩后长方体的各项参数:显然, n = n e a r n=near n=near, f = f a r f=far f=far。根据 y O z yOz yOz 平面上的三角形关系, t = n tan ( 1 2 f o v y ) t=n\tan(\frac{1}{2}fov_y) t=ntan(21fovy), b = − t b=-t b=−t, r = t × r a t i o r=t\times ratio r=t×ratio, l = − r l=-r l=−r