前言
本文是对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)。在远平面附近小的变化不会影响到值(这里可以简单地将代入到上面等式中)。n和f之间的距离应尽可能地短,以最大程度地减小深度缓冲区的精度问题。
正交投影(Orthographic Projection)
构建GL_PROJECTION
的正投影矩阵要比透视投影要简单许多,观察坐标的分量都是线性映射到归一化坐标中。因此我们只需要将长方形体积缩放到立方体中将其移动到原点即可。
现在我们可以根据线性关系求得GL_PROJECTION矩阵的各个元素。
到的映射
将映射到:
然后用点(r, +1) 替换 :
将 β 代入到方程中得到:
到的映射
同理我们也可以求出对应的函数表示:
然后用点(t, +1) 替换 :
将 β 代入到方程中得到:
到的映射
这里需要注意的是它们都是取的负数。
将(-f, 1)替换:
将 β 代入到方程中得到:
正交投影矩阵
对于正交投影来说,w分量不是必须的。我们将GL_PROJECTION的中第4行设置为(0,0,0,1)
。因此GL_PROJECTION完整的正交投影矩阵为:
如果观察空间是对称的,即。我们可以进一步的简化:
将上的等式代入到原矩阵中,化简之后的矩阵为: