最近在shadow map过程中卡住了。因为我想在OpenGL ES 2.0兼容的平台上运行shadow map,而要顺利运行shadow map,通常的情况是拥有GL_OES_depth_texture扩展,而有些兼容的机器是没有这个扩展,这给我们开发人员带来了一些难度。不过办法还是有的,前提是要对计算机图形学的Z-Buffer要有一个深入的了解才行。
蒋彩阳原创文章,首发地址:http://blog.csdn.net/gamesdev/article/details/44939923。欢迎同行前来探讨。
参考维基百科上对Z-Buffer的介绍(这里第一个公式很可能是错误的,感觉是2far·near而不是-2far·near),当场景中的顶点在做MVP变换的时候,它的Z值也会受到相应的变化。这里我们尤其关注的是在视角空间下的顶点位置(Xe, Ye, Ze),在经过投影矩阵的变换时候会发生什么变化。
首先是根据投影矩阵,对顶点进行投影变换:
然后将齐次坐标转换成为普通坐标,即同除以-Ze进行规格化,让第四个分量为1:
在NDC(Normalized Device Coordinates)坐标系转化为屏幕坐标系的过程中,Z值也要被线性插值。此时的Zndc∈[-1,1]的,首先需要插值到[0,1]中,然后根据深度的位数(一般是16、24和32位,几乎不存在8位的深度了),来平均映射过去。
这时有:Zw=S·[(Zndc/2)+0.5]
其中S=2n-1,n是系统设定的深度位数。
即
以Ze作为Zw的函数,并化简,则为:
我们可以大致地看到,Ze和Zw是成反比的(和1/Zw成正比)。为了详细了解相关情况,我们对它求导数:
则
计算后得到
我们可以很快看出,当
时,取得最小值。这也就说明无限多的相机空间的距离只影响几近于0的Z缓存,换句话说,一个位的Z缓存变化就要反映无限长的相机空间距离变化。
那么这个最小值点能否取到呢?由于计算机的Z缓存的深度变化是Zw∈[0,2n-1],那么一定满足
这里左边的不等式满足的充分必要条件是near<far,这是肯定的。而右边不等式满足的充分必要条件是
这要求near和far异号,但是我们通常是不会设置near和far异号的。所以右边的不等式不满足。因此
在Zw∈[0,2n-1]上是单调递增的,而Ze在Zw∈[0,2n-1]上是单调递减的。所以这反映出,Z缓存中将更多的精度(二进制位数)给了距离摄像机(或者zNear)较近的物体,而远处(或者zFar较近)的物体,留给的Z缓存精度很少。所以Z缓存与zNear于zFar的远近精度关系并不是线性的。这要求我们将更多的物体放在近平面处,远处的物体,可以使用LOD手法将其忽略显示,避免出现z-fighting。同时由于这样的特性,shadow map在不同远近出也会呈现分辨率不一致的情况。这就要求我们使用Cascade Shadow Map(CSM)这样的技术来解决问题了。
参考文献:
OpenGL FAQ:https://www.opengl.org/archives/resources/faq/technical/depthbuffer.htm
深入探索透视矩阵:http://blog.csdn.net/popy007/article/details/1797121
Z-Buffering on Wikipedia:http://en.wikipedia.org/wiki/Z-buffering#Mathematics
疑问:
OpenGL FAQ里面的
d(ze / we) / d zw = - f * (f-n) * (1/s) / n
= -f * (f/n-1) / s
可能是错误的,应该加上一对括号。成为
= -f * (f/(n-1)) / s