圖形渲染管線是圖形渲染引擎物件繪制的流程邏輯叫榕,平時在工作過程中經(jīng)常會碰到各種跟管線相關(guān)的專業(yè)術(shù)語:前向渲染(Forward Rendering)玩裙,延遲渲染(Deferred Rendering),F(xiàn)orward+齿税, Forward Clustering等等彼硫,那么這些管線究竟對應(yīng)的是怎樣的工作流程,他們之間的優(yōu)劣以及應(yīng)用場景又是怎么樣的呢凌箕?為了解答這個問題拧篮,因此新開一篇文章,嘗試通過聚少成多的方式牵舱,一點點的匯總被業(yè)界所接受的各種渲染流程串绩,因為時間關(guān)系,文章可能不是一蹴而就的芜壁,會不定期更新礁凡。
為了統(tǒng)一敘述,下面的渲染管線名稱統(tǒng)一使用英文詞匯慧妄。
Forward Rendering
Forward Rendering就是俗稱的前向渲染管線顷牌,是最為傳統(tǒng)最為簡單的渲染管線,也是目前移動端上應(yīng)用最為廣泛的渲染管線腰涧。
所謂的前向渲染韧掩,就是將物件串成一個隊列,一個接一個的進行渲染窖铡,通常有兩種實現(xiàn)路徑疗锐,這兩種實現(xiàn)路徑都對應(yīng)于兩個嵌套的循環(huán),區(qū)別在于循環(huán)的主題順序有所不同费彼,按照Nvidia的說法滑臊,可以分別用Single-Pass Lighting與Multipass-Lighting對之進行區(qū)分,實現(xiàn)的偽代碼分別給出如下:
//Single-Pass Lighting
for obj in obj_list
for light in light_list
lighting(obj, light);
//Multi-Pass Lighting
for light in light_list
for obj obj_list
lighting(obj, light);
所謂的Single-Pass Lighting指的是影響物體的所有的光照一次性計算完畢箍铲,而Multi-Pass Lighting則是以Light作為循環(huán)主體雇卷,物件作為循環(huán)副體,分多次完成物體上光照的處理颠猴。
前向渲染管線實現(xiàn)思路比較簡潔关划,但是其缺陷也非常的明顯:
- 從上面的偽代碼我們知道,整個場景渲染時光照計算時間復(fù)雜度為MxN(M為物件數(shù)目翘瓮,N為動態(tài)光源數(shù)目)贮折,因此如果動態(tài)光源數(shù)目增加,場景渲染的效率就會直線下降资盅,因此通常會對場景中同時存在的動態(tài)光源數(shù)目進行限制筐带,在當前的硬件條件下剩燥,通常限制為4或者6。當然偽代碼里面給的算法都比較粗淺,有極大的優(yōu)化空間旺拉,比如Multi-Pass Lighting中赌莺,可以在CPU中挑出受某個光照影響的物件列表秃诵,副循環(huán)中只需要傳入這個物件列表而非所有物件列表降低渲染消耗。
- 如果不做特殊處理的話枢析,場景渲染的overdraw比較高。針對這個問題,有比較多的解決方案姐扮,比如使用大塊物件制作的HZB讹堤,以及將動態(tài)物件使用depth-only模式提前繪制一遍的Early-Z Cull算法等,都是通過增強剔除力度的方法來降低overdraw,不過這些方案本身也不是沒有消耗的瘾境,因此通常在使用的時候會需要在收支之間做一個平衡歧杏。
另外,從物件渲染順序來看迷守,前向渲染的渲染邏輯通常按照如下邏輯完成:
Opaque -> Translucent -> PostProcess
Deferred Rendering
延遲渲染Deferred Rendering管線的引出是為了解決前向渲染管線中光源數(shù)目過多時渲染效率低下的問題犬绒,簡單來說,延遲渲染通過兩個pass完成:
- Geometry Pass兑凿,完成物體的幾何數(shù)據(jù)處理凯力,將光照計算所需要的數(shù)據(jù)提取出來寫入到GBuffer中
- Lighting Pass,通過一個后處理對每盞光源所覆蓋的像素進行PS計算礼华,輸出結(jié)果到FrameBuffer中
延遲渲染的優(yōu)點在于通過兩個pass極大的降低了Shading時候的Overdraw咐鹤,其缺點在于
- 具有較高的帶寬要求
- 透明物體的渲染需要額外的pass處理
- 不支持MSAA(帶寬進一步加劇)
- 只支持單一的Lighting Model圣絮。因為Shading Pass是通過渲染Light Geometry來完成的祈惶,而Light Geometry只能綁定一個Pixel Shader(不能為不同的Light Geometry綁定不同的Pixel Shader嗎?)扮匠,因而如果希望實現(xiàn)多個lighting model的話捧请,用延遲渲染就不合適了。
關(guān)于延遲渲染棒搜,此前已經(jīng)寫過一篇比較詳細的文章疹蛉,此處不再贅述,有需要的同學請自行移步帮非。
Tiled Deferred Shading
由于延遲渲染的實現(xiàn)需要較高的帶寬氧吐,而移動端在帶寬能力上嚴重不足,因此移動端常用的渲染管線是前向渲染末盔;而我們前面介紹過,前向渲染的缺點在于動態(tài)光源較多的時候座慰,場景負載較高陨舱,且由于較高的overdraw,所以如果不做特殊處理版仔,在移動端上的消耗還是非常高游盲,因此有人嘗試尋找一種性能更優(yōu)的解決方案。
延遲渲染的缺陷在于帶寬要求高蛮粮,那么一種非常直觀的思路就是益缎,能否將屏幕劃分成多個子區(qū)域,分塊進行渲染然想,這樣就能將每次渲染的帶寬需求降低了莺奔。乍一聽這個想法是一個餿主意,因為如果物件跨越多個區(qū)域的話变泄,可能就需要進行多次渲染令哟,但實際證明恼琼,只要框架設(shè)計的好,收益遠比付出高屏富,再加上硬件支持晴竞,這個方案很快就稱為移動端硬件的標配(這個方案最開始是Imagination Tech公司提出,簡稱TBDR狠半,應(yīng)用在蘋果的PowerVR芯片上噩死,后來陸續(xù)被其他Android芯片廠商跟進發(fā)展)。
Deferred Lighting
Deferred Lighting是與Deferred Shading名字非常相似的一種渲染管線神年,曾經(jīng)在CryEngine 3中被使用已维。Deferred Shading是將所有的著色處理都放到PostProcess階段完成,而這個步驟需要較多的輸入數(shù)據(jù)瘤袖,因此對于帶寬具有較高要求衣摩,而Deferred Lighting則是只將光照部分放到后處理階段完成(光照計算只需要提供對應(yīng)的法線數(shù)據(jù)與高光Power數(shù)據(jù)就已經(jīng)足夠),相對而言捂敌,所需要的輸入數(shù)據(jù)有所減少艾扮,因而可以降低帶寬的要求,提升渲染性能占婉。
通過對Deferred Shading進行分析泡嘴,發(fā)現(xiàn)G-buffer數(shù)據(jù)中Diffuse Color跟Specular Color占了很大一部分,而這部分是Lighting所不需要的數(shù)據(jù)逆济,因此考慮將Lighting單獨做延遲處理來降低帶寬消耗酌予,其基本實現(xiàn)步驟給出如下:
- 跟Deferred Shading一樣,經(jīng)過一個Geometry Pass奖慌,這個Pass輸出的數(shù)據(jù)只包含三項:
1.1 Normal
1.2 Specular Power
1.3 Depth
除了Depth之外抛虫,其余兩項可以共用一個RenderTarget(如果對發(fā)現(xiàn)進行壓縮的話,只需要三個通道即可)简僧。 - Lighting Pass建椰,對G-Buffer數(shù)據(jù)進行取用,計算各個輸入光源作用后的Lighting Accumulation Buffer
- 進行一遍額外的Geometry Pass岛马,使用Lighting Accumulation Buffer作為光照結(jié)果與Diffuse Color進行混合得到最終的輸出Color棉姐,由于已經(jīng)進行過一遍Geometry Pass,此時有此前的Depth Buffer的作用啦逆,這一個Geometry Pass可以節(jié)省大量的PS計算(相當于屏幕空間渲染)伞矩,效率非常高。
跟Deferred Shading一樣夏志,上述步驟只針對不透明物件乃坤,透明物件的渲染流程在不透明物件渲染完成之后,按照前向渲染完成。
這種方案相對于Deferred Shading而言侥袜,其優(yōu)點在于蝌诡,具有更小的帶寬要求。
Forward+
Forward+渲染管線也叫Tiled Fowrad Rendering枫吧,是AMD在EUROGRAPHICS 2012上首次提出的渲染管線浦旱,其實現(xiàn)也是通過3個Pass來完成,參考文獻[3]中給出了此方案的源碼與相應(yīng)的輸出結(jié)果九杂,下面介紹中的部分資源就來源于此:
1. Depth Pass
前面介紹過的Tiled Deferred Rendering第一遍Geometry Pass主要用于獲取Tiled G-Buffer數(shù)據(jù)颁湖,而這里的Depth Pass只是為了獲取屏幕空間的Depth數(shù)據(jù),方便下一步進行逐Tile的Light Culling例隆,也是為了避免后面Geometry Shading Pass的Overdraw甥捺。
2. Light Culling Pass
眾所周知,前向渲染之所以慢镀层,就是因為每個像素需要考慮每盞燈光的影響镰禾,為了減少浪費,可以對每盞燈光影響的像素范圍進行計算唱逢,使得不受光照影響的像素無需考慮此盞燈光的輸入吴侦。
這里會將屏幕空間劃分成四方的Tile,Tile尺寸過大坞古,這個算法優(yōu)化幅度就非常有限备韧,而如果Tile尺寸過小就會導(dǎo)致Light Cull復(fù)雜度增加,都會導(dǎo)致效率的下降痪枫,一般來說织堂,此Tile可以跟移動端硬件劃分的Tile統(tǒng)一起來,最大程度保證渲染效率奶陈。
根據(jù)Tile Size以及上一步獲取到的Depth Buffer易阳,獲取每個Tile的Depth Extent,并根據(jù)每個Tile的Sub Frustum與Light的作用范圍計算出影響到當前Tile的Light List吃粒,整個過程可以通過Compute Shader完成闽烙,有Scatter跟Gather兩種實現(xiàn)方案,Gather方案中声搁,每個Tile可以通過一個Thread Group(每個Thread對應(yīng)于一個像素)來統(tǒng)計,輸出Tile的Depth Extent(即Min Depth與MAX Depth)捕发,之后進行Light與Sub Frustum的相交檢測時疏旨,也可以通過Thread Group來完成,并行進行多盞燈光的Cull處理扎酷。
AABB/Sphere Test精度問題
如果按照普通的Frustum Face Cull的方法處理Light對于Tile是否可見檐涝,可能會導(dǎo)致一些原本不可見的Light被識別成可見,從而導(dǎo)致Shading消耗增加,如下圖所示:
黑色梯形表示的Frustum與點光作用范圍的藍色圓形谁榜,F(xiàn)rustum上下左右邊界都是與圓形相交的幅聘,因此會被錯誤的認為此Light對于Tile可見,這個問題Gareth Thomas在GDC 2015 的Advancements in Tiled-Based Compute Rendering中給出了解決方案:
如上圖所示窃植,由Min Z & MAX Z以及Tile的上下左右邊界構(gòu)成的Frustum帝蒿,其對應(yīng)的AABB由紅色虛線框表示,要判斷此Tile對于點光是否可見巷怜,只需要以AABB邊界為圓心葛超,以點光作用范圍為半徑外擴,得到的圓角方形與點光中心點進行相交檢測延塑,其算法實現(xiàn)如下圖所示:
其實施結(jié)果對比如下圖所示:
渲染效率大概有11%~14%的提升绣张。
min/max extent統(tǒng)計算法
此外,在統(tǒng)計Tile深度的Min/Max數(shù)據(jù)時关带,傳統(tǒng)的atomic min & max算法也可以通過Parallel Reduction算法來提升效率:
每個Tile的Frustum由Min/Max Depth來界定侥涵,而 一般來說Tile的Min/Max是通過atomic min & max操作計算得到的,tile中的每個像素對應(yīng)一個thread宋雏,但這種做法效率并不高芜飘,比如每個tile包含16個像素,那么就需要經(jīng)過15個比較來得到最終的結(jié)果好芭,在比較的結(jié)果時候燃箭,如果發(fā)現(xiàn)待寫入結(jié)果被鎖定,還需要進行等待舍败,但實際上這個處理過程可以分成多層完成:先將16個像素分成8對招狸,兩兩比較,之后將8個結(jié)果分成4對兩兩比較邻薯,最終比較次數(shù)為:8+4+2+1=15裙戏。雖然比較次數(shù)沒有減少,但是在比較的過程中不需要原子操作進行鎖定厕诡,因此效率會更高累榜。
Depth Discontinuities問題
在某個Tile中如果存在較大的Depth Discontinuities,那么在進行光照剔除時可能會導(dǎo)致false positive灵嫌,即light對于此tile沒有貢獻壹罚,但是卻被包裹在了tile frustum中,如下圖所示:
對于這個問題寿羞,目前有較多的解決方案猖凛,如Harada在12年給出的2.5D culling算法通過將min/max范圍分割成更小的范圍,之后針對每個小范圍統(tǒng)計light 覆蓋區(qū)間與depth覆蓋區(qū)間绪穆,如下圖所示:
2.5D Culling方案的不足在于劃分較多會導(dǎo)致計算復(fù)雜度的線性增加辨泳,另一個方案則是GPU Pro 6中記載的HalfZ方案虱岂,此方案的目的不在于將false positive light移除,而在于將這種情況下的depth range一分為二菠红,上半部分與下半部分分別進行l(wèi)ight計算第岖,相對于原有實現(xiàn)方案,此方案復(fù)雜度并沒有過多增加试溯,但是在光照密度較高的情況下蔑滓,可以將單個tile中的light計算復(fù)雜度減半:
相對于HalfZ方案,GPU Pro 6還給出了另外一種更優(yōu)越的方案Modified HalfZ耍共,這種方案是在HalfZ的基礎(chǔ)上烫饼,進一步收縮兩個Depth Range的范圍(需要對Tile上的Depth Range進行兩次Depth Extent計算),保證收縮后的兩個Depth Range剛好包裹兩頭的Geometry Depth Range试读,如下圖所示:
這幾種方案的性能對比如下圖所示:
可以看到杠纵,Modified HalfZ方案表現(xiàn)最佳,此外不同的Tile Size對于最終的實施性能也有比較顯著的影響:
在一定范圍內(nèi)钩骇,Tile尺寸較斜仍濉(32x32Tiles)時具有較高的性能,具體表現(xiàn)可能需要根據(jù)應(yīng)用進行單獨測試倘屹。
每個Tile Cull之后的Visible Light Indices被寫入到Shader Storage Buffer中银亲。
3. Geometry Shading Pass
這個Pass就是普通的Forward渲染Pass(需要注意,這里的物件不僅僅局限于不透明物件纽匙,透明物件也可以一起提交务蝠,不過如果需要提交透明物件的話,要保證絕對精確烛缔,前面Depth Pass可能需要將透明物件的深度寫入到Color Buffer中馏段;但如果不需要保證絕對精確,也可以直接使用不透明物件的深度進行光照剔除践瓷,之后將光照應(yīng)用到所有物件上院喜,其結(jié)果可能會有部分光源損失),與普通Forward Pass不同的是晕翠,這里每個pixel在執(zhí)行PS的時候不是針對所有l(wèi)ight進行遍歷計算喷舀,而是計算當前像素所歸屬的Tile,之后獲取Tile中的Light列表淋肾,在這個上面進行光照計算硫麻。
這里給出了Forward+與Deferred Rendering方案的性能對比,可以看到樊卓,雖然增加了一個Pass庶香,但實際上其渲染性能要優(yōu)于Deferred Rendering(這個優(yōu)化應(yīng)該是來源于Light Culling)
下面列出兩者分階段的耗時對比,中間的‘<’表示優(yōu)化简识,‘<<’表示大幅優(yōu)化:
Clustered Forward Rendering
Forward+是在屏幕空間中劃分Tile進行光源過濾來降低消耗的赶掖,這個劃分是在XY 2D平面進行的,而Clustered Forward Rendering則是在這個基礎(chǔ)上更進一步七扰,在Depth方向上也同樣進行一次劃分奢赂,進一步縮小光照的影響范圍,降低光照計算的浪費颈走。因為劃分的結(jié)果是3D的Frustum膳灶,每個Frustum被稱為一個Cluster,這就是Cluster的由來立由。
為什么要在Depth方向上進行劃分轧钓?
在部分場景中,渲染的距離比較廣锐膜,在局部光源比較密集的情況下毕箍,單個Tile中可能存在著較多的光源,這些光源在深度范圍上比較分散道盏,如果使用Forward+ Rendering管線而柑,會存在較大的浪費,因此考慮在深度方向上增加一維劃分荷逞,希望通過這種方式降低光照計算的消耗媒咳。
在Depth方向上的劃分具體是如何實施的?
在Depth方向上的劃分并不是線性的(指數(shù)劃分)种远,各個Cluster在Depth方向上的劃分是對齊的(是否可以考慮非對齊劃分涩澡?可以仿照Forward+,先通過一個Depth Pass獲取各個Tile上的深度范圍坠敷,之后在此范圍上進行劃分)
為什么Depth上不做線性劃分妙同?
由于透視相機的作用,需要保留更多近處細節(jié)常拓,導(dǎo)致近大遠小渐溶,如果按照線性劃分,性價比可能會有所下降弄抬。
整個實現(xiàn)過程如果做成與Forward+類似茎辐,那么就同樣需要分成三個Pass:
- Depth Pass,用于實現(xiàn)Cluster劃分與Overdraw過濾
- Cluster Filling掂恕,通過Compute Shader完成(需要三個Pass)拖陆,輸出Lighting List Info
Subpass1:統(tǒng)計影響各個Cluster的Light數(shù)目
Subpass2:根據(jù)上一個subpass的結(jié)果,計算當前Cluster對應(yīng)的Light在Light Indices數(shù)組中的起始位置
Subpass3:填充Light Indices數(shù)組懊亡,數(shù)組中存儲的是影響各個Cluster的Light在全局Light Info數(shù)組中的索引
- Shading Pass依啰,逐Cluster渲染Geometry,進行Shading處理店枣。
除了上述實現(xiàn)方案之外速警,也可以用不同于Forward+的實現(xiàn)方案來完成叹誉,我們說過,Cluster的劃分是規(guī)則的闷旧,也就是不需要任何的Depth信息长豁,就可以完成,那么就不必像Forward+一樣忙灼,使用Depth Pass來一遍強制的Early-Z匠襟,如果沿著這種思路,那么實現(xiàn)方案需要2個Pass(其實私下里認為该园,還是在前面加一個Depth Pass效果更好酸舍,可以避免大量的Overdraw):
- Cluster Filling Pass,跟上面的Pass2一樣里初,填充各個Cluster的光照信息
- Shading Pass啃勉,不需要逐Cluster進行,而是全場景一遍Geometry Shading青瀑,在PS中根據(jù)Pixel Depth恢復(fù)期世界坐標璧亮,并進而找到對應(yīng)的Cluster,之后使用Cluster對應(yīng)的Lighting數(shù)據(jù)進行光照處理斥难。
具體性能表現(xiàn)又是如何的枝嘶?
‘Detroit: Become Human’ 方案只給出了在主機上的時間消耗,沒有給出與其他方案的對比數(shù)據(jù):
Avalanche Studios的Emil Persson在Siggraph 13上分享的Clustered Forward Rendering方案與Deferred Rendering方案的性能對比(如果有與Forward+的性能對比就好了):
參考文獻
1. 游戲引擎中的光照算法 - 知乎fengliancanxue
2. Deferred Shading VS Deferred Lighting
3. Forward+ Renderer
4. Forward+ (EUROGRAPHICS 2012) Slides
5. Clustered Forward Rendering and Anti-Aliasing in ‘Detroit: Become Human’ - GDC 2018
6. Practical Clustered Shading - Emi Persson - Siggraph 13
7. Advancements in Tiled-Based Compute Rendering