前面渲染模型时候,没有考虑每个点的z坐标,这种方式叫做正交投影Orthographic projection ,模型看起来偏胖,因为我们平时在3d世界看到的物体都是近大远小的。透视投影Perspective projection 就是用近大远小的方式投影。
两种投影对比:
线性变换从几何直观有三个要点:
变换前是直线的,变换后依然是直线
直线比例保持不变
变换前是原点的,变换后依然是原点
说白了就是缩放、裁切和旋转, 不包括平移:可以看这个文章
平面上的线性变换都可以用一个二维矩阵计算:
$$
\begin{bmatrix}
a & b \\
c & d
\end{bmatrix}
\begin{bmatrix}
x \\ y
\end{bmatrix}
=
\begin{bmatrix}
ax + by \\
cx + dy
\end{bmatrix}
$$
说简单点就是线性变换加上平移,用矩阵计算:
$$
\begin{bmatrix}
a & b \\
c & d
\end{bmatrix}
\begin{bmatrix}
x \\ y
\end{bmatrix}
+
\begin{bmatrix}
e \\ f
\end{bmatrix}
=
\begin{bmatrix}
ax + by + e\\
cx + dy + f
\end{bmatrix}
$$
把2x2的变换矩阵加上一行一列,变成3x3,并且把等待变换的向量加上一个总是1的坐标:
$$
\begin{bmatrix}
a & b & e \\
c & d & f \\
0 & 0 & 1
\end{bmatrix}
\begin{bmatrix}
x \\ y \\ 1
\end{bmatrix}
=
\begin{bmatrix}
ax + by + e\\
cx + dy + f \\
1
\end{bmatrix}
$$
这样就实现了和仿射变换一样的效果!这个想法非常简单。平移在二维空间中不是线性的。所以我们将2D嵌入到3D空间中(通过简单地为第三个分量加1)。这意味着二维空间是三维空间中z=1的平面。然后我们执行一个3D线性变换,并将结果投影到我们的2D物理平面上。
将3d投射到2d只需要除以3d分量:
$$
\begin{bmatrix}
x \\
y \\
z
\end{bmatrix}
\rightarrow
\begin{bmatrix}
x/z \\
y/z
\end{bmatrix}
$$
如果z无限逼近0代表被投影后的点在无穷远处:
被投影的点 -> 投影到平面z=?的2d坐标
(x,y,1) -> (x,y)
(x,y,1/2) -> (2x,2y)
(x,y,1/4) -> (4x,4y)
可以看到,随着平面的下降,投影后的点越来越远,所以当z=0时,表示的是一个向量而不是3d空间中的一个点。
2d的仿射变换可以通过吧2d嵌入3d,转换成3d中的线性变换,再投影回2d。同样的道理:3d的仿射变换,可以通过吧3d嵌入4d,转换成4d中的线性变换,在投影回3d!
使用齐次坐标: 点(x,y,z) -> (x,y,z,1),用下面的矩阵试着把它在4d空间中进行变换:
$$
\begin{bmatrix}
1 & 0 & 0 & 0 \\
0 & 1 & 0 & 0 \\
0 & 0 & 1 & 0 \\
0 & 0 & r & 1
\end{bmatrix}
\begin{bmatrix}
x \\ y \\ z \\ 1
\end{bmatrix}
=
\begin{bmatrix}
x \\ y \\ z \\ rz+1
\end{bmatrix}
$$
再投影回3d:
$$
\begin{bmatrix}
x \\ y \\ z \\ rz+1
\end{bmatrix}
\rightarrow
\begin{bmatrix}
\frac{x}{rz+1} \\ \frac{y}{rz+1} \\ \frac{z}{rz+1}
\end{bmatrix}
$$
先把这个结果放一边。来看一个模拟现实中人眼将3d中一个点投影到平面上的例子:
有一个点P=(x,y,z),我们要把它投影到z=0的平面上,摄像机(也就是人的眼睛)在z轴上(0,0,c)的位置
根据初中还是高中的知识,三角形ABC和ODC是相似三角形,所以$\frac{AB}{AC}=\frac{OD}{OC}$,进而得出$\frac{x}{c-z}=\frac{x^{'}}{c}$
所以:
$$
x^{'}= \frac{x}{1-z/c}
$$
同理:
$$
y^{'}= \frac{y}{1-z/c}
$$
回到矩阵,让r=-1/c:
$$
\begin{bmatrix}
x \\ y \\ z \\ rz+1
\end{bmatrix}
\rightarrow
\begin{bmatrix}
\frac{x}{rz+1} \\ \frac{y}{rz+1} \\ \frac{z}{rz+1}
\end{bmatrix}
\rightarrow
\begin{bmatrix}
\frac{x}{1-z/c} \\ \frac{y}{1-z/c} \\ \frac{z}{1-z/c}
\end{bmatrix}
$$
如果我们想用位于z轴上距离原点为c的摄像机计算一个中心投影,分三步:
将3d嵌入到4d中
在4d中进行线性变换
投影回3d
$$
\begin{aligned}
\begin{bmatrix}
x \\ y \\ z
\end{bmatrix}
\rightarrow
\begin{bmatrix}
x \\ y \\ z \\ 1
\end{bmatrix}
&\qquad(1)\\
\begin{bmatrix}
1 & 0 & 0 & 0 \\
0 & 1 & 0 & 0 \\
0 & 0 & 1 & 0 \\
0 & 0 & -1/c & 1
\end{bmatrix}
\begin{bmatrix}
x \\ y \\ z \\ 1
\end{bmatrix}
=
\begin{bmatrix}
x \\ y \\ z \\ 1-z/c
\end{bmatrix}
&\qquad(2)\\
\begin{bmatrix}
x \\ y \\ z \\ 1-z/c
\end{bmatrix}
\rightarrow
\begin{bmatrix}
\frac{x}{1-z/c} \\ \frac{y}{1-z/c} \\ \frac{z}{1-z/c}
\end{bmatrix}
&\qquad(3)
\end{aligned}
$$
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 4d投影到3d
fn v4p2v3 ( v : glm ::Vec4 ) -> glm ::Vec3 {
glm ::vec3 ( v . x / v . w , v . y / v . w , v . z / v . w )
}
// ...
let camera : glm ::Vec3 = glm ::vec3 ( 0. , 0. , 3. );
// 投影变换矩阵,注意gml初始化一行是矩阵中的一列
let projection = glm ::mat4 (
1. , 0. , 0. , 0. ,
0. , 1. , 0. , 0. ,
0. , 0. , 1. , - 1. / camera . z ,
0. , 0. , 0. , 1. );
// ...
// 透视投影
let a = v4p2v3 ( projection * a . extend ( 1. ));
let b = v4p2v3 ( projection * b . extend ( 1. ));
let c = v4p2v3 ( projection * c . extend ( 1. ));
详细代码见这里076b31fc4ea69f00e2cee530e5e3e25445189b67