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

上一篇提到了在视口间共享shadow map的优化,以及对代码的重构。重构中的一个小发现造就了在不增加存储占用的情况下,就能支持透明物体的渲染。

透明物体和Deep G-Buffer

透明的烦恼一文中,为了用纯Deferred的框架解决透明物体渲染的问题,KlayGE引入了Deep G-Buffer的方法。对不透明物体、透明物体的背面、透明物体的正面分为三个G-Buffer layer,分别有一条Deferred流水线,最终通过alpha把三层blend成最终结果。这种方法虽然能简单有效地解决问题,并避免繁琐的forward流水线,但需要三倍的存储和带宽。而且如果depth peeling的层数增加,开销还会进一步加大。最近出现的几种OIT方法,都不兼容于deferred。所以如果不用forward,就得想办法解决这个问题,至少减轻一点算一点。

Deferred的流水线如果考虑了Deep G-Buffer,看起来是这样的:

for each shadowed light:
   generate shadow map or reflective shadow map.

for each view port:
   for each G-Buffer layer:
      generate g-buffer.

   for each shadowed light:
      for each G-Buffer layer:
         shadowing from shadow map.
         lighting.

   for each G-Buffer layer:
      shading.

   for each G-Buffer layer:
      merge shading result.

在重构Update之前,一团乱麻的代码根本看不出这个结构。重构之后可以清晰地看到,这个流水线应该进一步整理为:

for each shadowed light:
   generate shadow map or reflective shadow map.

for each view port:
   for each G-Buffer layer:
      generate g-buffer.

      for each shadowed light:
         shadowing from shadow map.
         lighting.

      shading.
      merge shading result.

也就是说,不同的G-Buffer layer之间可以共享G-Buffer、lighting texture、shading texture等。由于不同的G-Buffer layer已经用stencil把需要计算的pixel标记出来了,这么共享texture不会造成错误的pixel值。只要把最终shading结果alpha blend起来,结果会和使用Deep G-Buffer完全相同。

比较一下这么改进前后的存储开销:

改进前 改进后 不支持透明物体时
G-Buffer 6张RGBA8 2张RGBA8 2张RGBA8
Depth stencil buffer 3张D24S8 1张D24S8 1张D24S8
Linear depth buffer 3张D32F 1张D32F 1张D32F
Shadowing texture 1张RGBA8 1张RGBA8 1张RGBA8
Lighting accumulate texture 3张R11G11B10F 1张R11G11B10F 1张R11G11B10F
Shading texture 1张R11G11B10F,2张RGBA16F 1张RGBA16F 1张R11G11B10F

很显然改进后所需的存储开销急剧减少,和不支持透明物体的时候,只是shading texture从R11G11B10F变成RGBA16F,其他都没区别了。如果进一步修改special shading,改成手工把shading和emit相加,就能仍然保留R11G11B10F的格式。有时间的话我会试着这么改。由此,我们得到了一个可以在完全不增加显存占用的情况下支持透明物体渲染的方案。而且,就算要用depth peeling增加更多透明层,这个框架仍然不需要增加存储消耗,是完全memory bounded的。

KlayGE 4.3中Deferred Rendering的改进基本就是这些了,下一篇会讲post process方面的改进。