模型变换矩阵
模型变换矩阵可以分解为旋转、缩放和位移矩阵,其目的是将物体变换到世界坐标系的中心,然后以此为基准对物体进行旋转、缩放和位移。
旋转矩阵
按
x
,
y
,
z
x,y,z
x,y,z 轴旋转指定角度,即可等价在任意轴上旋转。按照右手坐标系法则,
x
x
x 和
z
z
z 轴直接叉乘得到的是
−
y
-y
−y 方向,因此
y
y
y 轴旋转矩阵和其他略有不同。注意,欧拉角的旋转会出现万向锁的现象,需要使用四元数来解决这一问题。
R
x
(
α
)
=
(
1
0
0
0
0
cos
α
−
sin
α
0
0
sin
α
cos
α
0
0
0
0
1
)
\mathbf{R}_{x}(\alpha)=\left(\begin{array}{cccc} 1 & 0 & 0 & 0 \\ 0 & \cos \alpha & -\sin \alpha & 0 \\ 0 & \sin \alpha & \cos \alpha & 0 \\ 0 & 0 & 0 & 1 \end{array}\right)
Rx(α)=⎝⎜⎜⎛10000cosαsinα00−sinαcosα00001⎠⎟⎟⎞
R y ( α ) = ( cos α 0 sin α 0 0 1 0 0 − sin α 0 cos α 0 0 0 0 1 ) \mathbf{R}_{y}(\alpha)=\left(\begin{array}{cccc} \cos \alpha & 0 & \sin \alpha & 0 \\ 0 & 1 & 0 & 0 \\ -\sin \alpha & 0 & \cos \alpha & 0 \\ 0 & 0 & 0 & 1 \end{array}\right) Ry(α)=⎝⎜⎜⎛cosα0−sinα00100sinα0cosα00001⎠⎟⎟⎞
R z ( α ) = ( cos α − sin α 0 0 sin α cos α 0 0 0 0 1 0 0 0 0 1 ) \mathbf{R}_{z}(\alpha)=\left(\begin{array}{cccc} \cos \alpha & -\sin \alpha & 0 & 0 \\ \sin \alpha & \cos \alpha & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \end{array}\right) Rz(α)=⎝⎜⎜⎛cosαsinα00−sinαcosα0000100001⎠⎟⎟⎞
缩放矩阵
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 ) \mathbf{S}\left(s_{x}, s_{y}, s_{z}\right)=\left(\begin{array}{cccc} s_{x} & 0 & 0 & 0 \\ 0 & s_{y} & 0 & 0 \\ 0 & 0 & s_{z} & 0 \\ 0 & 0 & 0 & 1 \end{array}\right) S(sx,sy,sz)=⎝⎜⎜⎛sx0000sy0000sz00001⎠⎟⎟⎞
位移矩阵
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 ) \mathbf{T}\left(t_{x}, t_{y}, t_{z}\right)=\left(\begin{array}{cccc} 1 & 0 & 0 & t_{x} \\ 0 & 1 & 0 & t_{y} \\ 0 & 0 & 1 & t_{z} \\ 0 & 0 & 0 & 1 \end{array}\right) T(tx,ty,tz)=⎝⎜⎜⎛100001000010txtytz1⎠⎟⎟⎞
视图变换矩阵
视图变换矩阵又称相机变换矩阵,其目的是将世界坐标系下的物体变换到以相机坐标系下,即观察空间。视图变换矩阵可以分解为位移矩阵和旋转矩阵。
位移矩阵
通过修改摄像机的位置,就可以实现摄像机的移动。而世界空间中的物体变换到观察空间就是相对摄像机位置的反方向移动。
T
view
=
(
1
0
0
−
x
e
0
1
0
−
y
e
0
0
1
−
z
e
0
0
0
1
)
T_{\text {view }}=\left(\begin{array}{cccc} 1 & 0 & 0 & -x_{e} \\ 0 & 1 & 0 & -y_{e} \\ 0 & 0 & 1 & -z_{e} \\ 0 & 0 & 0 & 1 \end{array}\right)
Tview =⎝⎜⎜⎛100001000010−xe−ye−ze1⎠⎟⎟⎞
旋转矩阵
设定摄像机位置为 e e e ,垂直向上的向量为 t t t ,观察方向为 g g g 。并默认为看向 − z - z −z 轴。因此旋转矩阵实际上是将 g g g 方向旋转到 − z -z −z 轴, t t t 方向旋转到 y y y 轴, g × t g\times t g×t 方向旋转到 x x x 轴。
该变换难以得出,但其逆变换很容易得出。而旋转变换是正交变换,其逆矩阵就等于其转置矩阵,因此可以通过逆矩阵反推出旋转矩阵。
R
view
−
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_{\text {view }}^{-1}=\left(\begin{array}{cccc} 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{array}\right)
Rview −1=⎝⎜⎜⎛xg^×t^yg^×t^zg^×t^0xtytzt0x−gy−gz−g00001⎠⎟⎟⎞
R view = ( 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_{\text {view }}=\left(\begin{array}{cccc} x_{\hat{\mathrm{g}} \times \hat{t}} & y_{\hat{\mathrm{g}} \times \hat{t}} & z_{\hat{\mathrm{g}} \times \hat{t}} & 0 \\ x_{t} & y_{t} & z_{t} & 0 \\ x_{-g} & y_{-g} & z_{-g} & 0 \\ 0 & 0 & 0 & 1 \end{array}\right) Rview =⎝⎜⎜⎛xg^×t^xtx−g0yg^×t^yty−g0zg^×t^ztz−g00001⎠⎟⎟⎞
投影变换矩阵
以 OpenGL \text{OpenGL} OpenGL 为基准,在投影变换阶段,需要将观察空间从右手坐标系变换到左手坐标系下的 NDC \text{NDC} NDC 空间,即从 z z z 轴向屏幕外变换到向屏幕内。在世界坐标系下近平面和远平面的 z z z 值是负的,但我们设定最近参数和最远参数 n , f n,f n,f 为正数。取垂直视场角 f o v fov fov 的一半做正切,即最高参数 t t t 与最近参数 n n n 的比值。再通过屏幕宽高比即可得出最右参数 r r r 。
tan f o v 2 = t n r = t × w h \tan{\frac{fov}{2}} = \frac{t}{n} \\ \\ r = t \times \frac{w}{h} tan2fov=ntr=t×hw
正交投影矩阵
View Space \text{View Space} View Space 在对称情形下, r r r 和 l l l , t t t 和 b b b 可以相互抵消,并且实际上一般都是对称的。
M o r t h o = ( 2 r − l 0 0 − r + l r − l 0 2 t − b 0 − t + b t − b 0 0 2 f − n − f + n f − n 0 0 0 1 ) = ( 1 r 0 0 0 0 1 t 0 0 0 0 2 f − n − f + n f − n 0 0 0 1 ) M_{ortho}= \left(\begin{array}{cccc} \frac{2}{r-l} & 0 & 0 & -\frac{r+l}{r-l} \\ 0 & \frac{2}{t-b} & 0 & -\frac{t+b}{t-b} \\ 0 & 0 & \frac{2}{f-n} & -\frac{f+n}{f-n} \\ 0 & 0 & 0 & 1 \end{array}\right)= \left(\begin{array}{cccc} \frac{1}{r} & 0 & 0 & 0 \\ 0 & \frac{1}{t} & 0 & 0 \\ 0 & 0 & \frac{2}{f-n} & -\frac{f+n}{f-n} \\ 0 & 0 & 0 & 1 \end{array}\right) Mortho=⎝⎜⎜⎛r−l20000t−b20000f−n20−r−lr+l−t−bt+b−f−nf+n1⎠⎟⎟⎞=⎝⎜⎜⎛r10000t10000f−n2000−f−nf+n1⎠⎟⎟⎞
位移矩阵
正交投影变换可以分解为位移变换和缩放变换。首先进行位移变换,将 View Space 移动到观察空间的坐标系原点。
T
=
(
1
0
0
−
r
+
l
2
0
1
0
−
t
+
b
2
0
0
1
−
f
+
n
2
0
0
0
1
)
=
(
1
0
0
0
0
1
0
0
0
0
1
−
f
+
n
2
0
0
0
1
)
T = \left(\begin{array}{cccc} 1 & 0 & 0 & -\frac{r+l}{2} \\ 0 & 1 & 0 & -\frac{t+b}{2} \\ 0 & 0 & 1 & -\frac{f+n}{2} \\ 0 & 0 & 0 & 1 \end{array}\right)= \left(\begin{array}{cccc} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 1 & -\frac{f+n}{2} \\ 0 & 0 & 0 & 1 \end{array}\right)
T=⎝⎜⎜⎛100001000010−2r+l−2t+b−2f+n1⎠⎟⎟⎞=⎝⎜⎜⎛10000100001000−2f+n1⎠⎟⎟⎞
缩放矩阵
然后再进行缩放变换,将 View Space 的长宽高变换为
[
−
1
,
1
]
[-1,1]
[−1,1] 的范围,即标准立方体。此时称为齐次裁剪空间。经过齐次裁剪,再对坐标进行透视除法,才变换到 NDC 空间。
S
=
(
2
r
−
l
0
0
0
0
2
t
−
b
0
0
0
0
2
f
−
n
0
0
0
0
1
)
=
(
1
r
0
0
0
0
1
t
0
0
0
0
2
f
−
n
0
0
0
0
1
)
S= \left(\begin{array}{cccc} \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{array}\right)= \left(\begin{array}{cccc} \frac{1}{r} & 0 & 0 & 0 \\ 0 & \frac{1}{t} & 0 & 0 \\ 0 & 0 & \frac{2}{f-n} & 0 \\ 0 & 0 & 0 & 1 \end{array}\right)
S=⎝⎜⎜⎛r−l20000t−b20000f−n200001⎠⎟⎟⎞=⎝⎜⎜⎛r10000t10000f−n200001⎠⎟⎟⎞
透视投影矩阵
经过这一步变换后的点坐标,实际上 z z z 值不再是线性的了。在进行透视除法时,需要保留齐次坐标的 w w w 值,为后续进行深度测试比较的 z z z 值进行透视矫正插值。
透视投影变换需要先将截锥体形状的 View Space 挤压成长方体形状的的 View Space 后,再进行正交投影变换。将该矩阵和正交投影矩阵左乘后即可得到完整的透视投影矩阵。
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_{persp \rightarrow ortho}= \left(\begin{array}{cccc} n & 0 & 0 & 0 \\ 0 & n & 0 & 0 \\ 0 & 0 & -(n+f) & -nf\\ 0 & 0 & 1 & 0 \end{array}\right)
Mpersp→ortho=⎝⎜⎜⎛n0000n0000−(n+f)100−nf0⎠⎟⎟⎞
M p e r s p = ( 2 n r − l 0 − r + l r − l 0 0 2 n t − b − t + b t − b 0 0 0 − f + n f − n − 2 f n f − n 0 0 − 1 0 ) = ( n r 0 0 0 0 n t 0 0 0 0 − f + n f − n − 2 f n f − n 0 0 − 1 0 ) M_{persp} = \left(\begin{array}{cccc} \frac{2 n}{r-l} & 0 & -\frac{r+l}{r-l} & 0 \\ 0 & \frac{2 n}{t-b} & -\frac{t+b}{t-b} & 0 \\ 0 & 0 & -\frac{f+n}{f-n} & -\frac{2 f n}{f-n} \\ 0 & 0 & -1 & 0 \end{array}\right)= \left(\begin{array}{cccc} \frac{n}{r} & 0 & 0 & 0 \\ 0 & \frac{n}{t} & 0 & 0 \\ 0 & 0 & -\frac{f+n}{f-n} & -\frac{2 f n}{f-n} \\ 0 & 0 & -1 & 0 \end{array}\right) Mpersp=⎝⎜⎜⎛r−l2n0000t−b2n00−r−lr+l−t−bt+b−f−nf+n−100−f−n2fn0⎠⎟⎟⎞=⎝⎜⎜⎛rn0000tn0000−f−nf+n−100−f−n2fn0⎠⎟⎟⎞
视口变换矩阵
视口变换的目的是将 NDC 空间的三维坐标变换到屏幕空间的二维坐标。根据图形API的不同,所规定的屏幕空间的坐标系也不同。以 OpenGL 为标准,我们构建将 NDC 空间的左下变换到原点的变换矩阵。齐次坐标的点坐标经过视口变换后截取
x
,
y
x,y
x,y 坐标即为其在屏幕空间下的坐标。
M
viewport
=
(
width
2
0
0
width
2
0
height
2
0
height
2
0
0
1
0
0
0
0
1
)
M_{\text {viewport }}=\left(\begin{array}{cccc} \frac{\text { width }}{2} & 0 & 0 & \frac{\text { width }}{2} \\ 0 & \frac{\text { height }}{2} & 0 & \frac{\text { height }}{2} \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \end{array}\right)
Mviewport =⎝⎜⎜⎛2 width 00002 height 0000102 width 2 height 01⎠⎟⎟⎞
法线变换矩阵
法线经过和顶点相同的模型变换往往得不到正确的垂直于三角面的法线,因此我们需要找到使法线正确变换到世界空间的变换矩阵。
由于法线是一个方向矢量没有起点,因此我们可以直接截取模型变换矩阵左上角的 3 × 3 3 \times 3 3×3 子矩阵作为模型变换矩阵 M M M 。
设三角形
△
A
B
C
\triangle ABC
△ABC 的切线
T
=
A
−
B
T = A-B
T=A−B ,其垂直于面法线
N
N
N 。且切线
T
T
T 经过模型变换后为
T
′
T^{\prime}
T′ 应仍然垂直变换后法线
N
′
N^{\prime}
N′ 。
N
⃗
′
⋅
T
⃗
′
=
0
(
G
N
⃗
)
⋅
(
M
T
⃗
)
=
0
\vec{N}^{\prime} \cdot \vec{T}^{\prime} = 0 \\ (G \vec{N} ) \cdot (M \vec{T} ) = 0
N′⋅T′=0(GN)⋅(MT)=0
设法线经过的变换矩阵为
G
G
G 。而向量的点积就是向量各维度分量乘积的和,因此可以将点积转换为前一个列向量转置为行向量再和后一个列向量进行分量相乘运算。
(
G
N
⃗
)
T
∗
(
M
T
⃗
)
=
0
N
⃗
T
G
T
M
T
⃗
=
0
( G \vec{N})^T * (M \vec{T} ) = 0 \\ \vec{N}^TG^TM \vec{T} =0
(GN)T∗(MT)=0NTGTMT=0
均匀缩放的法线矩阵
切线 T T T 应始终垂直于法线 N N N ,切线 T T T 经过变换 G T M G^TM GTM 应没有发生变化,即 G T M G^TM GTM 为单位矩阵 E E E 。
如果模型变换矩阵 M M M 的缩放变换是均匀的,那么模型变换矩阵 M M M 就是一个可逆的旋转矩阵,因此法线 N N N 经过的法线变换 G G G 就是模型变换矩阵 M M M 逆矩阵的转置。
注意,经过变换的法线
N
N
N 不能保证其仍然是单位长度,因此需要对法线重新归一化。但如果模型变换矩阵只包含了旋转变换,则法线变换矩阵
G
G
G 就是模型变换矩阵
M
M
M ,此时无需对法线进行归一化。
G
T
M
=
E
G
T
=
M
−
1
G
=
(
M
−
1
)
T
G^TM = E \\ G^T=M^{-1} \\ G = (M^{-1})^{T}
GTM=EGT=M−1G=(M−1)T
非均匀缩放的法线矩阵
如果模型变换矩阵 M M M 含有不均匀的缩放变换,则该矩阵可能无法求出逆矩阵。这是因为缩放变换必须满足方阵对角线上的元素都不为 0 0 0 才是可逆的。
已知矩阵的伴随矩阵的特性有
A
A
∗
=
∣
A
∣
E
AA^*=|A|E
AA∗=∣A∣E ,因此直接将伴随矩阵替换掉逆矩阵同样可以得到法线变换矩阵
G
G
G 。且其常数因子会被我们后续对法线的归一化所消除。
G
=
(
M
∗
)
T
G = (M^*)^T
G=(M∗)T
TBN 变换矩阵
施密特标准正交化
施密特正交化可以由非线性相关的一组向量
(
α
1
,
α
2
,
α
3
)
(\alpha_1,\alpha_2,\alpha_3)
(α1,α2,α3) 构造出互相正交的一组向量
(
β
1
,
β
2
,
β
3
)
(\beta_1,\beta_2,\beta_3)
(β1,β2,β3) 。再对构造出的向量进行归一化就得到了一组单位基向量
(
γ
1
,
γ
2
,
γ
3
)
(\gamma_1,\gamma_2,\gamma_3)
(γ1,γ2,γ3) 。
β
1
=
α
1
β
2
=
α
2
−
(
α
2
,
β
1
)
(
β
1
,
β
1
)
β
1
β
3
=
α
3
−
(
α
3
,
β
1
)
(
β
1
,
β
1
)
β
1
−
(
α
3
,
β
2
)
(
β
2
,
β
2
)
β
2
\begin{aligned} \beta_1 &= \alpha_1 \\ \beta_2 &= \alpha_2 - \frac{(\alpha_2,\beta_1)}{(\beta_1,\beta_1)} \beta_1 \\ \beta_3 &= \alpha_3 - \frac{(\alpha_3,\beta_1)}{(\beta_1,\beta_1)} \beta_1 - \frac{(\alpha_3,\beta_2)}{(\beta_2,\beta_2)} \beta_2 \end{aligned}
β1β2β3=α1=α2−(β1,β1)(α2,β1)β1=α3−(β1,β1)(α3,β1)β1−(β2,β2)(α3,β2)β2
γ 1 = β 1 ∣ ∣ β 1 ∣ ∣ , γ 2 = β 2 ∣ ∣ β 2 ∣ ∣ , γ 3 = β 3 ∣ ∣ β 3 ∣ ∣ \gamma_1 = \frac{\beta_1}{||\beta_1||}, \gamma_2 = \frac{\beta_2}{||\beta_2||}, \gamma_3 = \frac{\beta_3}{||\beta_3||} γ1=∣∣β1∣∣β1,γ2=∣∣β2∣∣β2,γ3=∣∣β3∣∣β3
构造切线空间
以顶点着色阶段插值得到的三角形内一点的世界空间法线
n
n
n 为
z
z
z 轴构建切线空间,则需要再取三角形纹理坐标系的
u
u
u 轴映射到三维空间为切线方向
t
t
t ,对
n
,
t
n,t
n,t 进行施密特标准正交化即可得到一组正交的单位基向量。
n
⊥
=
n
t
⊥
=
n
o
r
m
a
l
i
z
e
(
t
−
(
t
⋅
n
)
n
)
b
⊥
=
n
⊥
×
t
⊥
\begin{array} {l} n_{\perp} = n \\ t_{\perp} = normalize(t-(t \cdot n) n) \\ b_{\perp} = n_{\perp} \times t_{\perp} \end{array}
n⊥=nt⊥=normalize(t−(t⋅n)n)b⊥=n⊥×t⊥
以上得出的是将切线空间法线贴图中得到的法线变换到世界空间的 TBN 矩阵,如果用模型空间法线建立切线空间则变换的结果是模型空间法线。
如果要在切线空间中运算,只需要将世界空间法线左乘 TBN 矩阵的逆矩阵即可。而TBN矩阵是一个正交矩阵,其逆矩阵等于其转置矩阵。
M
T
B
N
=
(
T
x
T
y
T
z
0
B
x
B
y
B
z
0
N
x
N
y
N
z
0
0
0
0
1
)
M_{T B N}=\left(\begin{array}{cccc} T_{x} & T_{y} & T_{z} & 0 \\ B_{x} & B_{y} & B_{z} & 0 \\ N_{x} & N_{y} & N_{z} & 0 \\ 0 & 0 & 0 & 1 \end{array}\right)
MTBN=⎝⎜⎜⎛TxBxNx0TyByNy0TzBzNz00001⎠⎟⎟⎞