Deferred Shading(延迟渲染)

发表于2018-01-23
评论0 9.6k浏览
考虑到有很多人对延迟渲染不怎么了解,也不知道如何使用,为此本篇文章就和大家详细介绍下延迟渲染的原理和使用方法。

1、简介
       在计算机图形学的词典里,Shading表示“对受光物体的渲染”,这个渲染过程包括下面几步[1]:
       1) 计算几何多边形(也就是Mesh)。
       2) 决定表面材质特性,例如法线、双向反射分布函数(bidirectional reflectance distribution function, BRDF)等等。
       3) 计算入射光照。
       4) 计算光照对表面的影响,并最终显示。
       一般渲染引擎,渲染场景中的物体的时候,是将这四步一次执行完的。延迟渲染则将前两步和后两步分开到渲染管道相互独立的两个部分来执行。

2、背景知识
       对比前向渲染(聚光灯的最简单的前向渲染见DirectX 9.0c的sample"Shadow Map"),前向渲染有多种问题[1]:
       1) 计算每个几何体受那些光影响耗费了CPU的时间,更坏的是,这是个O(n*m)的操作。
       2) Shader经常需要超过一次以上的Pass来渲染光照,渲染n个灯光,对于复杂的Shader,可能需要O(n)次运算。
       3) 增加新的光照模型和新的光源类型,可能需要改变所有Effect的源文件。
       4) Shader很快就将达到或者超出SM2的指令限制。
       在MMO里,我们对游戏环境很少会有过于苛求的要求。我们无法控制同屏可见的玩家数量、无法控制同屏会有多少特效和光源。由于传统前向渲染缺乏对环境的控制,且对于光源的复杂度难于估量,因此我们选择了延迟渲染。这可以让我们的画面更接近于当今顶尖的游戏引擎,并且让光照所耗费的资源独立于场景的几何复杂度。
       延迟渲染提供了下面的好处:
       1) 光照所耗费的资源独立于场景复杂度,这样就不用再费尽心机去想着处理那些光源影响几何体了。
       2) 不必要再为几何体的受光提供附加的Pass了,这样就节省了Draw Call和状态切换的数量。
       3) 在增加新的光源类型和光照模型时,材质的Shader不需要做出任何改变。
       4) 材质Shader不产生光照,这样就节省了计算额外的几何体的指令数。
       延迟渲染需要显卡提供MRT的支持,且利用了不断增加的存储器的带宽——这也就意味着我们可能得对玩家所使用的硬件提出更高的要求。因此我们既实现了前向渲染,也实现了延迟渲染。我们优化了前向渲染管道,并在此基础上完成了延迟渲染管道。

3、基本原理
       目前我们看到最终画面都是2D的,只能看到有限的像素数,理论上我们只要处理(指光照,阴影处理)最终我们可以看到的点的效果就够了,多余的处理是浪费的。而正常的前向渲染(Forward Shading)流程是把空间的点进行各种剪裁后,进行处理,所处理量远远大于我们最终看到的。所以延迟渲染出现了。它先将摄像机空间的点光栅化转化成屏幕坐标后再进行处理。这样就能减少处理的次数,从而提高效率[5]。
       既然把处理流程放在了后面,那么处理所需要的参数也必须带到后面的流程。这里使用MRT(multi target render)就很重要,RT占用的显存越大,对显卡的的带宽要求也就越高,DX10支持8个MRT(DX11的MRT数量我没查到,在DX11的新特征中,也没有提到增加MRT,所以可能也是8个)。后面的处理至少需要空间位置信息,可以通过Depth(至少16位)获得,其他可以将法线信息(normal),高光信息(specular),AO系数,diffuse,自发光(emissive),材质编号等信息放入MRT中。
       延迟渲染管线可分为四个阶段(和第一节说的4个阶段有一些不同,只是某些功能进行不一样的划分而已,大致还是一样的):Geometry,Lighting, Composition, Post-processing[4],Post-processing阶段与传统的forward shading没有太大差别,这里不提,只说明一下前三个阶段。
       1) Geometry阶段:将本帧所有的几何信息光栅化到G-buffer。包括位置,法线,贴图等。
       2) Lighting阶段:以G-buffer作为输入(位置,法线)进行逐像素的光照计算,将diffuse lighting和specular lighting 结果分别保存在两张RT上作为lighting buffer。
       3) Composition阶段:将G-buffer中的贴图buffer和lighting buffer融合,得到渲染结果。

4、延迟渲染核心部分
       4.1、G-buffer
       Geometry阶段将几何信息渲染到multi render target上(MRT),当前最多支持4个MRT。并且驱动要求4个MRT必须相同的bit宽度。RT对显存占用过大会增加带宽,降低cache命中。而简单格式的RT又会影响画质。因此决定使用32bit的RT(如A8R8G8B8,R16G16F)或64bit宽度的RT(如A16R16G16B16F)。需要在画质和性能间做出折衷。(开发时尽可能可以方便的配置)[3]。

       MRT中必须的信息:position(depth), normal, diffuse(texture)
       可能需要的信息:specular, power, emissive, ao, material id
       这些信息需要在这4个RT上用合理格式,合理的组织。这里还可以就存储空间和shader的复杂性做折衷。如只保存depth,然后在光照时计算position,以及用球面坐标保存法线。以目前的资料得出的结论是应该尽可能地pack数据,减少内存占用,多出来的若干条shader指令不会明显影响性能。


4.2、光照计算及相关优化
       使用延迟渲染技术最大的好处就是可以渲染光照极为复杂的场景。这里场景中的光照可以分为两类。影响整个场景的scenelight。如directionallight。渲染一个screenquad,逐像素光照计算,没什么好说的;另一类是只影响一部分区域的locallight,如点光源、聚光灯、和特效等等。这些locallight只影响到屏幕上的某些像素,当然不需要逐像素的进行光照计算。最简单的方法是绘制这些光源的包围体(点光源的包围体是球,聚光灯的包围体是圆锥),包围体的大小要大于等于光源的衰减范围。这些包围体经过变换投影到屏幕上的对应区域,随后在pixelshader中计算光照[3]。

       优化:
       1.光源包围体的视锥剔除,遮挡剔除。
       2.光源包围体投影后很小时剔除;若干个靠的比较近的小光源合并成一个较大的光源
       3.光源包围体的backfaceculling
       4.屏幕空间中没有被光源照到的,或者被更近的物体遮挡住的像素不需要光照计算,因此可以逐像素的深度剔除。有两种方法:a.使用正确的stencillightvolume。类似shadowvolume的方案,将渲染lightvolume的正反两面,得到正确的stencilmask,然后光照计算时使用stencilbuffer。这种方法可以得到正确的结果,但是需要渲染每盏灯时频繁改变renderstate,可能会带来一定性能上的损失。b.使用ztest,可以得到“一定程度上正确”的结果。


4.3、阴影计算
       光照计算的同时计算阴影。使用传统的shadowmap,预先生成一张阴影图(相关)。考虑在编辑场景的时候指定那些重要的光源才会产生阴影。在计算shadowmap时要针对光源的bindingvolume进行剔除[3]。

       方向光和聚光灯可以使用基本的Shadow Map投影(正交投影,透视投影),点光源会复杂一些,需要使用Cube Shadow Map,由于较老的硬件不支持在不同纹理之间插值,使得不同纹理之间会有接缝(见[6] P171),在DX10中才支持Cube Map纹理之间的插值。


5、优缺点分析
       延迟渲染主要的好处包括[1,6]:
       1) 光照的开销与场景复杂度无关。
       2) Shader可以访问深度和其他像素信息。
       3) 每个象素对每个光源仅运行一次。也就是说,那些被遮挡的像素是不会被光照计算到的。
       4) 材质和光照的Shader完全分开。
       延迟渲染还需要克服的主要障碍包括:
       1) 较高的显存带宽占用
       2) G-buffer消耗较多的填充率,这个问题在游戏机上比较严重
       3) 无硬件反锯齿的支持
       4) 对Alpha Blend支持较差
       解决方案:
       1) 我们发现当前驻留的显卡已经可以在稍低的分辨率下解决贷款问题了,而在当今最高端的机器上,可以在开启全部特性的前提下,适应更高的分辨率。在DX10 即便显卡上,ATI和NVIDIA都增强了MRT的性能。DX10和SM4都提供了GPU支持的整数处理,以及从深度缓冲中读取数据。所有这些都可以减少显存带宽。当提供了新的硬件和特性时,性能自然就会提升。
       2) 在合适的Filter作用下,精确的边缘检测可以减少几何体边缘的锯齿。虽然这些方法并不像硬件全场景反锯齿那样精确,但是仍然可以以假乱真。
       3) 对不透明物体采用延迟渲染,透明物体采用正向渲染,可以解决Alpha Blend的问题(Unity3D采用这种解决方案)。
 
PS:对某个具体项目的说明,包括其中的一些优化见资料[1],GPU GEMS2和3中都有这个引擎延迟渲染的文章,值得一看。
 
参考文献:
[1]http://blog.csdn.net/noslopforever/article/details/3951273
[2]http://www.opengpu.org/forum.php?mod=viewthread&tid=434
[3]http://blog.csdn.net/pizi0475/article/details/7521394
[4]http://blog.csdn.net/Garuda/article/details/5273106
[5]http://blog.sina.com.cn/s/blog_766f82770100p2kc.html
[6]Akenine-Möller T, Haines E, Hoffman N. Real-time rendering 3 [M]. 

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引