轉自http://blog.csdn.net/pizi0475/article/details/7521394
技術背景
最近太懶了,打LOL,RM這一塊都荒廢了下來,關於延遲渲染的RM工程其實個把月前寫好的,雜務所擾,一直沒有靜下心來整理,現在正值年關,終於可以繼續了,努力在接下來的幾周把接下來的Monkey計劃完成。
關於延遲渲染技術,在現階段的遊戲程序中正被越來越推廣使用,從PS3,Xbox360的次時代,到暴雪的星際2,及最近效果完全征服我的戰地3,無一例外的使用了延遲渲染DeferredShadin(以下簡稱DS,DeferredLighting,DL技術暫不在這裏討論)。
那麼傳說中的強大延遲渲染技術到底有何強大之處呢?!
其實,理論早在幾十年前就已經提出,實現也不難,真正的優勢,要從傳統的前向渲染(ForwardShading,FS)說起,下面引用一段介紹:
“
在TabulaRasa中,我們一開始的渲染引擎是基於最初的DX9而完成的傳統前向渲染技術(FS)的,使用了HLSL和D3DXEffect。我們的Effect使用了Pass裏的Annotation來描述這個Pass所支持的光照。而在CPU這邊,引擎可以算出來每個幾何體被那些光源所影響——這個信息連同那些在Pass的Annotation裏的信息一起,用於設置光源的參數、以及確定每個Pass該調用多少次。
這種前向着色有多種問題:
1,計算每個幾何體受那些光影響耗費了CPU的時間,更壞的是,這是個O(n*m)的操作。
2,Shader經常需要超過一次以上的Pass來渲染光照,渲染n個燈光,對於複雜的Shader,可能需要O(n)次運算。
3,增加新的光照模型和新的光源類型,可能需要改變所有Effect的源文件。
4,Shader很快就將達到或者超出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),當前最多支持4個MRT。並且驅動要求4個MRT必須相同的bit寬度。RT對顯存佔用過大會增加帶寬,降低cache命中。而簡單格式的RT又會影響畫質。因此決定使用32bit的RT(如A8R8G8B8,R16G16F)或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階段再根據透明度將透明物體和背景逐像素的混合起來。
優點:
光照一致性。半透明的物體也參加延遲渲染,可以接受多光源的光照。
簡單並且健壯。不需要單獨區分不透明物體和半透明物體,不需要單獨的半透明渲染管道。
速度快。只增加了7到10條ps指令,兩張貼圖,只有約2%的性能損失。
缺點:
模糊。在半透明的物體上會有一點模糊,原因是在交織的過程中會有一定信息損失。
邊緣鋸齒。反交織的過程中半透明物體的邊緣會產生一些鋸齒。
只能有一層半透明。
多種材質
在延遲光照的過程中支持多種材質需要如下方案:
在G-buffer階段輸出材質的ID到G-buffer的一個通道中,隨後在lighting階段根據材質ID使用不同的光照函數計算光照。這種方案在sm3.0中使用動態分支的前提下可以很好的工作。
反鋸齒
Dx9API不支持反鋸齒的MRT(需要在後處理上自行解決AntiAlias),Dx10支持。
一種方案是使用超採樣,先渲染到大的RT上,再downsample到正常的大小,得到沒有鋸齒的結果。延遲渲染的效率跟分辨率有很大關係,因此這種方法會極大的降低性能,基本不可取。
另一種方案是使用“intelligentblur”,只模糊物體邊緣的像素:
根據相鄰像素的深度和法線提取物體邊界,然後對提取出的邊界進行模糊。模糊時要避免不正確的泄露。如後面物體的顏色泄露到前面的物體上。總體而言實現會較爲複雜。
另一種方案:pre-lighting
一種pre-zrendering和deferredrendering的結合。G-buffer階段只保存depth和normal,然後計算光照信息到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相比:
優點:
佔用帶寬小,第一遍渲染只輸出normal,depth是自動獲得的。可以用在較老的硬件平臺上,不需要MRT支持。對現有forwardshading管道改動較小,比較容易實現。
缺點:
整個場景需要渲染兩遍,相當於在pre-z和forwardshading中間加了一個lightingstage。
《Resistance2》,《GTAIV》,《MidnightClubLosAngeles》使用了這種技術。CryEngine3中也使用了這種方案。
RT選擇分配
MRT中必須的信息:position(depth),normal,diffuse(texture)
可能需要的信息:specular,power,emissive,ao,materialid這些信息需要在這4個RT上用合理格式,合理的組織。這裏還可以就存儲空間和shader的複雜性做折衷。如只保存depth,然後在光照時計算position。以及用球面座標保存法線。以目前的資料得出的結論是應該儘可能地pack數據,減少內存佔用,多出來的若干條shader指令不會明顯影響性能。
RT的預置,分配是需要針對實際使用而設計的,如何最大化地發揮每一個bit的作用,是一門需要不斷實踐的技術活。
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範例工程,如下圖
可見,DS分成兩個Pss,分別實現了Geometry和Lighting過程。
在VetexShader中,輸出了世界變換座標,視角變換空間座標,及世界變換空間中的法向量。
這裏的PixelShader中由於是簡單例子,只使用了兩個RT,分別輸出世界座標到RT0,輸出法向量到RT1
具體渲染過程可以由下清晰可見,有興趣地可以拿起筆算一下WorldPos,看一下紅黃藍綠黑白的分佈~
ID材質標記
前面提到過MaterialID,這裏也對其做一個實現,順便深入下實際使用中的DS技術。
在新的工程中,Geometry階段使用了兩個Pass,利用標記不同的Alpha值來來實現DS中兩中材質的渲染,當然,實際使用中,Alpha通道通常會被佔用,來存儲EmittColor、SepcStrength等參數,這時候可以使用StencilBuffer來做標記~
在兩個Pass中標記材質直接使用了指定Alpha值,在Compostion中判斷值,但Float值是存在精度問題的,這裏借鑑了DriectX中工具函數的一個做法,加入一個Epsilon值,實現一個約等於的Equal函數,在DX10後,會有Int,就好多了~
實現效果見下圖,在Composition中實現了純色,法線貼圖兩種材質~
AntiAlias
關於抗鋸齒的問題,在KlayGE上看到一篇,拿來以饗讀者
“
從Deferred Shading發明的一天起,anti-alias的問題就一直困擾着所有Deferred的方法。雖然很多無良的遊戲廠商直接在Deferred Rendering的遊戲裏不支持AA,但確實AA對提升畫面質量很有幫助。
Edge AA
在Deferred的框架裏,很自然會想到用Edge AA來處理AA。其過程不外乎:
- 邊緣檢測,得到每個像素“像邊緣的程度”
- 在shader里根據“像邊緣的程度”來控制採樣座標
這本身並不是個複雜的過程,尤其是第二步,非常直截了當了,所以這裏集中討論的是如何進行邊緣檢測。
GPU Gems 2的“Deferred Shading in STALKER”一文提供了一種邊緣檢測的方法,通過把周圍像素的法線差和深度差的和來判斷邊緣,由e_barrier這個參數來定義閾值和比例,而這個參數和分辨率有關。GPU Gems 3的“Deferred Shading in Tabula Rasa”改進了這個過程,只判斷法線差和深度差最大和最小的兩組。由於只是局部的相對量而已,這樣就做到了和分辨率無關的邊緣檢測。KlayGE目前用的也是這種方法,得到的邊緣如下:
Edge
另一個可能用於邊緣檢測的方法是,前面提到了如何恢復出每個pixel的view space position,每個pixel取得周圍4個pixel的位置之後,就可以直接cross得出一個normal,姑且稱爲screen space normal。如果一個像素是連續的,那麼這個normal就會很接近於G-Buffer中保存的normal,否則它們的方向就會差別很大。下圖爲G- Buffer中的normal:
Normal in G-Buffer
這是screen space計算出的normal:
Normal in screen space
把這兩個normal做一次dot,小於某個閾值的就認爲是邊緣,得到:
Screen space normal based edge
利用硬件MSAA作邊緣檢測
前面提到的邊緣檢測結果雖然不錯,但其實都是是參數相關的。能否就用硬件的MSAA來做邊緣檢測呢?在Shader model 3.0以上的GPU,vertex attribute插值的時候可以選擇centroid這個modifier。開啓了centroid的attribute,會選擇覆蓋到的sample中心來插值,而不是像素中心。所以,同一個屬性,如果即有centroid又有不帶centroid的版本都傳給pixel shader,在pixel shader裏面判斷兩者不一致,就表示這個pixel在邊緣上。這樣的話,邊緣的情況就和硬件MSAA完全一致了。但其實MSAA會過渡判斷邊緣,所有三角形的邊緣都會被認出來,即便只是物體內部的。所以謹慎使用。
能不能就用MSAA?
前面討論了那麼多都是基於Edge的AA。在Deferred Lighting框架下,難道就不能直接用MSAA?可以!這也是Deferred Lighting比Deferred Shading優秀的方面之一。Deferred Shading不能直接MSAA的本質原因是在G-Buffer之後,物體幾何信息全部拋棄了。相比Deferred Lighting,在shading pass,物體會被再次渲染一遍,這個時候還是有幾何信息的,如果在shading pass打開了MSAA,就可以像Forward shading那樣利用硬件MSAA了。唯一不同的是,光照來自於lighting pass的texture,而不是從光源計算。就算硬件MSAA,也只是每個pixel執行一次pixel shader,在按照覆蓋情況寫入sample的,所以在這裏視覺上幾乎和Forward shading一樣。
”
實際使用DS
到目前爲止,這裏都是很淺顯的理論探究,自己做了一個簡單地實現,同時渲染幾十盞燈光都可以輕易地保持高FPS,這在傳統的FS體系中是難以想象的,so,感受到DS技術的吸引力了吧?!
閉門造車是難以前進的,多借鑑下成功的實際項目纔是王道,載錄若干如下,其它更多詳見最後的附錄:
載錄引用地址
http://blog.csdn.net/Garuda/article/details/5273106
http://blog.csdn.net/noslopforever/article/details/3951273
《延遲渲染-KlayGE》
其它相關內容
工欲善其事必先利其器,除了必要的實踐,在原理上上做足功課亦是非常重要,下面將相關的文章引用列如下:
[1]DeferredShadingDemo
http://www.puzzledhero.com/projects/pages/deferred_shading_demo.htm
[2]Deferredshading2(dx10)
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.
[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.
[20]WikiDeferredshading
http://en.wikipedia.org/wiki/Deferred_shading
工程下載
/Files/hmxp8/DeferredShading_V1.1.rar