zoukankan      html  css  js  c++  java
  • 一天干掉一只Monkey计划(三)——延迟渲染,及材质ID

    一天干掉一只Monkey计划(三)--延迟渲染、材质ID

    --Zephyroal

    技术背景

    最近太懒了,打LOLRM这一块都荒废了下来,关于延迟渲染的RM工程其实个把月前写好的,杂务所扰,一直没有静下心来整理,现在正值年关,终于可以继续了,努力在接下来的几周把接下来的Monkey计划完成。

    关于延迟渲染技术,在现阶段的游戏程序中正被越来越推广使用,从PS3Xbox360的次时代,到暴雪的星际2,及最近效果完全征服我的战地3,无一例外的使用了延迟渲染DeferredShadin(以下简称DSDeferredLightingDL技术暂不在这里讨论)

    那么传说中的强大延迟渲染技术到底有何强大之处呢?!

    其实,理论早在几十年前就已经提出,实现也不难,真正的优势,要从传统的前向渲染(ForwardShadingFS)说起,下面引用一段介绍:

    TabulaRasa中,我们一开始的渲染引擎是基于最初的DX9而完成的传统前向渲染技术(FS)的,使用了HLSLD3DXEffect。我们的Effect使用了Pass里的Annotation来描述这个Pass所支持的光照。而在CPU这边,引擎可以算出来每个几何体被那些光源所影响——这个信息连同那些在PassAnnotation里的信息一起,用于设置光源的参数、以及确定每个Pass该调用多少次。

    这种前向着色有多种问题:

    1计算每个几何体受那些光影响耗费了CPU的时间,更坏的是,这是个O(n*m)的操作。

    2Shader经常需要超过一次以上的Pass来渲染光照,渲染n个灯光,对于复杂的Shader,可能需要O(n)次运算。

    3增加新的光照模型和新的光源类型,可能需要改变所有Effect的源文件。

    4Shader很快就将达到或者超出SM2的指令限制。

    MMO里,我们对游戏环境很少会有过于苛求的要求。我们无法控制同屏可见的玩家数量、无法控制同屏会有多少特效和光源。由于传统前向渲染缺乏对环境的控制,且对于光源的复杂度难于估量,因此我们选择了延期着色。这可以让我们的画面更接近于当今顶尖的游戏引擎,并且让光照所耗费的资源独立于场景的几何复杂度。

    延期着色提供了下面的好处:

    1光照所耗费的资源独立于场景复杂度,这样就不用再费尽心机去想着处理那些光源影响几何体了。

    2不必要再为几何体的受光提供附加的Pass了,这样就节省了DrawCall和状态切换的数量。

    3在增加新的光源类型和光照模型时,材质的Shader不需要做出任何改变。

    4材质Shader不产生光照,这样就节省了计算额外的几何体的指令数。

             简单的说,DS最大优势便是在如今渲染模块中PixelShader变得越来越复杂的情况下,能最大限度地节约硬件效能。

    实现原理

    延迟渲染管线可分为俩个阶段:Geometry,Lighting,当然如将GeometryBuffer中的丰富的信息仅仅用于光照点亮过程显而易见不是俺们程序猿“雁过拔毛”的性格,所以通常各种Post-processing也会被被我们加入管线之中,这里且不做深入。

    鄙人很懒,相比码字,更喜欢用代码说话,所以这里就杂糅一些别人的著说,后面再一一提名感谢,嘿嘿~~~

    Geometry阶段:将本帧所有的几何信息(位置,法线,贴图)光栅化到G-buffer

    Lighting阶段:以G-buffer作为输入(位置,法线)进行逐像素的光照计算,得到渲染结果。

    DS整体渲染过程并不复杂,但问题往往出在细节,下面先一一列举各个步骤。

    G-buffer

    Geometry阶段将几何信息渲染到MultiRenderTarget上(MRT),当前最多支持4MRT。并且驱动要求4MRT必须相同的bit宽度。RT对显存占用过大会增加带宽,降低cache命中。而简单格式的RT又会影响画质。因此决定使用32bitRT(如A8R8G8B8R16G16F)或64bit宽度的RT(如A16R16G16B16F)。需要在画质和性能间做出折衷。(开发时尽可能可以方便的配置)。中有一些性能比较。

    光照计算

    使用延迟渲染技术最大的好处就是可以渲染光照极为复杂的场景。这里场景中的光照可以分为两类。

    影响整个场景的scenelight。如directionallight。渲染一个screenquad,逐像素光照计算,没什么好说的。

    另一类是只影响一部分区域的locallight。如点光源,聚光灯,以及特效等等。这些locallight只影响到屏幕上的某些像素,当然不需要逐像素的进行光照计算。最简单的方法是绘制这些光源的包围体(点光源的包围体是球,聚光灯的包围体是圆锥),包围体的大小要大于等于光源的衰减范围。这些包围体经过变换投影到屏幕上的对应区域,随后在pixelshader中计算光照。

    优化:

    1.光源包围体的视锥剔除,遮挡剔除。

    2.光源包围体投影后很小时剔除;若干个靠的比较近的小光源合并成一个较大的光源

    3.光源包围体的backfaceculling

    4.屏幕空间中没有被光源照到的,或者被更近的物体遮挡住的像素不需要光照计算,因此可以逐像素的深度剔除。

    a.使用正确的stencillightvolume。类似shadowvolume的方案,将渲染lightvolume的正反两面,得到正确的stencilmask,然后光照计算时使用stencilbuffer。这种方法可以得到正确的结果,但是需要渲染每盏灯时频繁改变renderstate,可能会带来一定性能上的损失。

    b使用ztest,可以得到“一定程度上正确”的结果。

    阴影

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

    方向光和聚光灯可以使用基本的shadowmap投影(正交投影,透视投影)。点光源会复杂一些,需要使用cubicshadowmap。(考虑unwrappingmethod[14]

    半透明

    由于在延迟渲染的过程中只计算离屏幕距离最近的那个像素的光照,因此无法处理半透明物体的光照。

    方案1

    延迟渲染的过程中只处理不透明的物体,将所有半透明的物体放在渲染过程的最后,使用传统的forwardshading渲染。

    方案2

    Geometry阶段将半透明的物体和背景逐像素的交织起来,将透明度放在一个单独的通道中。按一般的方法计算光照。随后在composition阶段再根据透明度将透明物体和背景逐像素的混合起来。

    优点:

    光照一致性。半透明的物体也参加延迟渲染,可以接受多光源的光照。

    简单并且健壮。不需要单独区分不透明物体和半透明物体,不需要单独的半透明渲染管道。

    速度快。只增加了710ps指令,两张贴图,只有约2%的性能损失。

    缺点:

    模糊。在半透明的物体上会有一点模糊,原因是在交织的过程中会有一定信息损失。

    边缘锯齿。反交织的过程中半透明物体的边缘会产生一些锯齿。

    只能有一层半透明。

    多种材质

    在延迟光照的过程中支持多种材质需要如下方案:

    G-buffer阶段输出材质的IDG-buffer的一个通道中,随后在lighting阶段根据材质ID使用不同的光照函数计算光照。这种方案在sm3.0中使用动态分支的前提下可以很好的工作。

    反锯齿

    Dx9API不支持反锯齿的MRT(需要在后处理上自行解决AntiAlias)Dx10支持。

    一种方案是使用超采样,先渲染到大的RT上,再downsample到正常的大小,得到没有锯齿的结果。延迟渲染的效率跟分辨率有很大关系,因此这种方法会极大的降低性能,基本不可取。

    另一种方案是使用“intelligentblur”,只模糊物体边缘的像素:

    根据相邻像素的深度和法线提取物体边界,然后对提取出的边界进行模糊。模糊时要避免不正确的泄露。如后面物体的颜色泄露到前面的物体上。总体而言实现会较为复杂。

    另一种方案:pre-lighting

    一种pre-zrenderingdeferredrendering的结合。G-buffer阶段只保存depthnormal,然后计算光照信息到lightingbuffer,格式如下

    LightColor.r*N.L*Att
    LightColor.g*N.L*Att
    LightColor.b*N.L*Att
    R.V^n*N.L*Att

    最后使用传统的forwardshading再将整个场景渲染一遍,期间查询lightingbuffer

    与普通的deferredshading相比:

    优点:

    占用带宽小,第一遍渲染只输出normaldepth是自动获得的。可以用在较老的硬件平台上,不需要MRT支持。对现有forwardshading管道改动较小,比较容易实现。

    缺点:

    整个场景需要渲染两遍,相当于在pre-zforwardshading中间加了一个lightingstage

    Resistance2,《GTAIV》,《MidnightClubLosAngeles》使用了这种技术。CryEngine3中也使用了这种方案。

    RT选择分配

    MRT中必须的信息:position(depth),normal,diffuse(texture)

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

             RT的预置,分配是需要针对实际使用而设计的,如何最大化地发挥每一个bit的作用,是一门需要不断实践的技术活。

    clip_image002[4]

    KillZone中的MRTf分布管理

    附上DX9SDK中对MRT的说明

    MultipleRenderTargets(Direct3D9)

    MultipleRenderTargets(MRT)referstotheabilitytorendertomultiplesurfaces(seeIDirect3D9Surface)withasingledrawcall.Thesesurfacescanbecreatedindependentlyofeachother.RendertargetscanbesetusingIDirect3DDevice9::SetRenderTarget.

    Multiplerendertargetshavethefollowingrestrictions:

    • Allrendertargetsurfacesusedtogethermusthavethesamebitdepthbutcanbeofdifferentformats,unlesstheD3DPMISCCAPS_MRTINDEPENDENTBITDEPTHScapisset.
    • Allsurfacesofamultiplerendertargetshouldhavethesamewidthandheight.
    • Someimplementationscannotperformpost-pixelshaderoperationsonmultiplerendertargets,including:nodithering,alphatest,nofogging,noblendingormasking,exceptthez-testandstenciltest.Devicesthatcansupportpost-pixelshaderoperationssetthecapbittoD3DPMISCCAPS_MRTPOSTPIXELSHADERBLENDING.

    WhentheD3DPMISCCAPS_MRTPOSTPIXELSHADERBLENDINGcapisset,youmustfirstconsulttheIDirect3D9::CheckDeviceFormatwiththeUSAGE_QUERY_POSTPIXELSHADER_BLENDINGresultforthespecificsurfaceformat.Iffalse,nopost-pixelshaderblendingoperationswillbeavailableforthatspecificsurfaceformat.Iftrue,thedeviceisexpectedtoapplythesamestatetoallsimultaneousrendertargetsasfollows:

      • Alphablend:ThecolorvalueinoCiisblendedwiththeithrendertarget.
      • Alphatest:ComparisonwillhappenwithoC0.Ifthecomparisonfails,thepixeltestisterminatedforallrendertargets.
      • Fog:Rendertarget0willgetfogged.Otherrendertargetsareundefined.Implementationscanchoosetofogthemallusingthesamestate.
      • Dithering:Undefined.
    • Noantialiasingissupported.
    • Someoftheimplementationsdonotapplytheoutputwritemask(D3DRS_COLORWRITEENABLE).Thosethatcan,haveindependentcolorwritemasks.Thisisexpressedusinganewcapabilitybit.Thenumberofindependentcolorwritemasksavailablewillbeequaltothemaximumnumberofelementsthedeviceiscapableof.

    RenderMonkey实践

    RenderMonkey中内置了一个简单的DS范例工程,如下图

    clip_image004[3]

    clip_image006[3]

    可见,DS分成两个Pss,分别实现了GeometryLighting过程。

     

    clip_image008[3]

    VetexShader中,输出了世界变换坐标,视角变换空间坐标,及世界变换空间中的法向量。

    clip_image010[3]

    这里的PixelShader中由于是简单例子,只使用了两个RT,分别输出世界坐标到RT0,输出法向量到RT1

    具体渲染过程可以由下清晰可见,有兴趣地可以拿起笔算一下WorldPos,看一下红黄蓝绿黑白的分布~

    clip_image012[3]

     

    ID材质标记

    前面提到过MaterialID,这里也对其做一个实现,顺便深入下实际使用中的DS技术。

    clip_image014[3]

    在新的工程中,Geometry阶段使用了两个Pass,利用标记不同的Alpha值来来实现DS中两中材质的渲染,当然,实际使用中,Alpha通道通常会被占用,来存储EmittColorSepcStrength等参数,这时候可以使用StencilBuffer来做标记~

    在两个Pass中标记材质直接使用了指定Alpha值,在Compostion中判断值,但Float值是存在精度问题的,这里借鉴了DriectX中工具函数的一个做法,加入一个Epsilon值,实现一个约等于的Equal函数,在DX10后,会有Int,就好多了~

    clip_image016[3]

    clip_image018[3]

             实现效果见下图,在Composition中实现了纯色,法线贴图两种材质~

    clip_image020[3]

    AntiAlias

    关于抗锯齿的问题,在KlayGE上看到一篇,拿来以飨读者

    Deferred Shading发明的一天起,anti-alias的问题就一直困扰着所有Deferred的方法。虽然很多无良的游戏厂商直接在Deferred Rendering的游戏里不支持AA,但确实AA对提升画面质量很有帮助。

    Edge AA

    Deferred的框架里,很自然会想到用Edge AA来处理AA。其过程不外乎:

    1. 边缘检测,得到每个像素像边缘的程度
    2. shader里根据像边缘的程度来控制采样坐标

    这本身并不是个复杂的过程,尤其是第二步,非常直截了当了,所以这里集中讨论的是如何进行边缘检测。

    GPU Gems 2“Deferred Shading in STALKER”一文提供了一种边缘检测的方法,通过把周围像素的法线差和深度差的和来判断边缘,由e_barrier这个参数来定义阈值和比例,而这个参数和分辨率有关。GPU Gems 3“Deferred Shading in Tabula Rasa”改进了这个过程,只判断法线差和深度差最大和最小的两组。由于只是局部的相对量而已,这样就做到了和分辨率无关的边缘检测。KlayGE目前用的也是这种方法,得到的边缘如下:

    clip_image021[3]

    Edge

    另一个可能用于边缘检测的方法是,前面提到了如何恢复出每个pixelview space position,每个pixel取得周围4pixel的位置之后,就可以直接cross得出一个normal,姑且称为screen space normal。如果一个像素是连续的,那么这个normal就会很接近于G-Buffer中保存的normal,否则它们的方向就会差别很大。下图为G- Buffer中的normal

    clip_image022[3]

    Normal in G-Buffer

    这是screen space计算出的normal

    clip_image023[3]

    Normal in screen space

    把这两个normal做一次dot,小于某个阈值的就认为是边缘,得到:

    clip_image024[3]

    Screen space normal based edge

    利用硬件MSAA作边缘检测

    前面提到的边缘检测结果虽然不错,但其实都是是参数相关的。能否就用硬件的MSAA来做边缘检测呢?在Shader model 3.0以上的GPUvertex attribute插值的时候可以选择centroid这个modifier。开启了centroidattribute,会选择覆盖到的sample 中心来插值,而不是像素中心。所以,同一个属性,如果即有centroid又有不带centroid的版本都传给pixel shader,在pixel shader里面判断两者不一致,就表示这个pixel在边缘上。这样的话,边缘的情况就和硬件MSAA完全一致了。但其实MSAA会过渡判断边缘,所有三角形的边缘都会被认出来,即便只是物体内部的。所以谨慎使用。

    能不能就用MSAA

    前面讨论了那么多都是基于EdgeAA。在Deferred Lighting框架下,难道就不能直接用MSAA?可以!这也是Deferred LightingDeferred Shading优秀的方面之一。Deferred Shading不能直接MSAA的本质原因是在G-Buffer之后,物体几何信息全部抛弃了。相比Deferred Lighting,在shading pass,物体会被再次渲染一遍,这个时候还是有几何信息的,如果在shading pass打开了MSAA,就可以像Forward shading那样利用硬件MSAA了。唯一不同的是,光照来自于lighting passtexture,而不是从光源计算。就算硬件MSAA,也只是每个pixel执行一次pixel shader,在按照覆盖情况写入sample的,所以在这里视觉上几乎和Forward shading一样。

    实际使用DS

    到目前为止,这里都是很浅显的理论探究,自己做了一个简单地实现,同时渲染几十盏灯光都可以轻易地保持高FPS,这在传统的FS体系中是难以想象的,so,感受到DS技术的吸引力了吧?!

    clip_image026[3]

    闭门造车是难以前进的,多借鉴下成功的实际项目才是王道,载录若干如下,其它更多详见最后的附录:

    clip_image028[3]

    Cryengine渲染引擎剖析》

     

    clip_image030[3]

    <星际争霸2>引擎技术解析》

    载录引用地址

    延迟渲染技术文献摘录

    http://blog.csdn.net/Garuda/article/details/5273106

    TabulaRasa中的延迟着色技术

    http://blog.csdn.net/noslopforever/article/details/3951273

    《延迟渲染-KlayGE

    http://www.klayge.org/wiki/

    其它相关内容

    工欲善其事必先利其器,除了必要的实践,在原理上上做足功课亦是非常重要,下面将相关的文章引用列如下:

    [1]DeferredShadingDemo

    http://www.puzzledhero.com/projects/pages/deferred_shading_demo.htm

    [2]Deferredshading2dx10                             

    http://www.humus.name/index.php?page=3D&ID=81

    [3]gpugems2Chapter9.DeferredShadinginS.T.A.L.K.E.R.

    http://http.developer.nvidia.com/GPUGems2/gpugems2_chapter09.html

    [4]Hargreaves,Shawn,andMarkHarris.2004."DeferredShading."Presentation.

    http://download.nvidia.com/developer/presentations/2004/6800_Leagues/6800_Leagues_Deferred_Shading.pdf

    [5]Chapter19.DeferredShadinginTabulaRasa

    http://http.developer.nvidia.com/GPUGems3/gpugems3_ch19.html

    [6]DeferredShadingTutorial.

    http://fabio.policarpo.nom.br/docs/Deferred_Shading_Tutorial_SBGAMES2005.pdf.

    [7]Deferredrenderingtransparency

    Shaderx7

    [8]DesigningaRendererformultiplelights:ThelightPre-PassRenderer

    Shaderx7

    [9]LightPre-PassDeferredLighting:LatestDevelopmentWolfgangEngel

    http://www.bungie.net/images/Inside/publications/siggraph/Engel/LightPrePass.ppt

    [10]deferred-shading-aa-alpha-blending-demo

    http://null-ptr.blogspot.com/2009/01/deferred-shading-aa-alpha-blending-demo.html

    [11]overcomingdeferredshadingdrawbacks

    shaderx5

    [12]Reconstructingpixel3Dpositionfromdepth

    http://www.gamedev.net/community/forums/topic.asp?topic_id=474166

    [13]StoringNormalsUsingSphericalCoordinates

    http://mynameismjp.wordpress.com/2009/06/17/storing-normals-using-spherical-coordinates/

    [14]Efficientomnidirectionalshadowmaps

    Shaderx3

    [15]DeferredShadingShawnHargreaves

    http://www.talula.demon.co.uk/DeferredShading.pdf

    [17]Pre-lightinginResistance2MarkLee

    http://cmpmedia.vo.llnwd.net/o1/vault/gdc09/slides/gdc09_insomniac_prelighting.pdf

    [18]DeferredRenderinginKillzone2

    http://www.guerrilla-games.com/publications/dr_kz2_rsx_dev07.pdf

    [19]DeferredShadingShines.DeferredLighting?NotSoMuch.

    http://gameangst.com/?p=141

    [20]WikiDeferredshading

    http://en.wikipedia.org/wiki/Deferred_shading

    工程下载

    /Files/hmxp8/DeferredShading_V1.1.rar

  • 相关阅读:
    团队协议
    C++ 多继承和虚继承的内存布局(转)
    轻量级的.Net ORM框架介绍
    EF下CodeFirst、DBFirst与ModelFirst分析
    JDK方式
    JSON
    事务的ACID特性
    数据库查询
    Assets
    内部文件存储
  • 原文地址:https://www.cnblogs.com/Zephyroal/p/2331292.html
Copyright © 2011-2022 走看看