OpenGL中,有一个函数叫frustum,字面的意思是截锥体,也就是一个去掉头部的锥体,如下图所示,
看了一下《计算机图形学》(英文名Computer Graphics with OpenGL)的透视投影推导过程,比较全面,各种情况都有描述。不过最近又参考了网上的一些资料,发现这里【1】的推导过程比较单纯直接。我们看一下,
注意到上面这个图,观察者的位置相对于(0,0,0)这个点是在右边的,也就目光是沿Z轴方向(所以距离 眼睛越近,显得越大嘛),OpenGL的camera也就是(0,0,0)点,沿-Z方向。
把xe映射到xp,ye映射到yp是这样的,
式(1)
通过Mprojection矩阵,把eye坐标投影到NDC空间(Normalized Device Coordinates, 归一化设备坐标系),
注意下上面的ndc和clip坐标系之间,只差了一个系数 wclip,他们都是3维齐次坐标【2】。下面这个方程中,c就是clip,变换矩阵的第4行设置为(0, 0, -1, 0),为什么呢?因为这一行实际上只处理系数wc,看前面我们知道xp与yp分别和(-Ze)成反比,所以这里我们也期望,wc和(-ze)成反比,且最后能归一化wc/(-ze)=1。
接下来的一步,就是完成xp,yp到xn,yn的归一化映射:[l, r] ⇒ [-1, 1] and [b, t] ⇒ [-1, 1],首先来看映射[l, r] ⇒ [-1, 1],如下图
这是一个非常简单的线性映射,写成方程式就是,
其中就是xp=0时在xn轴上的截距,根据映射,当xp=r时,xn=1,代入到该式中,就可以得到,
然后变换一下格式,
现在把代回最上面那个式子,就得到了,
式(2)
同样的道理,可以得到
式(3)
把式(2),式(3)代回到式(1),就得到这样一个结果,
这两个结果,结合我们前面得到的wc=-ze,整个矩阵就可以等价地写成下面的形式,
现在只有矩阵的第3行是未知的,我们这样处理,
因为在eye的空间,we=1(w是一个和透视距离有关的量,0表示无穷远点,参考【2】),所以,
,和上面的道理类似,映射 (ze, zn)满足范围为近点=(-n, -1),远点= (-f, 1),
求解该线性方程组,得到,
这样,得到
式(4)
最后得到的矩阵就可以写成
这个矩阵,也正是我们frustum函数变换所采用的矩阵。
这里也顺便提一下Z-finghting。根据式(4),Zn和Ze的关系为
因此,当[-n, -f]的距离比较大的时候,因为有理数取值都有误差,在远点就容易形成z-finghting,也就是当两个实体在远点有交接时,因为误差产生的距离抖动,无法准确得到交接线从而产生类似下图的结果,
本文结束。
参考
【1】http://www.songho.ca/opengl/gl_projectionmatrix.html
【3】https://en.wikipedia.org/wiki/Z-fighting