一个物体要投影到屏幕上需要依次经过 Model(世界矩阵,转换到世界空间)、View(视图矩阵,转换到观察空间/摄像机空间)、Projection(投影矩阵,转换到裁剪空间),合起来就是常见的MVP矩阵。
Model矩阵和View矩阵很好理解,只是单纯的坐标系变换(注:Unity里摄像机坐标系采用的是右手系,与其世界坐标系相反,算是一个小的坑点),如果有不理解的推荐一个视频课程,线性代数的本质,这里就不详细说明了。
齐次坐标
在介绍投影矩阵前,我们先来了解下齐次坐标。齐次坐标就是将一个原本是n维的向量用一个n+1维向量来表示,比如三维向量
(
x
,
y
,
z
)
\displaystyle (x,y,z)
(x,y,z)采用
(
x
,
y
,
z
,
w
)
\displaystyle (x,y,z,w)
(x,y,z,w)来表示。其存在的意义有篇文章介绍的很好,这里给出原文和译文链接。而在线性变换过程中,齐次坐标的作用主要是提供了位移功能。
[
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
)
\begin{bmatrix} \color{red}1 & \color{red}0 & \color{red}0 & \color{red}{T_x} \\ \color{green}0 & \color{green}1 & \color{green}0 & \color{green}{T_y} \\ \color{blue}0 & \color{blue}0 & \color{blue}1 & \color{blue}{T_z} \\ \color{purple}0 & \color{purple}0 & \color{purple}0 & \color{purple}1 \end{bmatrix} \cdot \begin{pmatrix} x \\ y \\ z \\ 1 \end{pmatrix} = \begin{pmatrix} x + \color{red}{T_x} \\ y + \color{green}{T_y} \\ z + \color{blue}{T_z} \\ 1 \end{pmatrix}
⎣⎢⎢⎡100001000010TxTyTz1⎦⎥⎥⎤⋅⎝⎜⎜⎛xyz1⎠⎟⎟⎞=⎝⎜⎜⎛x+Txy+Tyz+Tz1⎠⎟⎟⎞ 上面就是一个标准的位移变换,移动的距离为
(
T
x
,
T
y
,
T
z
)
\displaystyle (\color{red}{T_x},\color{green}{T_y},\color{blue}{T_z})
(Tx,Ty,Tz),而齐次坐标
w
\displaystyle w
w则为1(当
w
\displaystyle w
w为0时,则说明
(
x
,
y
,
z
)
\displaystyle (x,y,z)
(x,y,z)代表一个不可位移的向量)。
投影矩阵
这里先推荐一篇很好的投影文章。
投影有两种方式,一种是近大远小的透视投影,另一种则是远近一样大的正交投影。我们的摄像机会有一个可视范围,投影要做的其实就是将这个可视范围转换成标准设备坐标(NDC)
(
−
1
⩽
x
⩽
1
,
−
1
⩽
y
⩽
1
,
−
1
⩽
z
⩽
1
)
\displaystyle ( -1\leqslant x\leqslant 1,-1\leqslant y\leqslant 1,-1\leqslant z\leqslant 1)
(−1⩽x⩽1,−1⩽y⩽1,−1⩽z⩽1),超出此范围的顶点则会进行裁剪。
透视投影
如上图所示,左边是我们摄像机的可视范围,右边则是NDC范围。透视投影的可视范围是一个锥形区域,现在我们需要想办法将其转换成一个正方体范围。这里的
n
,
f
\displaystyle n, f
n,f表示近平面和远平面距摄像机的距离,
l
、
r
、
t
、
b
\displaystyle l、r、t、b
l、r、t、b则为近平面的左、右、上、下。
我们先考虑如何求投影坐标的
x
\displaystyle x
x。其实观察空间的原点和目标点相连的直线与近平面相较的坐标点,就是投影的坐标点,而这一点我们可以很容易的通过相似三角形等比计算出来。所以可得
x
p
=
n
⋅
x
e
−
z
e
\displaystyle x_p = \frac{n\cdot x_e}{-z_e}
xp=−zen⋅xe(注:这里
x
e
\displaystyle x_e
xe表示该点在观察空间上的
x
\displaystyle x
x坐标)。同理,
y
p
=
n
⋅
y
e
−
z
e
\displaystyle y_p = \frac{n\cdot y_e}{-z_e}
yp=−zen⋅ye。我们可以先观察下这个坐标,
z
e
\displaystyle {z_e}
ze在分母上,这是一般的矩阵变换不可能得到的结果,我们需要借助齐次坐标来实现,所以我们首先可以确定的是,经透视投影矩阵变换后的坐标的
w
\displaystyle w
w,一定是
z
e
\displaystyle {z_e}
ze的倍数。于是透视投影矩阵的第四行可以确定了。
(
x
c
y
c
z
c
w
c
)
=
(
⋅
⋅
⋅
⋅
⋅
⋅
⋅
⋅
⋅
⋅
⋅
⋅
0
0
−
1
0
)
(
x
e
y
e
z
e
w
e
)
,
∴
w
c
=
−
z
e
\left( \begin{matrix} x_c\\ y_c\\ z_c\\ w_c \end{matrix} \right)=\left( \begin{matrix} \cdot & \cdot &\cdot &\cdot\\ \cdot & \cdot &\cdot &\cdot\\ \cdot & \cdot &\cdot &\cdot\\ 0 & 0 & -1 & 0 \end{matrix} \right) \left( \begin{matrix} x_e\\ y_e\\ z_e\\ w_e \end{matrix} \right), \therefore w_c=-z_e
⎝⎜⎜⎛xcyczcwc⎠⎟⎟⎞=⎝⎜⎜⎛⋅⋅⋅0⋅⋅⋅0⋅⋅⋅−1⋅⋅⋅0⎠⎟⎟⎞⎝⎜⎜⎛xeyezewe⎠⎟⎟⎞,∴wc=−ze 接下来我们来确定
x
\displaystyle x
x和
y
\displaystyle y
y坐标该如何变换。以
x
\displaystyle x
x为例,投影到近平面的点
x
p
\displaystyle x_p
xp的范围是
[
l
,
r
]
\displaystyle [l,r]
[l,r],我们的目标是将其转换为NDC下的坐标点
x
n
\displaystyle x_n
xn(注:这里的
x
n
\displaystyle x_n
xn是齐次坐标进行了转换后的结果,即
x
n
=
x
c
w
c
=
x
c
−
z
e
\displaystyle x_n=\frac{x_c}{w_c}=\frac{x_c}{-z_e}
xn=wcxc=−zexc),而
l
\displaystyle l
l与
r
\displaystyle r
r是对称的,即
l
+
r
=
0
\displaystyle l+r = 0
l+r=0。所以我们可推得公式:
x
n
=
x
p
r
=
n
x
e
−
z
e
r
x_n=\frac{x_p}{r}=\frac{nx_e}{-z_er}
xn=rxp=−zernxe 同理可得:
y
n
=
y
p
t
=
n
y
e
−
z
e
t
y_n=\frac{y_p}{t}=\frac{ny_e}{-z_et}
yn=typ=−zetnye 所以矩阵的前两行我们又可以确定了
(
x
c
y
c
z
c
w
c
)
=
(
n
r
0
0
0
0
n
t
0
0
⋅
⋅
⋅
⋅
0
0
−
1
0
)
(
x
e
y
e
z
e
w
e
)
\left( \begin{matrix} x_c\\ y_c\\ z_c\\ w_c \end{matrix} \right)=\left( \begin{matrix} \frac{n}{r} & 0 & 0 & 0\\ 0 & \frac{n}{t} & 0 & 0\\ \cdot & \cdot & \cdot & \cdot\\ 0 & 0 & -1 & 0 \end{matrix} \right) \left( \begin{matrix} x_e\\ y_e\\ z_e\\ w_e \end{matrix} \right)
⎝⎜⎜⎛xcyczcwc⎠⎟⎟⎞=⎝⎜⎜⎛rn0⋅00tn⋅000⋅−100⋅0⎠⎟⎟⎞⎝⎜⎜⎛xeyezewe⎠⎟⎟⎞ 最后我们来确定
z
\displaystyle z
z轴的变换。在观察空间中
z
e
\displaystyle z_e
ze的范围是
[
−
n
,
−
f
]
\displaystyle [-n,-f]
[−n,−f],我们希望也能将其转换成NDC空间的
[
−
1
,
1
]
\displaystyle [-1,1]
[−1,1]。因为这个不可能和
x
,
y
\displaystyle x,y
x,y有关,所以我们设矩阵为
(
x
c
y
c
z
c
w
c
)
=
(
n
r
0
0
0
0
n
t
0
0
0
0
A
B
0
0
−
1
0
)
(
x
e
y
e
z
e
w
e
)
\left( \begin{matrix} x_c\\ y_c\\ z_c\\ w_c \end{matrix} \right)=\left( \begin{matrix} \frac{n}{r} & 0 & 0 & 0\\ 0 & \frac{n}{t} & 0 & 0\\ 0 & 0 & A & B\\ 0 & 0 & -1 & 0 \end{matrix} \right) \left( \begin{matrix} x_e\\ y_e\\ z_e\\ w_e \end{matrix} \right)
⎝⎜⎜⎛xcyczcwc⎠⎟⎟⎞=⎝⎜⎜⎛rn0000tn0000A−100B0⎠⎟⎟⎞⎝⎜⎜⎛xeyezewe⎠⎟⎟⎞ 可推得
z
n
=
z
c
w
c
=
A
z
e
+
B
w
e
−
z
e
=
A
z
e
+
B
−
z
e
z_n =\frac{z_c}{w_c}= \frac{Az_e + Bw_e}{-z_e}= \frac{Az_e + B}{-z_e}
zn=wczc=−zeAze+Bwe=−zeAze+B 我们可以分别将
(
−
1
,
1
)
\displaystyle (-1,1)
(−1,1)代入
z
n
\displaystyle z_n
zn,
(
−
n
,
−
f
)
\displaystyle (-n,-f)
(−n,−f)代入
z
e
\displaystyle z_e
ze得到方程组
{
−
A
n
+
B
=
−
n
−
A
f
+
B
=
f
\left\{ \begin{array}{lr} -An + B = -n &\\ -Af + B = f & \end{array} \right.
{−An+B=−n−Af+B=f 解该方程组得到
{
A
=
−
f
+
n
f
−
n
B
=
−
2
f
n
f
−
n
\left\{ \begin{array}{lr} A = -\frac{f+n}{f-n}&\\ B = -\frac{2fn}{f-n}& \end{array} \right.
{A=−f−nf+nB=−f−n2fn 所以最终可推得透视投影矩阵
(
x
c
y
c
z
c
w
c
)
=
(
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
)
(
x
e
y
e
z
e
w
e
)
\left( \begin{matrix} x_c\\ y_c\\ z_c\\ w_c \end{matrix} \right)=\left( \begin{matrix} \frac{n}{r} & 0 & 0 & 0\\ 0 & \frac{n}{t} & 0 & 0\\ 0 & 0 & -\frac{f+n}{f-n} & -\frac{2fn}{f-n}\\ 0 & 0 & -1 & 0 \end{matrix} \right) \left( \begin{matrix} x_e\\ y_e\\ z_e\\ w_e \end{matrix} \right)
⎝⎜⎜⎛xcyczcwc⎠⎟⎟⎞=⎝⎜⎜⎛rn0000tn0000−f−nf+n−100−f−n2fn0⎠⎟⎟⎞⎝⎜⎜⎛xeyezewe⎠⎟⎟⎞
正交投影
相较于透视投影,正交投影就简单太多了,其转换过程与一般的坐标系变换并没有太大分别,这里就不展开谈了,直接上矩阵:
(
1
r
0
0
0
0
1
t
0
0
0
0
−
2
f
−
n
−
f
+
n
f
−
n
0
0
0
1
)
\left( \begin{matrix} \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{matrix} \right)
⎝⎜⎜⎛r10000t10000f−n−2000−f−nf+n1⎠⎟⎟⎞
视口矩阵
视口矩阵既是将NDC下的坐标转换为屏幕上的指定像素区域,也可以理解为该摄像机在屏幕上的显示区域。我这里就偷懒直接覆盖全屏幕了。
(
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
)
\left( \begin{matrix} \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{matrix} \right)
⎝⎜⎜⎛2width00002height0000102width2height01⎠⎟⎟⎞
总结
这一章中,我们介绍了三维空间中的坐标是如何一步步转换为屏幕上坐标的,其实这里结合上一章中的绘制三角形,我们就已经可以尝试去渲染3d模型了。所以,下一章则会介绍下最基本的渲染管线。