文章目录
一、Viewing Transformation概述
1.1 什么是Viewing Transformation
Viewing(视图) Transformation由View transformation 和Projection transformation组成,视图变化是将三维空间中的点变换成二维空间中,就好像是人们用相机将三维空间的场景拍摄下来,最后形成照片一样。而这个视图变换过程又包含很多复杂的操作,比如相机的位置和方向、投影的类型、视野大小,以及图像的处理等。
1.2 怎么进行Viewing Transformation
在虎书中,将整个过程拆解成四个步骤:
- 模型变换:将游戏场景中的物体调整至它们应有的状态或位置;
- 摄像机变换:根据物体和摄像机的相对位置实现摄像机变换;
- 投影变换:根据摄像机变换得到了所有可视范围内的物体对于摄像机的相对位置坐标 ( x , y , z ) (x, y, z) (x,y,z)之后,选择正交投影或者透视投影,将三维空间投影至标准二维平面 ( [ − 1 , 1 ] 2 ) ([-1, 1]^2) ([−1,1]2)之上;
- 视口变换:经过投影变换后,物体的形状大小会发生变化,需要通过视图变换将物体“还原”成原来的大小,即处于于标准平面的像素映射到屏幕分辨率之内,即:
[
−
1
,
1
]
2
→
[
0
,
w
i
d
t
h
]
∗
[
0
,
h
e
i
g
h
t
]
[-1, 1]^2 \rightarrow[0, width]*[0, height]
[−1,1]2→[0,width]∗[0,height],其中width和height指屏幕分辨率大小。
二、 模型变换(Modeling Transformation)
模型变换比较简单,就是利用变换矩阵通过仿射变换(平移、旋转、缩放等)使得物体的状态和位置发生改变,让它出现在应该出现的位置并呈现应有的状态。
三、 摄像机/视图变换(Camera/View Transformation)
首先,我们需要先定义一个摄像机,可以用三个向量表示:
- 摄像机位置(camera position) e ⃗ \vec{e} e
- 观察方向(gaze position) g ^ \hat{g} g^
- 视点正上方向(view-up vector) t ^ \hat{t} t^
为什么要做摄像机变换呢?想象一下,当我们把摄像机和物体的相对位置确定之后,如果此时摄像机和物体都不在坐标原点,这时候相对位置和坐标表示是不是很麻烦呢?是不是会导致计算的繁琐?如果能够把摄像机的坐标移动到标准的
x
y
z
xyz
xyz轴的
(
0
,
0
,
0
)
(0, 0, 0)
(0,0,0)点,那么此时物体的坐标不自然就是相对坐标了吗。并且为了方便物体投影到
x
y
xy
xy平面,在右手系中,我们假定摄像机的观察方向
g
^
\hat{g}
g^是朝着-Z的,视点正上方向
t
^
\hat t
t^是朝着Y的。
那么我们要做的事情其实只有两件(也就是下图中右上->左下):
- 将相机位置移动至原点
- 通过旋转矩阵将二者坐标系重合
具体地,我们需要完成以下操作过程: - 将摄像机位置平移到原点;
- 将摄像机的观察方向 g ^ \hat g g^旋转到-Z方向;
- 将视点正上方向 t ^ \hat t t^旋转朝向Y;
- 旋转 g ^ ∗ t ^ \hat g * \hat t g^∗t^朝至X
我们先来完成第一个步骤,将摄像机位置平移到原点,结合上一篇笔记平移变换的相关知识,我们很容易得到变换矩阵:
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}
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
]
R^{-1}_{view} = \begin{bmatrix}x_{\hat g* \hat t} & x_t & x_{-g} & 0 \\ y_{\hat g* \hat t} & y_t & y_{-g} & 0 \\ z_{\hat g* \hat t} & z_t & z_{-g} & 0 \\ 0 & 0 & 0 & 1\end{bmatrix}
Rview−1=⎣
⎡xg^∗t^yg^∗t^zg^∗t^0xtytzt0x−gy−gz−g00001⎦
⎤
而根据旋转变换矩阵的一个重要性质:旋转矩阵都是正交矩阵,那么其逆矩阵等于转置矩阵。故可知,摄像机变换的旋转矩阵为:
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} = \begin{bmatrix}x_{\hat g* \hat t} & y_{\hat g* \hat t} & z_{\hat g* \hat t} & 0 \\ x_t & y_t & z_t & 0 \\ x_{-g} & y_{-g} & z_{-g} & 0 \\ 0 & 0 & 0 & 1\end{bmatrix}
Rview=⎣
⎡xg^∗t^xtx−g0yg^∗t^yty−g0zg^∗t^ztz−g00001⎦
⎤
综上,我们得到了摄像机变换的变换矩阵:
M
v
i
e
w
=
R
v
i
e
w
T
v
i
e
w
M_{view} = R_{view}T_{view}
Mview=RviewTview
四、 投影变换(Projection Transformation)
要想将3维物体打印在屏幕上,必须将其投影到2维平面,这一操作称为投影变换。如下图,投影变换又分为正交投影和透视投影。
4.1 正交投影变换(Orthographic Projection Transformation)
正交投影是一种相对简单的投影变换,坐标的相对位置都不会改变,所有光线都是平行传播的,我们只需将物体全部转换到一个 [ − 1 , 1 ] 3 [-1, 1]^3 [−1,1]3的空间中即可。为什么要这么做呢?其实这只是为了之后的计算更加方便而已,在转换到屏幕坐标的时候就会重新拉伸回来,不必太做纠结,只需要抓住正交投影的变化核心:所有物体的相对大小位置都不会有任何变化即可。
例如下图,我们现在有一个摄像机在零点,摄像机的观察方向
g
^
\hat g
g^是朝着-Z的,视点正上方向
t
^
\hat t
t^是朝着Y的,如何得到此物体的正交投影呢?我们只需要通过将立方体的中心平移到原点和之后缩放立方体至标准
[
−
1
,
1
]
3
[-1, 1]^3
[−1,1]3空间即可。
我们可以推导出上述的正交投影矩阵:
M
o
r
t
h
=
[
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
]
=
[
2
r
−
l
0
0
−
r
+
l
r
−
l
0
2
t
−
b
0
−
t
+
b
t
−
b
0
0
2
n
−
f
−
n
+
f
n
−
f
0
0
0
1
]
M_{orth} = \begin{bmatrix} \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{bmatrix} \begin{bmatrix} 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{bmatrix} = \begin{bmatrix} \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}{n-f} & -\frac{n+f}{n-f} \\ 0 & 0 & 0 & 1\end{bmatrix}
Morth=⎣
⎡r−l20000t−b20000n−f200001⎦
⎤⎣
⎡100001000010−2r+l−2t+b−2n+f1⎦
⎤=⎣
⎡r−l20000t−b20000n−f20−r−lr+l−t−bt+b−n−fn+f1⎦
⎤
4.2 透视投影变换(Perspective Projection Transfoemation)
透视变换(Perspective Transformation)是指利用透视中心、像点、目标点三点共线的条件,按透视旋转定律使承影面(透视面)绕迹线(透视轴)旋转某一角度,破坏原有的投影光线束,仍能保持承影面上投影几何图形不变的变换。
通俗来讲就是,原来平行的事物变得不平行,遵循近大远小的规律,是一种与人类的正常感官相符合的一种投影变换。
举个例子,上面的两幅图中,左边是透视投影,右边是正交投影。我们猜想透视投影可以这样做:将Frustum的后半部分进行压缩至与前面最终的投影界面大小相同,这样就成了一个立方体,之后进行一次假设的正交投影得到每条线应该投影到的位置,最后再进行一次真正的正交投影得到最终的透视投影。上述猜想基于两个性质:
- 后半部分的面进行压缩时,z变量是不动的,即不会向前或向后伸缩;
- 后半部分的中心点位置不变。
更形象地,我们用侧面图来推导投影矩阵:将一点
(
x
,
y
,
z
)
(x, y, z)
(x,y,z)投影至投影屏幕之后,坐标变为
(
x
′
,
y
′
,
z
′
)
(x', y', z')
(x′,y′,z′),点原始距离摄影机为
z
z
z,投影后距离摄影机
n
n
n。
我们此时只考虑
y
y
y的变换,由相似三角形可以得到
y
′
=
n
2
y
y'=\frac{n}{2}y
y′=2ny,类似地有,
x
′
=
n
2
x
x'=\frac{n}{2}x
x′=2nx,根据齐次坐标的性质,我们可以知道
(
1
,
0
,
0
,
1
)
T
(1, 0, 0, 1)^T
(1,0,0,1)T与
(
k
,
0
,
0
,
k
)
T
(k, 0, 0, k)^T
(k,0,0,k)T表示同一个点,所以我们可以得到:
[
x
y
z
1
]
⇒
[
n
x
z
n
y
z
u
n
k
n
o
w
1
]
=
[
n
x
n
y
u
n
k
n
o
w
z
]
\begin{bmatrix}x \\ y \\ z \\ 1\end{bmatrix} \Rightarrow \begin{bmatrix}\frac{nx}{z} \\ \frac{ny}{z} \\ unknow \\ 1\end{bmatrix} = \begin{bmatrix}nx \\ ny \\ unknow \\ z\end{bmatrix}
⎣
⎡xyz1⎦
⎤⇒⎣
⎡znxznyunknow1⎦
⎤=⎣
⎡nxnyunknowz⎦
⎤即:
M
p
e
r
→
o
r
t
h
4
∗
4
[
x
y
z
1
]
=
[
n
x
n
y
u
n
k
n
o
w
z
]
M^{4*4}_{per \rightarrow orth}\begin{bmatrix}x \\ y \\ z \\ 1\end{bmatrix} = \begin{bmatrix}nx \\ ny \\ unknow \\ z\end{bmatrix}
Mper→orth4∗4⎣
⎡xyz1⎦
⎤=⎣
⎡nxnyunknowz⎦
⎤
所以我们可以得到:
M
p
e
r
→
o
r
t
h
=
[
n
0
0
0
0
n
0
0
?
?
?
?
0
0
1
0
]
M_{per \rightarrow orth} = \begin{bmatrix}n & 0 & 0 & 0 \\ 0 & n & 0 & 0 \\ ? & ? & ? & ? \\0 & 0 & 1& 0\end{bmatrix}
Mper→orth=⎣
⎡n0?00n?000?100?0⎦
⎤
那么接下来我们要做的就是求出第三行,这里要用到透视投影的两个性质:
- 被投影面(近面),即上图 z = n z=n z=n的那个面,其上的点投影后位置不变;
- 投影面(远面),即上图 z = f z=f z=f的那个面,其上的点投影后 z z z位置不变。
将上述
z
z
z替换为
n
n
n,由第一条性质可得:
[
x
y
z
1
]
⇒
[
n
x
z
n
y
z
u
n
k
n
o
w
1
]
=
=
[
n
x
n
y
u
n
k
n
o
w
z
]
⇒
[
x
y
n
1
]
⇒
[
x
y
n
1
]
=
=
[
n
x
n
y
n
2
n
]
\begin{bmatrix}x \\ y \\ z \\ 1\end{bmatrix} \Rightarrow \begin{bmatrix}\frac{nx}{z} \\ \frac{ny}{z} \\ unknow \\ 1\end{bmatrix} == \begin{bmatrix}nx \\ ny \\ unknow \\ z\end{bmatrix} \Rightarrow \begin{bmatrix}x \\ y \\ n \\ 1\end{bmatrix} \Rightarrow \begin{bmatrix}x \\ y \\ n \\ 1\end{bmatrix} == \begin{bmatrix}nx \\ ny \\ n^2 \\ n\end{bmatrix}
⎣
⎡xyz1⎦
⎤⇒⎣
⎡znxznyunknow1⎦
⎤==⎣
⎡nxnyunknowz⎦
⎤⇒⎣
⎡xyn1⎦
⎤⇒⎣
⎡xyn1⎦
⎤==⎣
⎡nxnyn2n⎦
⎤从而,与透射到正交的变换矩阵的第三行相乘有:
[
C
D
A
B
]
[
x
y
n
1
]
=
n
2
\begin{bmatrix} C & D & A & B\end{bmatrix} \begin{bmatrix}x \\ y \\ n \\ 1\end{bmatrix} = n^2
[CDAB]⎣
⎡xyn1⎦
⎤=n2不难得到
C
=
D
=
0
C=D=0
C=D=0,而
A
n
+
B
=
n
2
An+B=n^2
An+B=n2;同理由第二条性质可得:
[
0
0
f
1
]
⇒
[
0
0
f
1
]
=
=
[
0
0
f
2
f
]
\begin{bmatrix}0 \\ 0 \\ f \\ 1\end{bmatrix} \Rightarrow \begin{bmatrix}0 \\ 0 \\ f \\ 1\end{bmatrix} == \begin{bmatrix}0 \\ 0 \\ f^2 \\ f\end{bmatrix}
⎣
⎡00f1⎦
⎤⇒⎣
⎡00f1⎦
⎤==⎣
⎡00f2f⎦
⎤与透射到正交的变换矩阵的第三行相乘有
A
f
+
B
=
f
2
Af+B=f^2
Af+B=f2;从而我们得到
A
=
n
+
f
,
B
=
−
n
f
A = n+f, B=-nf
A=n+f,B=−nf
综上,透视投影到正交投影的变换矩阵为:
M
p
e
r
→
o
r
t
h
=
[
n
0
0
0
0
n
0
0
0
0
n
+
f
−
n
f
0
0
1
0
]
M_{per \rightarrow orth} = \begin{bmatrix}n & 0 & 0 & 0 \\ 0 & n & 0 & 0 \\ 0 & 0 & n+f & -nf \\ 0 & 0 & 1 & 0\end{bmatrix}
Mper→orth=⎣
⎡n0000n0000n+f100−nf0⎦
⎤
最后,将这个被压缩过的空间,重新正交投影成标准小立方体,故定义透视投影变换矩阵:
M
p
e
r
=
M
o
r
t
h
M
p
e
r
→
o
r
t
h
M_{per} = M_{orth}M_{per \rightarrow orth}
Mper=MorthMper→orth计算结果为:
M
p
e
r
=
[
2
n
r
−
l
0
l
+
r
l
−
r
0
0
2
n
t
−
b
b
+
t
b
−
t
0
0
0
f
+
n
n
−
f
−
2
f
n
f
−
n
0
0
1
0
]
M_{per} = \begin{bmatrix}\frac{2n}{r-l} & 0 & \frac{l+r}{l-r} & 0\\ 0 & \frac{2n}{t-b} & \frac{b+t}{b-t} & 0 \\ 0 & 0 & \frac{f+n}{n-f} & -\frac{2fn}{f-n} \\ 0 & 0 & 1 & 0\end{bmatrix}
Mper=⎣
⎡r−l2n0000t−b2n00l−rl+rb−tb+tn−ff+n100−f−n2fn0⎦
⎤
五、 视口变换(Viewport Transformation)
在经过了前面的MVP变换后,空间被转换为一个
[
−
1
,
1
]
3
[-1, 1]^3
[−1,1]3这么一个立方体,接下来,需要将这个立方体画到屏幕上,这就需要用到视口变换。
对于标准立方体
[
−
1
,
1
]
3
[-1, 1]^3
[−1,1]3,先不管它的Z轴数据(由深度缓冲来处理),屏幕映射需要将X轴和Y轴
[
−
1
,
1
]
2
[-1, 1]^2
[−1,1]2映射到屏幕坐标
[
0
,
w
i
d
t
h
]
∗
[
0
,
h
e
i
g
h
t
]
[0, width] * [0, height]
[0,width]∗[0,height],和MVP变换类似,我们可以得到该变换矩阵为:
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} \frac{width}{2} & 0 & 0 & \frac{width}{2} \\ 0 & \frac{height}{2} & 0 & \frac{height}{2} \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1\end{bmatrix}
Mviewport=⎣
⎡2width00002height0000102width2height01⎦
⎤