OpenGL学习脚印: 投影矩阵的推导
写在前面
本节内容翻译和整理自http://www.songho.ca songho的博客《OpenGL Projection Matrix》内容,以供自己和初学者熟悉投影矩阵推导过程。
通过本节,你可以了解到:
- 投影矩阵计算的阶段
- 透视投影和正交投影的矩阵推导
1.概览
计算机屏幕是2维的,OpenGL渲染的3D场景必须以2D形式的图像投影到屏幕上。GL_PROJECTION 矩阵就是用来设置投影变换的。首先,它将所有顶点从眼坐标(照相机坐标)转换到裁剪坐标系下。然后,这些裁剪坐标通过透视除法,即除以裁剪坐标中w分量,转换到归一化设备坐标系(NDC)。
一个由视锥裁剪的三角形
因此,我们要记住,裁剪(视锥剔除frustum culling)和NDC转换都集成到了GL_PROJECTION 矩阵。接下来的部分描述了怎么样通过left, right, bottom, top, near and far 这6个界限参数来构造投影矩阵。
注意:
视锥剔除是在裁剪坐标系中进行的,并且恰好在透视除法之前进行。裁剪坐标xc, yc 和 zc 通过与wc比较来进行测试。 如果某个坐标值比Wc小或者比Wc大,那么这个顶点将被丢弃。然后,OpenGL会重新在裁剪进行的地方构造多边形的边缘。
补充内容:
实际上,眼坐标系下坐标在乘以投影矩阵后,裁剪测试和透视除法都是由GPU来执行的。而后面这两个过程处理的裁剪坐标系数据都是由投影矩阵变换的。
1. 裁剪测试也即视锥剔除
-Wc < Xc,Yc,Zc < Wc
2. NDC透视除法
Xn = Xc / Wc Yn = Yc / Wc Zn = Zc / Wc
这里需要注意的是,我们在构造16个参数的投影矩阵的同时,不仅要考虑到裁剪,还要考虑到透视除法的过程。这样,最终的NDC坐标才会满足:
-1 < Xn,Yn,Zn < 1
2.透视投影
在透视投影中,在眼坐标下截头椎体(a truncated pyramid frustum)内的3D点被映射到NDC下一个立方体中;x坐标从[l,r]映射到[-1,1],y坐标从[b,t]映射到[-1,1],z坐标从[n,f]映射到[-1,1]。
注意:
眼坐标系使用右手坐标系,而NDC使用左手坐标系。这就是说,眼坐标系下,在原点处的照相机朝着-Z轴看去,但是在NDC中它朝着+Z轴看去。因为glFrustum() 仅接受正的near和far距离,我们在构造GL_PROJECTION 矩阵时,需要取其相反数。眼坐标系和NDC坐标系如下图所示:
在OpenGL中,眼坐标下3D点被投影到近裁剪面(即投影平面)。下图展示了眼坐标系下点(xe, ye, ze) 如何投影到近裁剪面上的 (xp, yp, zp) 的。(左侧是视锥的俯视图,右侧是视锥的侧视图,拿出右手构成右手坐标系,然后比划比划就出来了)
根据三角形的相似性,由俯视图可得出:
由侧视图可以得出:
补充: xp 和yp其实是一个中间值,我们要找的是(Xc, Yc, Zc)和 (Xn, Yn, Zn)之间的关系,但是可以利用:
这一关系做过渡,后面利用xp和yp,映射到NDC中xn和yn的线性关系就利用到了 xp 和yp。这一点很重要。
注意,这里 xp 和yp 都依赖于ze,他们与 -ze成反比。换句话说,他们都被 -ze相除。这个事构造GL_PROJECTION 矩阵最初的线索。在眼坐标通过乘以 GL_PROJECTION 来转换时,裁剪坐标系仍然是一个齐次坐标系。通过对裁剪坐标进行透视除法得到最终的NDC坐标。下图解释了这个过程:
因此我们可以把裁剪坐标系下的w分量设为-ze,那么GL_PROJECTION矩阵第4行变为(0, 0, -1, 0),如下图(求出了投影矩阵第4行):
下现在我们把xp和yp,映射到NDC中xn和yn,他们之间是线性关系: [l, r] ? [-1, 1]和[b, t] ? [-1, 1].
线性关系如下图所示:
则可以推导出:
细节部分有删节,这个推导过程使用的就是简单的y=kx+b线性关系推导,同理利用[b, t] ? [-1, 1]可推得:
将上面的 xp 和yp带入求得:
注意这里Xn和Yn已经是NDC中的坐标了,通过这两个坐标可以求出GL_PROJECTION 的前两行来,书写如下(求出了投影矩阵第1,2,4行):
现在怎么求出第3行呢?
找出zn与找出xn和yn不同,因为 ze总是被投影近裁剪面-n上。但是我们需要唯一的Z值进行裁剪和深度测试。另外,我们还能够unproject即反向变换(inverse transform)。因为Z值不依赖于x或者y,因此我们借用w分量来找出 zn 和 ze之间的关系。
因此我们可以这样指定第3行:
在眼坐标下We等于1,因此上式变为:
我们使用(ze, zn)的关系(-n, -1)和 (-f, 1)来求解出系数A,B;
细节部分有删节,使用消元法即可求出:
我们求出了A和B,那么ze和zn关系如下式:
最终的投影矩阵如下式:
这个公式对应的是一般的视锥,如果视锥是对称的,即r = -l ,t= -b,那么有:
z-fighting
在继续之前,我们来看看表示ze和zn关系的式3.这是一个有理函数,并且ze和zn之间不是线性关系。这意味着,在近裁剪面的精度很高,而远裁剪面则很小。如果[-n, -f]范围变得大写,就会引起深度精度问题,即z-fighting。在远裁剪面附近,ze的小变化根本不影响zn值。近裁剪面和远裁剪面之间的n和f举例,应该尽可能小,来减少深度缓存的精度问题。可参考下图来帮助理解。
3.正交投影
构造正交投影的矩阵简单很多。所有的是眼坐标下xe, ye 和ze,都被线性的映射到NDC中。我们需要做的就是讲长方体视景体缩放为规范视见体,然后移动到原点。如下图所示:
以xe和xn之间映射关系为例,[l,r]=>[-1,1],则可以推导如下:
y,z也有类似推导,这里省略,最后得出投影矩阵为:
如果视锥是对称的话,即r = -l ,t= -b的话,则可以简化为:
到这里透视投影和正交投影矩阵推导完毕。