在学习OpenGL的时候,或者在使用其他的3D引擎的时候总会遇到投影矩阵。这里就对投影矩阵进行推导 这里只推导透视投影,如果会了透视投影矩阵的推导平行投影矩阵其实也是一样的。
坐标系
首先是坐标系。坐标系分为左手坐标系,右手坐标系: 记忆的话很简单: 首先要记住每个手指👋对应的坐标轴:拇指——x,食指——y,中指——z。 然后将三个手指互相垂直形成三个坐标轴互相垂直的情况。 然后拇指指向右边。如果是左手坐标系就用左手,右手坐标系就用右手。这样就可以得到左右手坐标系了。
投影矩阵概念
之所以需要投影矩阵,是因为屏幕本身是2D的,我们需要将3D中的点投影到屏幕中,也就是将3D投影到2D中。
透视投影是根据人的视觉成像来设计的:
而平行投影(或者称为正投影)的投影线是直的:
在游戏编程中为了模拟人眼看到的,需要用到透视投影。
透视投影推导
首先来看一下透视投影的概念图: 这里后面的那个锥体就是视锥体,紫色的被称为近平面,蓝色的被称为远平面。在视锥体中的所有点都会被投影到近平面中,也就是会产生这样的映射:
这里保留了z轴的映射,为了OpenGL或者我们自己进行深度测试。
那么要如何做到呢?推导如下: 首先定义一些值:
- 近平面到原点的距离是n
- 远平面到原点的距离是f 然后要定义近平面的各个点的坐标:
那么这样就OK了,需要注意的是这里的$l,r,t,b$都是距离,也就是说都是正的(所以坐标上面有负数)
然后就是公式推导了:
首先我们需要计算在视锥体内的$p$点在近平面上的投影$p^{'}$的坐标。这很简单:
首先已经确定了$p^{'}$的$z$坐标是$n$,然后使用相似三角形可以得到:
$$
\frac{n}{z}=\frac{x^{'}}{x}=\frac{y^{'}}{y}
$$
$$
\begin{cases}
x^{'}=\frac{n}{z}x \\
y^{'}=\frac{n}{z}y
\end{cases}
$$
然后我们需要将$x^{'},y^{'}$的值映射到[-1,1]
上,由于$p^{'}$在近平面上,所以有这样的不等式:
$$
\begin{cases}
l\leq x^{'}\leq t \\
b\leq y^{'}\leq t
\end{cases}
$$
然后变换一下: $$ \begin{cases} l\leq x^{'} \leq r \\ b\leq y^{'} \leq t \end{cases} \Rightarrow \begin{cases} 0\leq x^{'}-l \leq r-l \\ 0\leq y^{'}-b \leq t-b \end{cases} \Rightarrow \begin{cases} 0\leq \frac{1}{r-l}x^{'} \leq 1 \\ 0\leq \frac{1}{t-b}y^{'} \leq 1 \end{cases} \Rightarrow \begin{cases} 0\leq 2\frac{1}{r-l}x^{'} \leq 2 \\ 0\leq 2\frac{1}{t-b}y^{1} \leq 2 \end{cases} \Rightarrow \begin{cases} -1\leq 2\frac{1}{r-l}-1x^{'} \leq 1 \\ -1\leq 2\frac{1}{t-b}-1y^{'} \leq 1 \end{cases} \Rightarrow $$ $$ \begin{cases} -1\leq \frac{2nx}{z(r-l)}-\frac{l+r}{r-l} \leq 1 \\ -1\leq \frac{2ny}{z(t-b)}-\frac{t+b}{t-b} \leq 1 \end{cases} \Rightarrow \begin{cases} -1\leq [\frac{2n}{r-l}x - \frac{l+r}{r-l}z]/z \leq 1 \\ -1\leq [\frac{2n}{t-b}y - \frac{t+b}{t-b}z]/z \leq 1 \end{cases} $$ 这里最后两步的变换是将上面使用相似三角形得到的等式放入得到的。 最后上面的公式可以得到: $$ \begin{cases} x^{''}=(\frac{2n}{r-l}x - \frac{l+r}{r-l}z)/z \\ y^{''}=(\frac{2n}{t-b}y - \frac{t+b}{t-b}z)/z \end{cases} (result1) \Rightarrow \begin{cases} zx^{''}=\frac{2n}{r-l}x - \frac{l+r}{r-l}z \\ zy^{''}=\frac{2n}{t-b}y - \frac{t+b}{t-b}z \end{cases} (result2) $$ 这里$x^{''},y^{''}$是最后映射到立方体上的点的坐标。
现在我们得到了最后映射点的$x^{''},y^{''}$坐标,还差$z^{''}$坐标。因为原本的$z$坐标和$x,y$是无关的(废话,$z$的值本身等于$n$啊),所以我们认为最后映射点的$z^{''}$坐标也和$x^{''},y^{''}$无关,那么通过$x^{''},y^{''}$的式子,我们推测出应该有如下等式: $$ z^{''}z=pz+q ^* $$ 如何解这个式子中的$z^{''}$呢?我们可以将近平面的距离$n$和远平面距离$f$代入求的,因为我们知道最后是将视锥体映射到单位正方体上,所以很显然$n$最后会映射到$1$处,$f$会映射到$-1$处,那么我们就有条件: $$ \begin{cases} z = n \\ z^{''} = 1 \end{cases} , \begin{cases} z = f \\ z^{''} = -1 \end{cases} $$ 那么代入上述式子$(*)$可以解得方程的结果为: $$ \begin{cases} p = \frac{n+f}{n-f} \\ q = -2\frac{2nf}{n-f} \end{cases} $$ 那么我们的$z^{''}$也就知道了: $$ \begin{cases} z^{''} = \frac{n+f}{n-f}z - \frac{2nf}{n-f} \end{cases} $$ 需要注意的是:这里的n,f也是距离,所以这里n,f前面要加负号(他俩在z轴负方向上): $$ zz^{''} = -\frac{f+n}{f-n}+\frac{2nf}{n-f} $$
那么综上: $$ \begin{cases} zx^{''}=\frac{2n}{r-l}x - \frac{l+r}{r-l}z \\ zy^{''}=\frac{2n}{t-b}y - \frac{t+b}{t-b}z \\ zz^{''} = -\frac{f+n}{f-n}+\frac{2nf}{n-f} \end{cases} $$ 这是一个线性方程组,完全可以放入矩阵中: $$ \begin{bmatrix} \frac{2n}{r-l} & 0 & \frac{l+r}{l-r} & 0 \\ \frac{2n}{t-b} & 0 & \frac{t+b}{t-b} & 0 \\ 0 & 0 & \frac{f+n}{n-f} & \frac{2nf}{n-f} \\ 0 & 0 & 1 & 0 \end{bmatrix} $$ 这里可能有人会问为什么最后一行第三个元素是1,因为你前面的线性方程组中左边的未知数不是$x^{''},y^{''},z^{''}$而是$zx^{''},zy^{''},zz^{''}$,所以要多乘上一个$z$来保持原式,这个1就是这个作用。
这里还有一个问题:不是说要将三个坐标映射到[-1,1]上吗?你上面那个结果等式左边可是$zx^{''},zy^{''},zz^{''}$,这怎么映射到[-1,1]上?。其实这一步是为了OpenGL准备的。OpenGL在将点左乘完透视投影矩阵之后,会自己再将生成点的坐标除以$z$,这一步叫做透视除法
。当然如果你是自己写游戏引擎,不想要透视除法的话完全可以使用result1公式,将里面的$z$变为$n$,然后继续推导。
这里还需要注意一次**$b,l$是距离,前面要加上负号**,所以最后的矩阵为: $$ \begin{bmatrix} \frac{n}{r} & 0 & 0 & 0 \\ 0 & \frac{n}{t} & 0 & 0 \\ 0 & 0 & \frac{f+n}{n-f} & \frac{2nf}{n-f} \\ 0 & 0 & -1 & 0 \end{bmatrix} $$ (这里最后一行变为-1是因为整个视锥体在z轴的负半轴上,除的z值是负数(也即是除以-n))
透视矩阵的其他表示形式
除了使用$l,r,b,t,n,f$参数表示透视矩阵以外,一般的游戏引擎还会使用俯仰角fov,近平面距离n,远平面距离f,宽高比pro四个参数来计算投影矩阵。这也很简单: $$ \frac{h}{2} = n\tan{\frac{fov}{2}} , w=pro*h=2npro\tan{\frac{fov}{2}} \Rightarrow t = \frac{h}{2}=n\tan{\frac{fov}{2}} , r = \frac{w}{2}=n*pro*\tan{\frac{fov}{2}} $$ 代入式子中就可以得到矩阵为: $$ \begin{bmatrix} \frac{\cot{\frac{fov}{2}}}{pro} & 0 & 0 & 0 \\ 0 & \cot{\frac{fov}{2}} & 0 & 0 \\ 0 & 0 & \frac{f+n}{n-f} & \frac{2nf}{n-f} \\ 0 & 0 & -1 & 0 \end{bmatrix} $$
其实投影矩阵的表示形式很多,主要还是要学会推导