[转]KlayGE 4.0中Deferred Rendering的改进

原文:http://www.klayge.org/2011/11/27/klayge-4-0%E4%B8%ADdeferred-rendering%E7%9A%84%E6%94%B9%E8%BF%9B%EF%BC%88%E4%B8%80%EF%BC%89%EF%BC%9A%E6%B5%81%E6%B0%B4%E7%BA%BF/

 

Deferred Rendering在KlayGE中已经出现比较长时间了,我也写过系列文章来阐述KlayGE中的延迟渲染。在将要推出的KlayGE 4.0中,Deferred Rendering进入了渲染系统的核心,可以作为更通用更方便的一个渲染封装来使用。

在功能上,KlayGE 4.0中的Deferred Rendering也有了长足的进步。本系列将着重于解析这些新改进。

流水线

先来看看Deferred Rendering的流水线。

Pipeline

在流水线方面,第一个比较大的变化是,G-Buffer改成了MRT的,用类似Deferred Shading的fat G-Buffer来避免在shading pass再次渲染一遍物体。新G-Buffer的布局将在下一篇分解。在shading pass阶段,只需要渲染一个全屏quad,在每个pixel上把材质和光照信息结合就可以了。

其次,G-Buffer内已经没有Depth的通道,直接使用D24S8格式的texture来保存depth。这样就需要做一个depth线性化的步骤,把24-bit非线性的depth转到32-bit float的纹理上,方便后面使用。线性化的方法为:

其中far为远平面,near为近平面。这样一来,就能省出一个通道,同时depth的精度也提高了。对于D3D9,也可以用扩展格式来实现D24S8纹理。

第三个改进是,规范化了stencil的使用。如果stencil的最高位为1,就表示那个pixel不会在lighting pass中计算光照。这样就可以挡掉一些不希望接受光照的特殊物体。

另 外,在shading pass之后增加一个special shading pass。标记有special shading属性的物体会在这个阶段再画一次。special shading的本意是渲染带emit的物体,其实可以和stencil mask配合,在这里作任何想做的forward shading效果。透明物体的alpha也可以在special shading中给出,请看后文关于透明物体的渲染一段。

新的Deferred Rendering流水线从G-Buffer上看,像Deferred Shading,而之后的阶段则更像Deferred Lighting。可以算作是两者的结合。

下一篇我将比较G-Buffer的变化,看看如何充分利用每一个bit,把需要的信息挤入狭小的G-Buffer中。

 

上一篇讲了在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框架都会面临的大问题:透明物体

 

上文讲到了如何把信息挤入有限的G-Buffer,另一个在实际中面临的问题是如何渲染透明物体。

透明物体

游戏中透明物体是不可缺少的,对于Deferred Rendering来说,透明物体一直是痛苦的。常见的做法是在deferred rendering的场景之上用forward shading来单独渲染透明物体,但那样就意味着必须单独实现一整套forward shading的流水线。这对于维护和扩展都是很不利的,对性能也很有影响。

在KlayGE 4.0里,我用的方法被称为Deep G-Buffer。其基本过程是,把第一篇所描述的Deferred Rendering流水线复制三份,不透明的物体、透明物体的背面、透明物体的正面分别有自己独立的G-Buffer、lighting pass、shading pass和special shading pass。最后会生成三张shading的结果,再把它们按照alpha混合起来就可以了。

首先建立的是不透明物体的G-Buffer,跟原先一样:
Opaque objects's G-Buffer
细致的朋友可以发现,由于用了best fit for normals,G-Buffer里的normal看上去很有趣。

然后用把cull设置为front,只画透明物体的背面,存在第二个G-Buffer中。这里还需要用类似depth peeling的方法clip掉比不透明物体更远的pixel。因为不透明物体挡住了绝大部分pixel,透明物体的背面只剩下很少一部分:
Transparent object back face's G-Buffer

同样,我们可以在第三个G-Buffer存透明物体的正面:
Transparent object front face's G-Buffer

经过lighting pass、shading pass和special shading pass,就得到了不透明物体的shading:

透明物体背面的shading,几乎没有被照亮的:
Transparent object back face's shading

以及透明物体正面的shading:
Transparent object front face's shading

注意透明物体都会在special shading pass给出像素alpha值。接下来只要把它们混合起来,就可以得到我们想要的结果:
After blending

再来一张侧面图,可以看到由于光照方式一样,透明物体和不透明物体的光照能连续平滑地过渡。

Side view

本篇抛砖引玉地提出了在Deferred框架下渲染透明物体的一个方法,它能简单有效地解决问题。缺点是三倍的内存和带宽消耗。如果depth peeling的层数增加,内存和带宽的消耗还会增加。这里其实也可以借鉴其他order independent transparency的方法,来取代depth peeling分离G-Buffer。下篇文章将会分析KlayGE中的实时GI

 

上一篇解决了透明物体的渲染问题;本文将挑战另一个实时渲染的神话,实时全局光照(GI)。

实时全动态GI

目前direct lighting在游戏中日趋成熟,比较前卫的游戏引擎已经不满足于diect lighting的效果了,逐渐开始尝试indirect lighting。早期的方法有通过离线渲染light map来实现静态场景、静态光源的GI。接着出现了PRT,可以处理静态场景、动态光源。CE3用了Light Propagation Volumes的方法,不需要预计算,可以产生动态场景、动态光源的diffuse GI。不过其速度和质量确实不敢恭维。难道就不能有全动态场景全动态光源diffuse和specular通吃实时GI方法吗?有!Multiresolution splatting for indirect illuminationMRSII)前来救驾。

在KlayGE 3.12中,团队成员atyuwen就已经实现了MRSII。经过半年多的改进,这种GI方法已经融入了新的Deferred Rendering框架中,并且性能也得到了很大的提升。下面就让我们来看看这种神奇的GI。

MRSII的渲染流程如下(感谢vanish整理了此流程图):

GI pipeline

首先,G-Buffer需要做mipmap,接着在每一层检测深度和法线的间断点,把那些间断点在stencil buffer中标记出来,得到了这样的stencil buffer:

Stencil Mip 0Stencil Mip 1Stencil Mip 2之前的stencil规则一样,最高位是1表示忽略。所以灰色的pixel是可以忽略掉的,黑色的是需要计算光照的。可以看出黑色所占的面积并不大,绝大部分pixel都被略过了。

另外,还需要生成一个Reflective shadow map。和shadow map类似,RSM也是从光源视角渲染一遍场景。除了深度以外,RSM还需要保存normal和flux信息。把RSM采样出一些点,比如256个,作为虚拟点光源(VPL)。目前KlayGE里面用的是均匀采样的方式,以后将改成importance sampling的方式提高VPL分布效率。

最后,每个VPL都可以根据BRDF生成一个light volume。用这些light volume去照亮G-Buffer的每一层。初始的light volume是个半球,在它的vertex阶段会根据各方向反射的亮度拉出某些顶点,生成一个奇怪形状的light volume。这个阶段因为涉及到大量的填充和计算,非常耗时,但因为stencil test是打开的,绝大部分pixel都会被挡掉,真正参与计算的pixel数远远少于G-Buffer的总pixel数,GI因此得到明显的加速。经过测试,在目前的场景下,如果只用一层G-Buffer(也就是不用multiresolution),速度只有用三层的一半。如果大于三层,速度已经没有提高了。所以默认就选了三层G-Buffer。

在生成每一层的indirect lighting结果之后,还需要做一个特殊的插值upsampling,才能得到光滑的结果。这个插值在MRSII的原paper中有描述,这里就不累赘了。

Indirect lighting: smooth sampling

如果只是用一般的最近点插值或者双线插值,结果会有很多悲催的锯齿:

Indirect lighting: point sampling

最后,把indirect lighting加到direct lighting中,继续做下一步的shading pass。最终结果如下:

Final GI

比较只有direct lighting的结果,可以看到右边和地面被照亮了:

Final no GI

用了MRSII后,对于512×512的RSM、256个VPL、三层G-Buffer的情况下,GI在GTX480上只需要1.09ms、在9800GT上需要4.3ms。目前还有不少性能空间可以挖掘,我预计在同质量的情况下,最终能达到在GTX480上0.5ms、9800GT上2.5ms的速度。

这套GI的框架不但可以做这样的反射型indirect lighting,也可以做caustics这样的高频反光,也可以处理sub-surface scattering等材质效果。在KlayGE以后的版本中,MRSII将会得到持续的发展。

本篇详细讲解了实时GI的做法,下一篇是关于post process的改进。

 

上一篇分析了KlayGE中实现实时全动态GI的方法,本篇是这个系列的完结篇,主要讲流水线的最后一段:Post process。

Post process

在KlayGE 4.0的Deferred Rendering中,post process主要有HDR、AA和color grading。下面将分别讲述它们的改进。

HDR

在KlayGE 3.12用了filmic tonemapping之后,HDR部分就几乎没有别的改变。这里唯一的变化是最终输出的float4,把亮度存在A通道上。这是为了后面FXAA的需要。

AA

在Deferred框架中,无法使用硬件AA曾经是个恼人的问题。随着这些年各种基于post process的AA方法大量出现,Deferred下AA的问题基本被解决了。

团队成员陈顺斌和郭鹏曾为KlayGE 3.12提供了FXAA。在新版本中,FXAA也升级到了最新的3.11版。从FXAA 3开始,就要求输入纹理是LDR的RGBL格式(L为亮度),所以计算AA的地点也就从HDR之前改到了HDR之后。虽然FXAA 3.11可以用G通道代替L,但效果肯定会受影响。既然让HDR post process输出RGBL轻而易举,我就没有把L改成G。FXAA极快,目前的实现在GTX480上可以达到0.1ms的惊人速度。几乎做到了无性能损失的高质量AA。

Color grading

Color grading是这个版本新增的。以前游戏一般不太重视color grading的作用,但在电影业,color grading是流水线非常重要的一步(可以和skinning相提并论的)。这里我实现的color grading是用16x16x16的3D texture作为查找表,用原RGB作为地址去查询,查询出的结果即为调色后的颜色(来自GPU Gems 2: Chapter 24. Using Lookup Tables to Accelerate Color Transformations)。除了runtime的post process之外,还需要一个离线工具,用来生成那个3D texture。这里我用的方法类似CE3,先生成一个摊平的256×16的2D texture:

Color grading flatten

在photoshop里打开一张游戏截图,调整RGB曲线至需要的色调,然后把那个RGB曲线应用到之前生成的2D texture,最终打包成3D texture就得到了我们所需要的查找表。以后可能会根据需要做一个在线调整color grading的工具。

总结

本系列文章把KlayGE 4.0中Deferred Rendering的改进逐一介绍了一下,希望能对也在做类似事情的朋友有所帮助。在总结里我也身边展望一下未来,看看在KlayGE 4.1中,Deferred Rendering部分还会可能出现什么改进。

  1. 更高的速度。Multiresolution的方法在GI中获得了成功,也许也可以扩展到direct lighting和SSVO中,用于加速整个Deferred Rendering。
  2. 改进HDR中的bloom filter。学习3DMark11,用FFT的方式在一个pass内完成bloom、lens flare等特效。
  3. 支持移动平台。精简的Deferred Rendering流水线将会以至到移动平台上。
  4. 更多例子用Deferred Rendering实现。目前只有3个例子用到了deferred框架,其他还是forward的。以后会有越来越多的例子转到deferred中。

对于KlayGE 4.x还有什么期待,可以上trac交流。

posted @ 2012-05-18 15:09  Scan.  阅读(574)  评论(0)    收藏  举报