数学基础
1 正交矩阵
如果一个方阵和它的转置矩阵的乘积是单位矩阵的话,我们就说这个矩阵是正交的
M
M
T
=
M
T
M
=
I
MM^T = M^TM = I
MMT=MTM=I
如果如果一个矩阵是正交的,那么它的转置矩阵和逆矩阵是一样的
M
T
=
M
−
1
M^T = M^{-1}
MT=M−1
正交矩阵满足的性质:
- 矩阵的每一行,即 c 1 c_1 c1, c 2 c_2 c2和 c 3 c_3 c3都是单位矢量,因为只有这样它们与自己的点积才能是1
- 矩阵的每一行,即 c 1 c_1 c1, c 2 c_2 c2和 c 3 c_3 c3都互相垂直,因为只有这样他们之间的点积才是0
2 变换
2.1 齐次坐标
由于 3 × 3 3\times3 3×3的矩阵不能表示平移操作,我们就把其扩展到 4 × 4 4\times4 4×4的矩阵,为此需要把三维矢量转换为四维矢量,即我们所说的齐次坐标(homogeneous coordinate),如何获得齐次坐标:
对于一个点, x x x, y y y, z z z坐标不变,将w坐标设为1,即 [ x , y , z , 1 ] [x, y, z, 1] [x,y,z,1]
对于一个方向矢量, x x x, y y y, z z z坐标不变,将w坐标设为0,即 [ x , y , z , 0 ] [x, y, z, 0] [x,y,z,0]
2.2 基础变换矩阵
[ M 3 × 3 t 3 × 1 0 1 × 3 1 ] \left[ \begin{matrix} M_{3\times3} & t_{3\times1} \\ 0_{1\times3} & 1 \\ \end{matrix} \right] [M3×301×3t3×11]
左上角的矩阵 M 3 × 3 M_{3\times3} M3×3用于表示旋转和缩放, t 3 × 1 t_{3\times1} t3×1用于表示平移, 0 1 × 3 0_{1\times3} 01×3表示零矩阵,右下角元素为标量1
2.3 平移矩阵
使用矩阵乘法来表示对一个点的平移变换
[ 1 0 0 t x 0 1 0 t y 0 0 1 t z 0 0 0 1 ] [ x y z 1 ] = [ x + t x y + t y z + t z 1 ] \left[ \begin{matrix} 1 & 0 & 0 & t_x \\ 0 & 1 & 0 & t_y \\ 0 & 0 & 1 & t_z \\ 0 & 0 & 0 & 1 \\ \end{matrix} \right] \left[ \begin{matrix} x \\ y \\ z \\ 1 \\ \end{matrix} \right] =\left[ \begin{matrix} x + t_x \\ y + t_y \\ z + t_z \\ 1 \\ \end{matrix} \right] 100001000010txtytz1 xyz1 = x+txy+tyz+tz1
点的x、y、z分量分别增加一个位置偏移,可以看作点 ( x , y , z ) (x, y, z) (x,y,z)在坐标空间被平移了 ( t x , t y , t z ) (t_x,t_y,t_z) (tx,ty,tz)个单位
然而,如果对一个矢量进行平移,结果如下:
[
1
0
0
t
x
0
1
0
t
y
0
0
1
t
z
0
0
0
1
]
[
x
y
z
0
]
=
[
x
y
z
0
]
\left[ \begin{matrix} 1 & 0 & 0 & t_x \\ 0 & 1 & 0 & t_y \\ 0 & 0 & 1 & t_z \\ 0 & 0 & 0 & 1 \\ \end{matrix} \right] \left[ \begin{matrix} x \\ y \\ z \\ 0 \\ \end{matrix} \right] =\left[ \begin{matrix} x \\ y \\ z \\ 0 \\ \end{matrix} \right]
100001000010txtytz1
xyz0
=
xyz0
可以发现,平移变换不会对矢量产生任何影响
平移矩阵的逆矩阵就是反向平移得到的矩阵: – 不是正交矩阵
[
1
0
0
−
t
x
0
1
0
−
t
y
0
0
1
−
t
z
0
0
0
1
]
\left[ \begin{matrix} 1 & 0 & 0 & -t_x \\ 0 & 1 & 0 & -t_y \\ 0 & 0 & 1 & -t_z \\ 0 & 0 & 0 & 1 \\ \end{matrix} \right]
100001000010−tx−ty−tz1
2.4 缩放矩阵
使用矩阵乘法来表示对一个缩放变换
[
k
x
0
0
0
0
k
y
0
0
0
0
k
z
0
0
0
0
1
]
[
x
y
z
1
]
=
[
k
x
x
k
y
y
k
z
z
1
]
\left[ \begin{matrix} k_x & 0 & 0 & 0 \\ 0 & k_y & 0 & 0 \\ 0 & 0 & k_z & 0 \\ 0 & 0 & 0 & 1 \\ \end{matrix} \right] \left[ \begin{matrix} x \\ y \\ z \\ 1 \\ \end{matrix} \right] =\left[ \begin{matrix} k_xx \\ k_yy \\ k_zz \\ 1 \\ \end{matrix} \right]
kx0000ky0000kz00001
xyz1
=
kxxkyykzz1
如果缩放系数
k
x
=
k
y
=
k
z
k_x = k_y = k_z
kx=ky=kz,我们把这样的缩放叫做统一缩放(uniform scale),否则称为非统一缩放(ununiform scale)
缩放矩阵的逆矩阵: – 一般不是正交矩阵
[
1
k
x
0
0
0
0
1
k
y
0
0
0
0
1
k
z
0
0
0
0
1
]
\left[ \begin{matrix} \frac{1}{k_x} & 0 & 0 & 0 \\ 0 & \frac{1}{k_y} & 0 & 0 \\ 0 & 0 & \frac{1}{k_z} & 0 \\ 0 & 0 & 0 & 1 \\ \end{matrix} \right]
kx10000ky10000kz100001
2.5 旋转矩阵
绕x轴旋转
θ
\theta
θ度:
R
x
(
θ
)
=
[
1
0
0
0
0
c
o
s
(
θ
)
−
s
i
n
(
θ
)
0
0
s
i
n
(
θ
)
c
o
s
(
θ
)
0
0
0
0
1
]
R_x(\theta) = \left[ \begin{matrix} 1 & 0 & 0 & 0 \\ 0 & cos(\theta) & -sin(\theta) & 0 \\ 0 & sin(\theta) & cos(\theta) & 0 \\ 0 & 0 & 0 & 1 \\ \end{matrix} \right]
Rx(θ)=
10000cos(θ)sin(θ)00−sin(θ)cos(θ)00001
绕y轴旋转
θ
\theta
θ度:
R
y
(
θ
)
=
[
c
o
s
(
θ
)
0
s
i
n
(
θ
)
0
0
1
0
0
−
s
i
n
(
θ
)
0
c
o
s
(
θ
)
0
0
0
0
1
]
R_y(\theta) = \left[ \begin{matrix} cos(\theta) & 0 & sin(\theta) & 0 \\ 0 & 1 & 0 & 0 \\ -sin(\theta) & 0 & cos(\theta) & 0 \\ 0 & 0 & 0 & 1 \\ \end{matrix} \right]
Ry(θ)=
cos(θ)0−sin(θ)00100sin(θ)0cos(θ)00001
绕z轴旋转
θ
\theta
θ度:
R
z
(
θ
)
=
[
c
o
s
(
θ
)
−
s
i
n
(
θ
)
0
0
s
i
n
(
θ
)
c
o
s
(
θ
)
0
0
0
0
1
0
0
0
0
1
]
R_z(\theta) = \left[ \begin{matrix} cos(\theta) & -sin(\theta) & 0 & 0 \\ sin(\theta) & cos(\theta) & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \\ \end{matrix} \right]
Rz(θ)=
cos(θ)sin(θ)00−sin(θ)cos(θ)0000100001
2.6 复合变换
将平移,旋转,缩放组合起来,形成一个复杂的变换过程
P
n
e
w
=
M
t
r
a
n
s
l
a
t
i
o
n
M
r
o
t
a
t
i
o
n
M
s
c
a
l
e
P
o
l
d
P_{new} = M_{translation}M_{rotation}M_{scale}P_{old}
Pnew=MtranslationMrotationMscalePold
绝大多数情况下,我们约定变换的顺序即为先进行缩放,再进行旋转,最后进行平移
从数学公式上理解,这样的变换顺序看起来会更加直观
M
t
M
r
M
s
=
[
1
0
0
t
x
0
1
0
t
y
0
0
1
t
z
0
0
0
1
]
[
c
o
s
(
θ
)
0
s
i
n
(
θ
)
0
0
1
0
0
−
s
i
n
(
θ
)
0
c
o
s
(
θ
)
0
0
0
0
1
]
[
k
x
0
0
0
0
k
y
0
0
0
0
k
z
0
0
0
0
1
]
=
[
k
x
c
o
s
(
θ
)
0
k
z
s
i
n
(
θ
)
t
x
0
k
y
0
t
y
−
k
x
s
i
n
(
θ
)
0
k
z
c
o
s
(
θ
)
−
t
z
0
0
0
1
]
M_{t}M_{r}M_{s} = \left[ \begin{matrix} 1 & 0 & 0 & t_x \\ 0 & 1 & 0 & t_y \\ 0 & 0 & 1 & t_z \\ 0 & 0 & 0 & 1 \\ \end{matrix} \right] \left[ \begin{matrix} cos(\theta) & 0 & sin(\theta) & 0 \\ 0 & 1 & 0 & 0 \\ -sin(\theta) & 0 & cos(\theta) & 0 \\ 0 & 0 & 0 & 1 \\ \end{matrix} \right] \left[ \begin{matrix} k_x & 0 & 0 & 0 \\ 0 & k_y & 0 & 0 \\ 0 & 0 & k_z & 0 \\ 0 & 0 & 0 & 1 \\ \end{matrix} \right] = \left[ \begin{matrix} k_xcos(\theta) & 0 & k_zsin(\theta) & t_x \\ 0 & k_y & 0 & t_y \\ -k_xsin(\theta) & 0 & k_zcos(\theta) & -t_z \\ 0 & 0 & 0 & 1 \\ \end{matrix} \right]
MtMrMs=
100001000010txtytz1
cos(θ)0−sin(θ)00100sin(θ)0cos(θ)00001
kx0000ky0000kz00001
=
kxcos(θ)0−kxsin(θ)00ky00kzsin(θ)0kzcos(θ)0txty−tz1
同时Unity还规定了按
x
,
y
,
z
x,y,z
x,y,z轴下的旋转顺序,在世界坐标下按
z
x
y
zxy
zxy的旋转和在局部坐标下按照
y
x
z
yxz
yxz的旋转得到的结果相同,这也是导致万向节死锁出现的根本原因(旋转具有顺序)
3 坐标空间
顶点着色器解决把模型的顶点坐标从模型空间转换到齐次裁剪坐标系中,本节着力于解决在不同的坐标空间中实现坐标转换
3.1 坐标空间的变换
现有一个父坐标空间
P
P
P和一个子坐标空间
C
C
C,现有一点
X
X
X,它在P坐标系下的坐标为
X
p
X_p
Xp,C坐标系下的坐标为
X
c
X_c
Xc,可以通过矩阵实现在不同坐标空间下的变换
X
p
=
M
c
→
p
X
c
X_p = M_{c\rightarrow p}X_c
Xp=Mc→pXc
X c = M p → c X p X_c = M_{p\rightarrow c}X_p Xc=Mp→cXp
其中,
M
c
→
p
M_{c\rightarrow p}
Mc→p,
M
p
→
c
M_{p\rightarrow c}
Mp→c均表示坐标空间变换矩阵,同时
M
c
→
p
M_{c\rightarrow p}
Mc→p和
M
p
→
c
M_{p\rightarrow c}
Mp→c互逆,其中
M
c
→
p
M_{c \rightarrow p}
Mc→p矩阵如下:
[
∣
∣
∣
∣
x
c
y
c
z
c
O
c
∣
∣
∣
∣
0
0
0
1
]
\left[ \begin{matrix} | & | & | & | \\ x_c & y_c & z_c & O_c \\ | & | & | & | \\ 0 & 0 & 0 & 1 \end{matrix} \right]
∣xc∣0∣yc∣0∣zc∣0∣Oc∣1
其中
x
c
x_c
xc,
y
c
y_c
yc,
z
c
z_c
zc均表示坐标系
C
C
C相对于坐标系
P
P
P的三个坐标轴,
O
c
O_c
Oc表示坐标空间的原点在坐标空间
P
P
P下的位置,即把坐标轴依次放入矩阵的前3列,把原点矢量放到最后一列,再用
[
0
,
0
,
0
,
1
]
[0, 0, 0, 1]
[0,0,0,1]填充最后一行即可
同时,我们可以利用反向思维,利用该变换矩阵来反推子坐标空间 C C C的原点坐标和坐标轴方向
另外,对于矢量而言是没有位置的,因此坐标空间的原点的变化是可以忽略的,我们仅需要保留三个坐标轴的信息即可,于是矢量坐标的变换即可使用
3
×
3
3\times3
3×3矩阵来表示:
M
c
→
p
=
[
∣
∣
∣
x
c
y
c
z
c
∣
∣
∣
]
M_{c \rightarrow p} = \left[ \begin{matrix} | & | & | \\ x_c & y_c & z_c \\ | & | & | \\ \end{matrix} \right]
Mc→p=
∣xc∣∣yc∣∣zc∣
4 从模型空间变换到屏幕空间的过程
4.1 模型空间
每个模型都有自己独立的坐标空间,当它移动或旋转时,模型空间也会跟随移动或旋转,利用左手坐标系来定义
模型空间的原点和坐标轴通常由美术人员在建模软件里确定好,导入Unity后,在顶点着色器中访问到的顶点的坐标都是相对于模型空间(原点)定义的
4.2 世界空间
即Unity中的世界空间坐标系,同样利用左手坐标系来定义
顶点变化的第一步,即模型变换(model transform),Unity中会按照先缩放,再旋转,最后平移的操作生成变化矩阵
例如:对物体进行
(
2
,
2
,
2
)
(2,2,2)
(2,2,2)的缩放,由进行
(
0
,
150
,
0
)
(0, 150, 0)
(0,150,0)的旋转和
(
5
,
0
,
25
)
(5,0,25)
(5,0,25)的平移,变换矩阵如下
M
m
o
d
e
l
=
[
1
0
0
t
x
0
1
0
t
y
0
0
1
t
z
0
0
0
1
]
[
c
o
s
θ
0
s
i
n
θ
0
0
1
0
0
−
s
i
n
θ
0
c
o
s
θ
0
0
0
0
1
]
[
k
x
0
0
0
0
k
y
0
0
0
0
k
z
0
0
0
0
1
]
M_{model} =\left[ \begin{matrix} 1 & 0 & 0 & t_x \\ 0 & 1 & 0 & t_y \\ 0 & 0 & 1 & t_z \\ 0 & 0 & 0 & 1 \\ \end{matrix} \right] \left[ \begin{matrix} cos\theta & 0 & sin\theta & 0 \\ 0 & 1 & 0 & 0 \\ -sin\theta & 0 & cos\theta & 0 \\ 0 & 0 & 0 & 1 \\ \end{matrix} \right] \left[ \begin{matrix} k_x & 0 & 0 & 0 \\ 0 & k_y & 0 & 0 \\ 0 & 0 & k_z & 0 \\ 0 & 0 & 0 & 1 \\ \end{matrix} \right]
Mmodel=
100001000010txtytz1
cosθ0−sinθ00100sinθ0cosθ00001
kx0000ky0000kz00001
= [ 1 0 0 5 0 1 0 0 0 0 1 25 0 0 0 1 ] [ c o s 150 ° 0 s i n 150 ° 0 0 1 0 0 − s i n ° 0 c o s 150 ° 0 0 0 0 1 ] [ 2 0 0 0 0 2 0 0 0 0 2 0 0 0 0 1 ] = [ − 1.732 0 1 5 0 2 0 0 − 1 0 − 1 , 732 25 0 0 0 1 ] = \left[ \begin{matrix} 1 & 0 & 0 & 5 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 1 & 25 \\ 0 & 0 & 0 & 1 \\ \end{matrix} \right] \left[ \begin{matrix} cos150\degree & 0 & sin150\degree & 0 \\ 0 & 1 & 0 & 0 \\ -sin\degree & 0 & cos150\degree & 0 \\ 0 & 0 & 0 & 1 \\ \end{matrix} \right] \left[ \begin{matrix} 2 & 0 & 0 & 0 \\ 0 & 2 & 0 & 0 \\ 0 & 0 & 2 & 0 \\ 0 & 0 & 0 & 1 \\ \end{matrix} \right] = \left[ \begin{matrix} -1.732 & 0 & 1 & 5 \\ 0 & 2 & 0 & 0 \\ -1 & 0 & -1,732 & 25 \\ 0 & 0 & 0 & 1 \\ \end{matrix} \right] = 10000100001050251 cos150°0−sin°00100sin150°0cos150°00001 2000020000200001 = −1.7320−10020010−1,732050251
于是,我们可以利用此矩阵进行模型变换:
P
w
o
r
l
d
=
M
m
o
d
e
l
P
m
o
d
e
l
P_{world} = M_{model}P_{model}
Pworld=MmodelPmodel
4.3 观察空间
也被称为摄像机空间,可以认为是模型空间的一个特例,它决定了渲染游戏所使用的视角
在观察空间中,摄像机位置为原点,然而不同的是,观察空间使用的右手坐标系, + x +x +x指向右方, + y +y +y指向上方, + z +z +z指向后方
注意观察者空间与屏幕空间的异同,观察者空间为三维坐标系,而屏幕空间为二维坐标系,从观察者空间到屏幕空间需要经过投影
顶点变化的第二步,即观察者变换(view transform),我们需要将点从世界空间转换到观察者空间上,有以下两种方法:
- 法一:计算观察空间的三个坐标轴在世界空间下的表示,构建从观察空间变换到是世界空间的变换矩阵,再求逆得到从世界空间到观察空间的变换矩阵
- 法二:想象平移观察空间,让摄像机位于世界坐标原点,坐标轴与世界坐标中的坐标轴重合,这样平移观察空间的过程也将观察空间中的点同样平移了
根据法二,由当前摄像机Transform组件可知摄像机先在世界空间中按
(
30
,
0
,
0
)
(30,0,0)
(30,0,0)进行旋转,然后按
(
0
,
10
,
−
10
)
(0,10,-10)
(0,10,−10)平移,于是,为了将摄像机移回原位置,进行逆向变换,即先按
(
0
,
−
10
,
10
)
(0,-10,10)
(0,−10,10)平移,再按
(
−
30
,
0
,
0
)
(-30,0,0)
(−30,0,0)进行旋转
M
v
i
e
w
=
[
1
0
0
0
0
c
o
s
θ
−
s
i
n
θ
0
0
s
i
n
θ
c
o
s
θ
0
0
0
0
1
]
[
1
0
0
t
x
0
1
0
t
y
0
0
1
t
z
0
0
0
1
]
M_{view} = \left[ \begin{matrix} 1 & 0 & 0 & 0 \\ 0 & cos\theta & -sin\theta & 0 \\ 0 & sin\theta & cos\theta & 0 \\ 0 & 0 & 0 & 1 \\ \end{matrix} \right] \left[ \begin{matrix} 1 & 0 & 0 & t_x \\ 0 & 1 & 0 & t_y \\ 0 & 0 & 1 & t_z \\ 0 & 0 & 0 & 1 \\ \end{matrix} \right]
Mview=
10000cosθsinθ00−sinθcosθ00001
100001000010txtytz1
= [ 1 0 0 0 0 c o s ( − 30 ) ° − s i n ( − 30 ) ° 0 0 s i n ( − 30 ) ° c o s ( − 30 ) ° 0 0 0 0 1 ] [ 1 0 0 0 0 1 0 − 10 0 0 1 10 0 0 0 1 ] = [ 1 0 0 0 0 0.866 0.5 − 3.66 0 − 0.5 0.866 13.66 0 0 0 1 ] =\left[ \begin{matrix} 1 & 0 & 0 & 0 \\ 0 & cos(-30)\degree & -sin(-30)\degree & 0 \\ 0 & sin(-30)\degree & cos(-30)\degree & 0 \\ 0 & 0 & 0 & 1 \\ \end{matrix} \right] \left[ \begin{matrix} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & -10 \\ 0 & 0 & 1 & 10 \\ 0 & 0 & 0 & 1 \\ \end{matrix} \right] =\left[ \begin{matrix} 1 & 0 & 0 & 0 \\ 0 & 0.866 & 0.5 & -3.66 \\ 0 & -0.5 & 0.866 & 13.66 \\ 0 & 0 & 0 & 1 \\ \end{matrix} \right] = 10000cos(−30)°sin(−30)°00−sin(−30)°cos(−30)°00001 1000010000100−10101 = 100000.866−0.5000.50.86600−3.6613.661
由于观察空间使用右手坐标系,需要对z轴取反,通过乘以一个特殊矩阵来得到最终的观察变换矩阵
M
v
i
e
w
M_{view}
Mview:
M
v
i
e
w
=
M
n
e
g
P
v
i
e
w
=
[
1
0
0
0
0
1
0
0
0
0
−
1
0
0
0
0
0
]
[
1
0
0
0
0
0.866
0.5
−
3.66
0
−
0.5
0.866
13.66
0
0
0
1
]
=
[
1
0
0
0
0
0.866
0.5
−
3.66
0
0.5
−
0.866
−
13.66
0
0
0
1
]
M_{view} = M_{neg}P_view= \left[ \begin{matrix} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & -1 & 0 \\ 0 & 0 & 0 & 0 \\ \end{matrix} \right] \left[ \begin{matrix} 1 & 0 & 0 & 0 \\ 0 & 0.866 & 0.5 & -3.66 \\ 0 & -0.5 & 0.866 & 13.66 \\ 0 & 0 & 0 & 1 \\ \end{matrix} \right] =\left[ \begin{matrix} 1 & 0 & 0 & 0 \\ 0 & 0.866 & 0.5 & -3.66 \\ 0 & 0.5 & -0.866 & -13.66 \\ 0 & 0 & 0 & 1 \\ \end{matrix} \right]
Mview=MnegPview=
1000010000−100000
100000.866−0.5000.50.86600−3.6613.661
=
100000.8660.5000.5−0.86600−3.66−13.661
接下来即可i利用该矩阵进行坐标变换
P
v
i
e
w
=
M
v
i
e
w
P
w
o
r
l
d
P_{view} = M_{view}P_{world}
Pview=MviewPworld
4.4 裁剪空间
顶点接下来要从观察空间转化到裁剪空间(clip space),用于变换的矩阵叫做裁剪矩阵(clip matrix),也被称作投影矩阵(projection matrix)
裁剪空间的目的是能够方便地对渲染图元进行裁剪:完全位于这块空间内部的图元将会被保留,完全位于这块空间外部的图元将会被剔除,而与这块空间相交的图元就会被裁剪,同时,这块空间由**视锥体(view frustum)**决定
视锥体由六个平面包围而成,这些平面被称为裁剪平面(clip planes),由两种投影类型:
- 正交投影(orthographic projection):一般用于2D游戏,完全保留物体的距离和角度,所有网格大小相等,裁剪平面为长方形
- 透视投影(perspective projection):一般用于3D游戏,离摄像机越近地物体越大,越远的越小,裁剪平面为梯台形
视锥体地6块裁剪平面中,有两块比较特殊,分别被称为近裁剪平面(near clip plane),远裁剪平面(far clip plane)
投影矩阵的目的:
- 为投影做准备:投影矩阵并没有进行真正的投影,而是为投影做准备,真正的投影发生在**齐次除法(homogeneous division)**中
- 对 x 、 y 、 z x、y、z x、y、z分量进行缩放:使用 w w w作为一个范围值,使 x 、 y 、 z x、y、z x、y、z均位于这个范围内
4.4.1 透视投影
- Field Of View(FOV):视锥体的张开角度
- Near:摄像机距离近裁剪平面的距离
- Far:摄像机距离远裁剪平面的距离
求得近裁剪平面和远裁剪平面的高度:
n
e
a
r
C
l
i
p
P
l
a
n
e
H
e
i
g
h
t
=
2
×
N
e
a
r
×
t
a
n
F
O
V
2
f
a
r
C
l
i
p
P
l
a
n
e
H
e
i
g
h
t
=
2
×
F
a
r
×
t
a
n
F
O
V
2
nearClipPlaneHeight = 2 \times Near \times tan{\frac{FOV}{2}} \\ farClipPlaneHeight = 2 \times Far \times tan{\frac{FOV}{2}}
nearClipPlaneHeight=2×Near×tan2FOVfarClipPlaneHeight=2×Far×tan2FOV
可根据摄像机的纵横比求得远近裁剪平面的宽度:
A
s
p
e
c
t
=
W
i
d
t
h
H
e
i
g
h
t
H
e
g
h
t
=
W
i
d
t
h
A
s
p
e
c
t
Aspect = \frac{Width}{Height}\\ Heght = \frac{Width}{Aspect}
Aspect=HeightWidthHeght=AspectWidth
这里,由于相关证明过于复杂,我们直接给出透视投影的投影矩阵:
M
p
e
r
s
p
e
c
t
i
v
e
=
[
c
o
t
F
O
V
2
A
s
p
e
c
t
0
0
0
0
c
o
t
F
O
V
2
0
0
0
0
−
F
a
r
+
N
e
a
r
F
a
r
−
N
e
a
r
−
2
×
N
e
a
r
×
F
a
r
F
a
r
−
N
e
a
r
0
0
−
2
0
]
M_{perspective} = \left[ \begin{matrix} \frac{cot{\frac{FOV}{2}}}{Aspect} & 0 & 0 & 0 \\ 0 & cot{\frac{FOV}{2}} & 0 & 0 \\ 0 & 0 & -\frac{Far+Near}{Far-Near} & -\frac{2\times Near \times Far}{Far - Near} \\ 0 & 0 & -2 & 0 \end{matrix} \right]
Mperspective=
Aspectcot2FOV0000cot2FOV0000−Far−NearFar+Near−200−Far−Near2×Near×Far0
由此,便可将点从观察空间通过透视投影投影到裁剪空间
P
c
l
i
p
=
M
p
e
r
s
p
e
c
t
i
v
e
P
v
i
e
w
P
v
i
e
w
=
[
x
,
y
,
z
,
w
]
T
P_{clip} = M_{perspective}P_{view} \\ P_{view} = [x,y,z,w]^T
Pclip=MperspectivePviewPview=[x,y,z,w]T
最后,我们需要判断一个变换后的点是否位于视锥体内,如果一个顶点位于视锥体内,那么它变换后的坐标一定满足:
−
w
≤
x
≤
w
−
w
≤
y
≤
w
−
w
≤
z
≤
w
-w \leq x \leq w \\ -w \leq y \leq w \\ -w \leq z \leq w \\
−w≤x≤w−w≤y≤w−w≤z≤w
4.4.2 正交投影
视锥体为长方体,可以通过Camera组件的Size属性来改变视锥体竖直方向上高度的一半,同时可以求出视锥体近裁剪平面和远裁剪平面的高度
n
e
a
r
C
l
i
p
P
l
a
n
e
H
e
i
g
h
t
=
A
×
S
i
z
e
f
a
r
C
l
i
p
P
l
a
n
e
H
e
i
g
h
t
=
n
e
a
r
C
l
i
p
P
l
a
n
e
H
e
i
g
h
t
nearClipPlaneHeight = A\times Size\\ farClipPlaneHeight = nearClipPlaneHeight
nearClipPlaneHeight=A×SizefarClipPlaneHeight=nearClipPlaneHeight
根据纵横比,可以得到裁剪平面的宽度:
A
s
p
e
c
t
=
W
i
d
t
h
H
e
i
g
h
t
H
e
g
h
t
=
W
i
d
t
h
A
s
p
e
c
t
Aspect = \frac{Width}{Height}\\ Heght = \frac{Width}{Aspect}
Aspect=HeightWidthHeght=AspectWidth
由于相关证明过于复杂,我们同样直接给正交投影的投影矩阵:
M
o
r
t
h
o
g
r
a
p
h
i
c
=
[
1
A
s
p
e
c
t
×
S
i
z
e
0
0
0
0
1
S
i
z
e
0
0
0
0
−
2
F
a
r
−
N
e
a
r
−
N
e
a
r
+
F
a
r
F
a
r
−
N
e
a
r
0
0
−
2
0
]
M_{orthographic } = \left[ \begin{matrix} \frac{1}{Aspect \times Size} & 0 & 0 & 0 \\ 0 & \frac{1}{Size} & 0 & 0 \\ 0 & 0 & -\frac{2}{Far-Near} & -\frac{Near + Far}{Far - Near} \\ 0 & 0 & -2 & 0 \end{matrix} \right]
Morthographic=
Aspect×Size10000Size10000−Far−Near2−200−Far−NearNear+Far0
由此,便可将点从观察空间通过透视投影投影到裁剪空间
P
c
l
i
p
=
M
o
r
t
h
o
g
r
a
p
h
i
c
P
v
i
e
w
P
v
i
e
w
=
[
x
,
y
,
z
,
w
]
T
P_{clip} = M_{orthographic}P_{view} \\ P_{view} = [x,y,z,w]^T
Pclip=MorthographicPviewPview=[x,y,z,w]T
正交投影进行变换后,其w分量仍为1,判断正交投影变换后的顶点是否位于视锥体内只用判断
x
、
y
、
z
x、y、z
x、y、z的绝对值是否小于1即可
4.5 屏幕空间
这一步我们要把视锥体投影到屏幕空间(screen space)
首先,进行齐次除法(homogeneous division),也叫透视除法(perspective division),即用齐次坐标系的x、y、z分量除以w分量,将坐标转换到**归一化的设备坐标系(Normalized Device Coordinates, NDC)**中,经过这一步,我们可以把坐标从齐次裁剪坐标空间转换到NDC中,经过透视变换后的裁剪空间,经过齐次除法后会变化到一个立方体内
接着,我们需要将变换后的x、y坐标映射到输出窗口对应的像素坐标,在Unity中,屏幕空间左下角像素坐标是
(
0
,
0
)
(0,0)
(0,0),右上角的像素坐标是
(
p
i
x
e
l
W
i
d
t
h
,
p
i
x
e
l
H
e
i
g
h
t
)
(pixelWidth,pixelHeight)
(pixelWidth,pixelHeight),由于当前
x
、
y
x、y
x、y坐标都是
[
−
1
,
1
]
[-1,1]
[−1,1],因此整个映射的过程就是一个缩放过程
s
c
r
e
e
n
x
=
c
l
i
p
x
×
p
i
x
e
l
W
i
d
t
h
2
×
c
l
i
p
w
+
p
i
x
e
l
W
i
d
t
h
2
s
c
r
e
e
n
y
=
c
l
i
p
y
×
p
i
x
e
l
H
e
i
g
h
t
2
×
c
l
i
p
w
+
p
i
x
e
l
H
e
i
g
h
t
2
screen_x = \frac{clip_x \times pixelWidth}{2\times clip_w} + \frac{pixelWidth}{2}\\ screen_y = \frac{clip_y \times pixelHeight}{2\times clip_w} + \frac{pixelHeight}{2}
screenx=2×clipwclipx×pixelWidth+2pixelWidthscreeny=2×clipwclipy×pixelHeight+2pixelHeight
剩余的z分量将会用于深度缓冲,将在后面继续学习
5 法线变换
一般来说,点和绝大部分方向矢量都可以使用同一个
4
×
4
4\times4
4×4或
3
×
3
3\times3
3×3 的变换矩阵
M
A
→
b
M_{A\rightarrow b}
MA→b把其从坐标空间A变换到坐标空间B中,但在变换法线的时候,如果使用同一个变换矩阵,就无法确保维持法线的垂直性
根据发现的性质,我们可知
T
A
×
N
A
=
0
T_A\times N_A = 0
TA×NA=0,给定变换矩阵,我们已知
T
B
=
M
A
→
B
T
A
T_B = M_{A\rightarrow B}T_A
TB=MA→BTA,经过以下公式推导:
T
B
N
A
=
(
M
A
→
B
T
A
)
(
G
N
A
)
=
0
(
M
A
→
B
T
A
)
(
G
N
A
)
=
(
M
A
→
B
T
A
)
t
(
G
N
A
)
=
T
A
T
M
A
→
B
T
G
N
A
=
T
A
T
(
M
A
→
B
T
G
)
N
A
=
0
T_BN_A = (M_{A\rightarrow B}T_A)(GN_A) = 0\\ (M_{A\rightarrow B}T_A)(GN_A) = (M_{A\rightarrow B}T_A)^t(GN_A) = T_A^TM_{A \rightarrow B}^TGN_A = T_A^T(M_{A \rightarrow B}^TG)N_A = 0
TBNA=(MA→BTA)(GNA)=0(MA→BTA)(GNA)=(MA→BTA)t(GNA)=TATMA→BTGNA=TAT(MA→BTG)NA=0
根据
T
A
×
N
A
=
0
T_A\times N_A = 0
TA×NA=0,因此我们只需要满足
M
A
→
B
T
G
=
I
M_{A \rightarrow B}^TG = I
MA→BTG=I即可,即
G
=
(
M
A
→
B
T
)
−
1
=
(
M
A
→
B
−
1
)
T
G = (M_{A \rightarrow B}^T)^{-1} = (M_{A \rightarrow B}^{-1})^T
G=(MA→BT)−1=(MA→B−1)T,即使用原变换矩阵的逆转置矩阵来变换法线就可以得到正确的结果
6 Unity Shader的内置变量
6.1 变换矩阵
变量名 | 描述 |
---|---|
UNITY_MATRIX_MVP | 当前的的模型+观察+投影矩阵,用于将顶点、方向矢量从模型空间变换到裁剪空间 |
UNITY_MATRIX_MV | 当前的模型+观察矩阵,用于将顶点/方向矢量从模型空间变换到观察空间 |
UNITY_MATRIX_V | 当前的观察矩阵,用于将顶点/方向矢量从世界空间变换到观察空间 |
UNITY_MATRIX_P | 当前的投影矩阵,用于将顶点/方向矢量从观察空间变换到裁剪空间 |
UNITY_MATRIX_VP | 当前的观察+投影矩阵,用于将顶点/方向矢量从世界空间变换到裁剪空间 |
UNITY_MATRIX_T_MV | UNITY_MATRIX_MV的转置 |
UNITY_MATRIX_IT_MV | UNITY_MATRIX_MV的逆转置矩阵,用于将法线从模型空间变换到观察空间,也可以用于得到UNITY_MATRIX_MV的逆矩阵 |
_Object2World | 当前的模型矩阵,用于将顶点/方向矢量从模型空间变换到世界空间 |
_World2Object | _Object2World的逆矩阵,用于将顶点/方向矢量从世界空间变换到模型空间 |
如果UNITY_MATRIX_MV是正交矩阵的话,UNITY_MATRIX_T_MV就是它的逆矩阵,
-
如果一个模型只包含旋转变换,UNITY_MATRIX_MV就是正交矩阵
-
如果一个模型的变换只包含旋转和统一缩放,那么UNITY_MATRIX_MV就几乎是一个正交矩阵,因为统一缩放会导致每一行的矢量长度不为1,而是k,不符合正交矩阵的特性,我们可以通过除以一个统一缩放系数,把它变成正交矩阵
-
如果我们只对方向矢量进行变换,即不用考虑平移变换,可以截取UNITY_MATRIX_T_MV的前3行3列来把方向矢量从观察空间变换到模型空间,对于方向矢量可以提前进行归一化处理以消除统一缩放的影响
对于UNITY_MATRIX_IT_MV矩阵,它可以把法线从模型空间变换到观察空间,将其转置也可以直接得到UNITY_MATRIX_MV的逆矩阵
// 法一:使用transpose得到UNITY_MATRIX_IT_MV的转置
// 得到UNITY_MATRIX_MV的逆矩阵,然后进行矩阵乘法,将观察空间中的点或方向矢量转换到模型空间
float4 modelPos = mul(transpose(UNITY_MATRIX_IT_MV), viewPos);
// 法二:交换mul参数的位置,进行乘法,本质一样
float4 modelPos = mul(viewPos, UNITY_MATRIX_IT_MV);
6.2 摄像机和屏幕参数
变量名 | 类型 | 描述 |
---|---|---|
_WorldSpaceCameraPos | float3 | 摄像机在世界空间的位置 |
_ProjectionParams | float4 | x = 1.0(如果正在使用一个翻转的投影矩阵进行渲染),y = Near,z = Far,w = 1.0 + 1.0 / Far,其中Near和Far分别是近裁剪平面和摄像机的距离 |
_ScreenParams | float4 | x = width,y = height,z = 1.0 + 1.0 / width,w = 1.0 + 1.0 / height,其中width和height分别是该摄像机的渲染目标的像素宽度和高度 |
_ZBufferParams | float4 | x = 1 - Far / Near,y = Far / Near,z = x / Far,w = y / Far,该变量用于线性化z缓存中的深度值 |
unity_OrthoParams | float4 | x = width,y = height,z没有定义,w = 1.0(正交摄像机)或0.0(透视摄像机),其中width和height是正交投影摄像机的宽度和高度 |
unity_CameraProjection | float4x4 | 该摄像机的投影矩阵 |
unity_CameraInvProjection | float4x4 | 该摄像机的投影矩阵的逆矩阵 |
unity_CameraWorldClipPlanes[6] | float4 | 摄像机的裁剪平面在空间下的等式,按左右下上近远裁剪平面的顺序 |
7 补充
7.1 使用 3 × 3 3\times3 3×3还是 4 × 4 4\times4 4×4矩阵
- 线性变换:如只对方向矢量进行变换,使用 3 × 3 3\times3 3×3矩阵
- 存在平移变换:如对顶点进行变换,使用 4 × 4 4\times4 4×4矩阵
7.2 CG中的矢量和矩阵类型
矢量类型:
float4 a = float4(1.0, 2.0, 3.0, 4.0);
float4 a = float4(1.0, 2.0, 3.0, 4.0);
float result = dot(a, b); // 进行点积操作
矩阵类型:
float4 v = float4(1.0, 2.0, 3.0, 4.0);
float4x4 M = float4x4{1.0, 0.0, 0.0, 0.0
0.0, 1.0, 0.0, 0.0
0.0, 0.0, 1.0, 0.0
0.0, 0.0, 0.0, 1.0}
float4 result = mul(M, v); // 矩阵乘法
float4x4 M_transpose = transpose(M)
float4 row = M[0]; // 访问M的第一行
float element = M[1][0]; // 访问第2行第1个元素
CG中,对float4x4等类型的变量都是按行优先的方式进行填充