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

KlayGE中的延迟渲染(一)里,我们推出了lighting pass里的计算,本篇将讲解G-Buffer阶段和Shading pass阶段。

G-Buffer分配

在Deferred Rendering的框架中,不管是Deferred Shading还是Deferred Lighting,G-Buffer的分配都是非常关键的。上一篇得出的lighting pass公式如下:

[latex]float4(1, 1, 1, (\mathbf{n} \cdot \mathbf{h_n})^{\alpha} F(c_{spec}, \mathbf{l_{cn}},\mathbf{h_n})) \times \mathbf{c}_{lightn} (\mathbf{n} \cdot \mathbf{l_{cn}})[/latex]

从公式可以看出,在light pass里需要的量有nh,alpha,cspeclc。因为h = (v + lc) / 2(见游戏中基于物理的渲染系列文章),而lc = normalize(lp)(l是光源位置,p是要计算的点位置),所以最终需要G-Buffer提供的量有:np,alpha和cspec。要完整的保存这些量,一共需要8个通道,normal占3个,position占3个,alpha和cspec分别占一个。这样对G-Buffer来说消耗太大了,必须要缩减。

显而易见的是,normal是经过归一化的,只需要保存2个分量。http://aras-p.info/texts/CompactNormalStorage.html比较了多种保存2分量的方法,其中Spheremap transform速度和效果综合起来最佳,Crytek也在用同样的方法,即:

float2 encode(float3 normal)
{
   return normalize(normal.xy) * sqrt(normal.z * 0.5 + 0.5);
}
float3 decode(float2 n)
{
   float3 normal;
   normal.z = dot(n, n) * 2 - 1;
   normal.xy = normalize(n) * sqrt(1 - normal.z * normal.z);
   return normal;
}

下 一步是position。实际上像素所在的位置已经提供了x和y,需要保存的仅仅是z。position何以很好地从z和像素位置计算出来。这里保存的是 view space的z除以far plane。在lighting pass,pixel shader里拿到像素在view space的位置之后,做这样的计算:

p = view_dir * ((z * far_plane) / view_dir.z);

其 中,view_dir是在vertex shader中计算之后传到pixel shader。对于把光源的几何体直接作为光源几何的情况(如果你不熟悉这个,请见下篇),那么view_dir就是顶点乘上world * view矩阵之后的结果。对于用全屏的四边形作为光源几何的情况,view_dir就是把view frustum在far plane上的四个点乘上inverse(projection)矩阵之后的结果。z * far_plane就还原出了该点在view space的z,然后根据相似三角形的定理很容易就能推出这个还原公式。现在,position成功地压缩到了1个通道。

剩下的就是alpha和cspec。如果不需要fresnel,可以直接忽略cspec,留到shading pass再做,这里直接存alpha就可以了。否则,就需要把alpha和cspec放入同一个通道。我用的方法是,floor(cspec * 100)作为整数部分,clamp(alpha, 0, 255) / 256座位小数部分。这样的限制是,alpha取值范围为[0, 256),一般来说够用了。

由此,所有lighting pass需要的信息都被压进4个通道内,G-Buffer只需要1张texture,省去了MRT。

Shading Pass

shading pass需要把前面所有lighting pass积累出来的光照信息和物体本身的材质信息组合起来,得出最后的着色。物体材质中的cspec已经存在G-Buffer,并在lighting pass中计算了,所以shading pass输入的材质有cdiff,cspec,cemit,alpha。别忘了在上一篇的公式中,specular号需要乘上归一化系数(alpha + 2) / 8。另一方面,在lighting pass的结果里,rgb存的是积累的diffuse,a存的是积累的specular亮度,如果还有计算AO,那么shading所用的公式就是:

[latex]\mathbf{c}_{emit} + (lighting.rgb * \mathbf{c}_{diff} + \frac{alpha + 2}{8} * lighting.a) * ao[/latex]

如果在G-Buffer和lighting pass因为不考虑fresnel而至保存了alpha,那么shading pass的公式就变成:

[latex]\mathbf{c}_{emit} + (lighting.rgb * \mathbf{c}_{diff} + \frac{alpha + 2}{8} * \mathbf{c}_{spec} * lighting.a) * ao[/latex]

现在Deferred Lighting的3个阶段都已经得到解释,下一篇将讲解如何更快地计算lighting pass。