转载请注明出处为KlayGE游戏引擎,本文的永久链接为http://www.klayge.org/?p=1574

上一篇讲了在KlayGE 4.0中,Deferred Rendering的流水线改进。本篇继续讲G-Buffer的变化。

G-Buffer布局

前面提到了G-Buffer改成了MRT,那么现在就来比较一下新老G-Buffer的区别。老G-Buffer的安排如下:

Old G-Buffer

老G-Buffer是4个通道、每个通道都是fp16的RGBA16F格式。其中normal用Spheremap Transform的方式映射到2个通道;depth单独存一个通道;specular和shininess挤在一个通道内,整数部分为specular * 100,小数部分为shininess / 256.0f。

这样的G-Buffer需要占据64-bit,IO开销不小,而且depth精度有限。如果按照新的MRT G-Buffer扩展到2个RT,就需要再增加一个32-bit的RT。对于不支持Independent MRT的D3D9硬件来说,甚至要增加一个64-bit的RT,会很影响性能。

最直接的改进就是把depth去掉,同时把specular和shininess分散到两个通道去,就像这样:

New G-Buffer V1

这么一来,所有的分量都可以存在8-bit之内,2个RT仍用64-bit就能解决,并且空闲了一个通道!但是,由于normal的位数下降了非常多(从原来32-bit变成16-bit),效果也会受到很大影响。例如,原先(2个16-bit通道)的高光是这个样子的:

Normal 32-bit

改用2个8-bit通道就出现了很明显而且丑陋的梯度:

Normal 16-bit

所以说2个8-bit通道没有能力表现出光滑的normal过渡,得把剩余的一个通道用上才行。但需要注意的是,和传统Deferred Shader的G-Buffer不同在于,这种MRT G-Buffer的每个lighting pass只需要读取一次RT0,到了shading pass才读一次RT1。如果把lighting pass需要的信息放到了RT1,就会造成lighting pass的IO加倍,失去Deferred Lighting的有效加速。

因此,我只能作出一个艰难的决定:放弃基于物理的fresnel。原先把specular放在RT0的目的就是,在lighting pass可以用它来计算fresnel:

基于物理的fresnel需要specular颜色(这里简化成只有亮度了)、light方向和halfway方向,必须在lighting pass计算。最常见的近似是用view和normal来代替light和halfway,这样就可以在shading pass才计算fresnel,而且对于所有角度的光源产生的fresnel系数都相同。实际上,这个近似只有在高光的那一个点的地方是相同的,越往边缘去会越暗。但因为fresnel本身比较弱,这个差异可以被直接忽略。因为通道实在不够,在KlayGE 4.0中,我也不得不采用这个近似的、不基于物理的fresnel,得到新的G-Buffer布局如下:

New G-Buffer V2

specular被挪到了RT1的A通道,RT0的RGB通道就能都用来存放normal了。那么,在24-bit normal下渲染结果又如何呢?

Normal 24-bit

可以看到,效果比只用16-bit好了许多,但离32-bit的情况还是很有差距的。至少一眼就能看出来梯度的现象。在SIGGRAPH 2010上,Crytek有个讲座叫CryENGINE 3: reaching the speed of light。里面提到了出现这个现象的根本原因在于:normal是被normalize过的!24-bit一共能表达256x256x256 = 16777216个不同的值,但如果仅限于normalizied的,就剩下了大概289880个,仅占了1.73 %。它有效的位数只有17-bit,所以梯度的格子仅比16-bit的时候密了一倍。Crytek的best fit for normals方法能表达16482364个值,也就是98.2 %,提升了几乎两个数量级。用best fit调整过的normal平滑的多了:

Normal 24-bit with Best Fit

已经看不出和32-bit normal的区别了。关于best fit for normals的具体方法,可以参考Crytek的ppt。这里提供了一个我的程序预计算出来的纹理,用来查询最佳长度。

normals_fitting.7z

和Crytek的方法不同的是,我省掉了它所说的y/x变换,所以从normal计算纹理坐标的时候也得去掉vTexCoord.y /= vTexCoord.x一行。

现在,lighting pass和shading pass需要的信息都已经挤到了狭小的64-bit中,下一篇我会讨论一个所有deferred框架都会面临的大问题:透明物体