对数深度缓冲
在超大型场景下,远近裁剪平面一半要很远的距离,使用普通的深度缓冲经常会因为误差,造成一些很难看的锯齿,本来不相交的图元相互穿透,Z-fighting等现象。
普通的Z/W深度缓冲,深度值不是线性的。在近处,深度比较精确,所使用的深度区间也比较大。在远处,仅会使用很小一部分深度区间,误差就会变得很大,产生难看的现象。
例如,在近裁剪平面为10,远裁剪平面为10000时:
Z | Z/W深度 |
90.09009 | 0.900901 |
490.4905 | 0.980981 |
990.991 | 0.990991 |
10000 | 1.0 |
可见近处的所用到的区间大,90以内的就用了0-0.9,越远越小,精度越低。
一种办法就是将深度转化为线性的,在D3D中用Shader自己算一个深度。
由于Z最终会被除以W,所以,可以在VS中,完成WorldViewProj之后,先对Z上乘以W,等最后被除以W的时候,就相当于什么事情也没有发生。
当然,也要除以远裁剪平面距离。使深度值映射到0-1之间。也可以通过修改投影矩阵来完成。
线性的深度分布,会使远处和近处的精度一样。即传说中的W缓冲。在超大场景下,远处就没有那么多锯齿了。但是,这样有时候,近处的精度就会不足。
还有一种办法就是在VS中用对数计算深度。
公式z = log(C*z + 1) / log(C*Far + 1) * w
其中,C是常量,不同的值,会影响深度的精度。
在z处的分辨率为
log(C*Far + 1) / (2^n * C/(C*z+1))
一下是一个参考的精度表,使用24位深度缓冲,远裁剪平面10000km
1 | 10 | 100 | 1k | 10k | 100k | 1M | 10M | |
C=1 | 1.9e-6 | 1.1e-5 | 9.7e-5 | 0.001 | 0.01 | 0.096 | 0.96 | 9.6 |
C=0.001 | 0.0005 | 0.0005 | 0.0006 | 0.001 | 0.006 | 0.055 | 0.549 | 5.49 |
使用对数深度问题是深度是被线性插值的,而不是对数。所以网格必须足够精确……或者直接在PS中算深度,不是很费性能,如果可以接受的话……
这种办法效果非常好,做真实比例的星球系统没啥问题。
使用这些非标准的深度,虽然效果好,但也是有坏处的。
首先,普通Z/W深度,在屏幕空间是线性的,如图所示:
这是使用ddx和ddy对Z/W深度求的导数图。
由颜色相同可以看出,深度是线性的(在屏幕空间)。原始图片为无损png,可以用画图颜料桶测试颜色是否相同。
对线性深度使用相同办法,效果如图。
可见是非线性的。
这对于一些基于屏幕空间深度的Post Effect来说不利。例如SSAO,HDAO,景深等。