前言

本文是对OpenGL Projection Matrix一文的中文翻译,初衷是因为自己学习OpenGL时,对投影变形的数学推导比较感兴趣,因此找到了该文章。而本文并不是对该一对一的翻译,其中会增加一些易于理解的内容。

正文

计算机显示器是2D平面的。如果3D场景通过OpenGL来渲染,那么就需要以2D图像的方式投影到计算机屏幕上。其中GL_PROJECTION矩阵就是用作投影变换。首先它将所有的顶点数据从观察(View)坐标变换到裁剪坐标;然后裁剪坐标还需要变换为归一化坐标(NDC,Normalized Device Coordinates),具体的方法就是裁剪坐标除以其w分量(默认是4分量向量,即x,y,z,w)。

因此需要注意的是裁剪(视锥剔除)和NDC变换都已经集成在了GL_PROJECTION矩阵中。而下面的内容主要就是描述如何从 left, right, bottom, top, near 和 far 这个6个值来构建投影矩阵。下图标明了这六个值:

这里要注意一下的是对于视锥剔除(也就是裁剪)是在裁剪坐标执行(即除以w分量)之前进行。在裁剪坐标中x、y、z分量都会尝试和w分量进行比较,如果其小于-w(注意这里是负数)或者大于w(这里是正数)都会认为这些顶点是无效的。即:

然后OpenGl会在发生裁剪的地方重建多边形的边缘。

透视投影(Perspective Projection)

在透视投影中,处于视锥(上图左边,观察坐标)中的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矩阵时需要对它们进行取反。

因此为了求得观察坐标到归一化坐标的变换矩阵,大致需要两步:首先需要将观察坐标 投影到近平面上的点 。其次是将转换为

观察坐标到近平面的投影

在OpenGl中,观察空间中的3D坐标都会投影到近平面(near plane)中,下面两幅图展示了如何将观察空间中的某一个点 投影到近平面上的点

俯视图:

侧视图:

从视锥俯视图来看, 映射到 。通过相似三角形比例:

从侧视图来看,可以使用类似的方法求得:

也就是说,无论都是依赖于,即它们和相反。换句话说,它们都会除以。这是作为构造GL_PROJECTION矩阵非常重要的线索。在观察坐标在通过乘以GL_PROJECTION矩阵转换后,裁剪坐标依然是一个齐次坐标:

最后通过除以裁剪坐标的w分量变成归一化坐标(NDC):

因此我们可以设置裁剪坐标的w分量为,GL_PROJECTION矩阵的第4行就变为(0, 0, -1, 0),并有如下的等式成立:

下一步根据线性关系,我们将 映射到 (即NDC坐标):

的映射

映射到

然后用(r, 1) 来替换 (),也就是在直线上取的特殊点:

将β带入等式一得到:

同理我们可以将 映射到

的映射

最后得到的等式为:

然后我们将前面得到(即等式一)带入到等式三中,我们的目的是要推导出和的关系:

将前面得到(即等式二)带入到等式四中,同样也是要推导出和的关系:

上面我们为了进行透视划分,将等式的每一项都除以,在前面我们有。因此对于等式五和等式六我们可以将括号内的设为,具体如下:

有了上面的关系之后,我们就可以求得变换矩阵GL_PROJECTION的对应项(第一行,第二行)了:

的映射

现在我们仅仅只有GL_PROJECTION矩阵3行(第1、2、4行)。寻找(归一化坐标中的z值)相对于其他来说是有点不一样的,这是因为在观察坐标中的总是投影在-n的近平面(near plane)上面(回想一下上面的那几张图)。但我们需要唯一的z值用于裁剪和深度测试,我们可以使用逆变换进行反投影。

我们知道z并不依赖于x和y的值,我们可以借用w分量来找到之间的关系。因此我们可以对上面矩阵的第3行指定为如下形式:

我们可以得到下面的等式,等式的前半部分是我们前面提到的基础,对于归一化坐标而言,是用裁剪坐标除以其w分量:

由于,所以进一步替换上面的等式:

在观察空间中w分量为1,也即是,因此等式可以进一步化简为:

为了找到系数,A和B我们使用之间的关系:-n对应-1,即(-n,-1);-f对应1,即(-f,1)。让这两个特殊的值带入到上面的等式中得到:

得出,然后将该等式带入到中,得到:

透视投影矩阵

将A和B的值带入到矩阵中得到完整的透视投影矩阵:

这个矩阵是通用的视锥投影矩阵。如果说观察的是对称的话,即,上面的矩阵可以化简为:

同样的我们也可以根据等式七求出的值

在我们继续后面的内容之前,我们先仔细看看上面之间的关系。我们注意到它们是非线性关系的合理函数。这意味在近平面(near plane)上有着非常高的精度,但在远平面(far plane)上的精度却很低。如果[-n, -f]变大,那么就会导致深度(depth)的精度问题(z-fighting)。$z_{e}$在远平面附近小的变化不会影响到值(这里可以简单地将代入到上面等式中)。n和f之间的距离应尽可能地短,以最大程度地减小深度缓冲区的精度问题。

正交投影(Orthographic Projection)

构建GL_PROJECTION的正投影矩阵要比透视投影要简单许多,观察坐标的分量都是线性映射到归一化坐标中。因此我们只需要将长方形体积缩放到立方体中将其移动到原点即可。

现在我们可以根据线性关系求得GL_PROJECTION矩阵的各个元素。

的映射

映射到

然后用点(r, +1) 替换

将 β 代入到方程中得到:

的映射

同理我们也可以求出对应的函数表示:

然后用点(t, +1) 替换

将 β 代入到方程中得到:

的映射

这里需要注意的是它们都是取的负数。

将(-f, 1)替换

将 β 代入到方程中得到:

正交投影矩阵

对于正交投影来说,w分量不是必须的。我们将GL_PROJECTION的中第4行设置为(0,0,0,1)。因此GL_PROJECTION完整的正交投影矩阵为:

如果观察空间是对称的,即。我们可以进一步的简化:

将上的等式代入到原矩阵中,化简之后的矩阵为: