投影矩阵之十万个为什么

part1. 关于概念

什么是投影矩阵?
投影矩阵是一个4x4齐次矩阵,将点从观察空间(viewing/eye/camera space)变换到裁剪空间(cliping space)(或者理解成空间的变换,这两者本质是一样的)

正交投影与透视投影的区别?
由视锥体(frustum)变换到裁剪空间。正交投影是由一个cube变换到裁剪空间。我们这里不讨论正交投影。

什么是裁剪空间?
一个四维的齐次空间,从几何上来看还是个视锥,但请不要用看待普通三维空间的方法去看这个空间!它具有一定的拓扑性质。对于透视投影来说,该空间下的点,其w分量有着特殊的意义(其实就是观察空间中的点的深度值$Z_e$,一会儿我们会提到)。

为什么叫裁剪空间?怎么裁剪

因为从视锥体映射到NDC,即x/w,y/w,z/w的值都从原来所在的观察空间被映射到了一个边长为2中心点位于原点的立方体中。所以此时x/w<1 。在divide by w之前,我们可以根据w值直接进行剔除。即$abs(x)<w$,$abs(y)<w$,$abs(z)<w$,不满足条件的点discard,并在裁剪边界生成新的顶点,更新索引,插值得到新顶点attribute的信息(如uv normal color)。但其实这样很慢。据说现代引擎不这样裁剪,而是细分离屏幕近的三角形,然后直接discard三角形。

刚刚提到的divide by w是什么?
顾名思义,裁剪空间下的点(x,y,z,w)除以其w分量就能得到其在NDC下的坐标,我们称这步为透视除法,或者divide by w。

NDC是什么
Normalized device coordinates(NDC), 一个立方体where$x\in[-1,1],y\in[-1,1],z\in[-1,1]$

为什么裁剪空间下的点除以其w分量就能得到在NDC下的坐标?
这个是设计上的原因,也正是齐次坐标系的定义与性质。详见part3

NDC是线性空间么?为什么?
不是的。其z分量与$1/W_{clip}$(1/Ze)成线性关系。观察空间是最后的线性空间。详见part3

GL与DX中投影矩阵的区别?
GL中变换到[-1,1](摄像机z=-1),而DX中变换到[0,1](可以理解为只用了NDC的一半,摄像机z=0),于是导致了系数是两倍的关系,另外因为dx使用左手系而gl右手系,而NDC统一为左手系。gl中的z变换会比dx多个符号。详见下面矩阵的推导。
有趣的是,因为dx使用行矩阵表示向量,并使用列优先存储矩阵,GL使用列矩阵表示向量并使用行优先存储矩阵,导致虽然完成同一个变换的矩阵在数学表示为互为转置,但在GL与DX的矩阵存储中,元素的顺序却是完全一样的。

什么是齐次矩阵(homogeneous matrix)?为什么要引入齐次矩阵?/ 什么是仿射变换?如何理解?
齐次矩阵或者齐次空间,就是原来n维矩阵拓展到n+1维。这里的齐次指的是(x,y,1)与(2x,2y,2)都能表示(x,y)的这种性质。仿射变换是高纬度的线性变换,或者说是低纬度平移与其他线性变换的组合。当我们添加了一个维度,我们就可以在高纬度中使用线性变换来表示低维度中非线性的变换(说的就是你,平移)。使用齐次矩阵,有两个好处(或者说不得不使用的原因),

首先是为了能在一个四维空间中将平移表示为线性(即可以写出矩阵乘法的形式u = Tv)。本质是为了方便计算,没有平移这种非线性变换(u = Tv + w),就没有加法,就可以使用矩阵的连乘从头乘到尾。这样一个4x4的齐次矩阵就能够表示三维空间下的一个任意的变换了。

第二个原因是,其次坐标的w分量可以表示向量与点的区别,w=0为向量,w!=0为点。大家可以思考向量与点加法或乘法的排列组合,其得到的结果也是正确的。并且我们认为,在齐次坐标下,(x,y,z,w)与(x/w,y/w,z/w,1)是同一个点(这便是divide by w的原理,详见part3)。齐次坐标下的点在z=infinity处翻转,(0,0,0,0)无意义。

为什么高纬度的仿射变换能表示低纬度的线性加平移变换?
https://en.wikipedia.org/wiki/Affine_transformation 这里有不错的动图方便理解。
https://www.zhihu.com/question/20666664/answer/157400568 这里有不错的讲解。

part2. 关于那些让人头大的左左右右

左手系与右手系的区别?
使用左手定则与右手定则建系的区别。对于固定的x轴y轴,这两个系的z轴相反。

叉乘方向收到左右手系的影响么?
右手系使用右手螺旋定则,左手系使用左手螺旋定则。其实数学运算上的结果一样,cross(x,y) = z。

行矩阵表示向量,列矩阵表示向量与坐标系的选择的关系?
没有关系。你可以选择任意的坐标系配合任意的矩阵向量表示法。

行矩阵表示向量、列矩阵表示向量与左乘、右乘的关系?
使用行向量一律搭配右乘,行向量在矩阵乘法的最左边。其右乘矩阵的列用来表示向量(或者说构成线性空间,构成一个线性变换等等)。列向量一律搭配左乘,行向量在矩阵乘法的最右边,其左乘矩阵的行用来表示向量。这是由线性代数的法则决定的。一个简单的例子:使用矩阵乘法计算向量dot,我们希望得到一个数。在左乘与列向量中是$V^TV$而在右乘与行向量中是$UU^T$,其中$V=U^T$。
表示同一个变换的矩阵(3x3线性也好,4x4仿射也好),在这两个不同的模式下,对应的两个矩阵互为转置。

part3. 关于矩阵的推导

投影矩阵是怎么来的?能带着我推一下么?
终于到了本文的重点了。其实有不少的思路可以推到这个矩阵,但是万变不离其宗,

$$
\begin{bmatrix}
X_{clip}\\
Y_{clip} \\
Z_{clip} \\
W_{clip}
\end{bmatrix}=M_{projection}
\begin{bmatrix}
X_{eye} \\
Y_{eye} \\
Z_{eye} \\
1
\end{bmatrix}
$$

首先我们把视锥压到一个方盒子,有

$X_{o}= \frac{nX_{eye}}{-Z_{eye}}$

$Y_{o}= \frac{nY_{eye}}{-Z_{eye}}$

然后再从这个方盒子映射到NDC,x由[l,r]映射到[-1,1], y由[t,b]映射到[-1,1]。

显然这是一个线性变换。

$X_{ndc}=-1 + \frac{(1-(-1))(X_o-l)}{r-l}$

带入上述的式子,化简有

$X_{ndc} = \frac{\frac{2n}{r-l}\cdot X_{eye}+\frac{r+l}{r-l}\cdot X_{eye}}{-Z_{eye}}$

同理,有

$Y_{ndc} = \frac{\frac{2n}{r-l}\cdot Y_{eye}+\frac{r+l}{r-l}\cdot Y_{eye}}{-Z_{eye}}$

我们知道裁剪空间下除以w分量就能得到ndc。这步也称为divide by w

$$
\begin{bmatrix}
X_{ndc}\\
Y_{ndc} \\
Z_{ndc} \\
\end{bmatrix}=
\begin{bmatrix}
X_{clip}/ W_{clip}\\
Y_{clip}/ W_{clip}\\
Z_{clip}/ W_{clip} \\
\end{bmatrix}
$$

我们观察到$X_{ndc} = \frac{\frac{2n}{r-l}\cdot X_{eye}+\frac{r+l}{r-l}\cdot X_{eye}}{-Z_{eye}}$, 于是我们不妨就设分母为$W_{clip}=-Z_{eye}$, 分子为$\frac{2n}{r-l}\cdot X_{eye}+\frac{r+l}{r-l}\cdot X_{eye}$,这样就能根据线性运算的关系写出矩阵的第一,三,四行了

$$
\begin{bmatrix}
X_{clip}\\
Y_{clip} \\
Z_{clip} \\
W_{clip}
\end{bmatrix}=
\begin{bmatrix}
\frac{2n}{r-l} & 0 & \frac{r+l}{r-l} & 0\\
0 & \frac{2n}{t-b} & \frac{t+b}{t-b} & 0 \\
? & ? & ? & ?\\
0 & 0 & -1 & 0
\end{bmatrix}
\begin{bmatrix}
X_{eye} \\
Y_{eye} \\
Z_{eye} \\
1
\end{bmatrix}
$$

又加上我们知道$Z_{clip}$的值是与$X_{eye}$,$Y_{eye}$无关的(物体的xy并不会影响到它的深度),所以我们可以设第三行为如下:

$$
\begin{bmatrix}
X_{clip}\\
Y_{clip} \\
Z_{clip} \\
W_{clip}
\end{bmatrix}=
\begin{bmatrix}
\frac{2n}{r-l} & 0 & \frac{r+l}{r-l} & 0\\
0 & \frac{2n}{t-b} & \frac{t+b}{t-b} & 0 \\
0 & 0 & A & B\\
0 & 0 & -1 & 0
\end{bmatrix}
\begin{bmatrix}
X_{eye} \\
Y_{eye} \\
Z_{eye} \\
1
\end{bmatrix}
$$

根据矩阵的运算法则,有

$Z_{ndc} = \frac{AZ_{eye}+B}{-Z_{eye}}$

再加上我们知道对于z值来说,从eye空间到ndc实际上是由[n,f]映射到了[-1,1]

$$
\begin{cases}
\frac{-An+B}{n}=-1\\
\frac{-Af+B}{f}=1
\end{cases}
$$

联立解得:

$$
\begin{cases}
A=-\frac{f+n}{f-n}\\
B=-\frac{2fn}{f-n}
\end{cases}
$$

于是我们终于得到了这个投影矩阵

$$
M_{projection}=
\begin{bmatrix}
\frac{2n}{r-l} & 0 & \frac{r+l}{r-l} & 0\\
0 & \frac{2n}{t-b} & \frac{t+b}{t-b} & 0 \\
0 & 0 & -\frac{f+n}{f-n} &-\frac{2fn}{f-n}\\
0 & 0 & -1 & 0
\end{bmatrix}
$$

一般情况下,视锥的左右和上下边界是等距的,即$r=-l,t=-b$

于是上面的矩阵可以简化成

$$
M_{projection}=
\begin{bmatrix}
\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{bmatrix}
$$

这便是OpenGL中的投影矩阵了。

上面那是OpenGL的啊,能推一下Dx的么?
好的没问题。注意DirectX和OpenGL有三个区别:

  1. 左右手系不同,所以$W_{clip}=+Z_{eye}$
  2. NDC的z取值范围不同,DirectX只用一般z,映射到[0,1]而不是[-1,1]
  3. 行向量列向量与左乘右乘的区别,在书写时,DirectX的投影矩阵应为OpenGL的转置。

推导过程和刚刚一模一样,注意区别就行了,最终的结果为

$$
M_{DirectX}=
\begin{bmatrix}
\frac{n}{r} & 0 & 0 & 0\\
0 & \frac{n}{t} & 0 & 0 \\
0 & 0 & \frac{f}{f-n} &-\frac{2fn}{f-n}\\
0 & 0 & 1 & 0
\end{bmatrix}^T
$$

为什么说观察空间是最后的线性空间?NDC中还能使用线性插值么?
在搞清楚这个之前,我们先要知道什么是线性插值。

什么是线性插值?
如果说对于给定的点v0,v1,对于给定的参数t,vt的值能表示为如下的形式:

$v_t = v_0(1-t) + v_1t$

那么我们称v满足关于参数t的线性插值。

我们回过头来,我们知道

$Z_{ndc} = \frac{AZ_{eye}+B}{-Z_{eye}}$

为什么我看不懂我的草稿了。。[To be fixed]

那么如何在NDC中插值呢?
需要做一个校正,很简单,关于属性插值有机会我专门写一篇。

如何避免因非线性映射所导致的精度问题?
1.对于摄像机来说,使用一个较小znear到zfar的范围
2.映射到$[1,0]$而不是$[0,1]$(由浮点数的存储方式所决定的)

我还有别的问题
请在评论区留言。

reference:
http://www.songho.ca/opengl/gl_projectionmatrix.html
Jim Blinn’s Corner: A Trip Down the Graphics Pipeline
Riesenfeld. (1981). Homogeneous Coordinates and Projective Planes in Computer Graphics. IEEE Computer Graphics and Applications, 1(1), 50–55.

投影矩阵之十万个为什么

https://matrix4f.com/Math/projection-matrix/

Author

oxine

Posted on

2020-04-21

Updated on

2020-05-22

Licensed under

Comments

昵称处填入QQ号,自动同步QQ头像与ID