【Unity Rendering】Unity SRP管線的底層執行過程
本文未經允許禁止轉載
作者:Heskey0
B站:https://space.bilibili.com/455965619
郵箱:[email protected]
SRP底層
一. Scriptable Culling
我們先思考一個問題:呼叫ScriptableRenderContex.Cull()
的時候底層會發生什麼
(1) Shadow Culling
先介紹陰影的Culling過程。首先,Unity會遍歷場景中實時的Light,如果勾選了CastShadow,那麼這個Light會遍歷場景中的物體,如果物體也勾選了CastShadow,那麼就會產生陰影。
底層:每個Cast Shadow的Real-time Light會分配一個Shadow的Job,每一個單獨的Job完成對Cast Shadow物體的Cull
(2) Dynamic Objects Culling
我們再來看Unity對動態物體的Culling過程。Unity為場景中所有掛載了Renderer Component的物體維護了一個Renderer的List,IndexList儲存了當前所有可見的Renderer在List中的下標。
【生成IndexList】
List中的Renderer會分組,每一組會分配一個Cull Job,每個Cull Job獨自執行完Cull之後,所有Cull Job的結果合併,生成IndexList。這個過程不需要引入鎖。
【ExtractRenderNodeQueue】
Unity為了保證渲染的速度,引入了RenderNode。對於所有IndexList中對應的Renderer物件,把Renderer裡所有引用型別的資料展開,拷貝到一個struct中,這個struct就是RenderNode
。RenderNode在記憶體中是連續的,RenderNode組成一個佇列,稱為RenderNodeQueue,方便做多執行緒渲染。(注:因為RenderNodeQueue是由IndexList生成的,所以RenderNode對應的Renderer是場景中可見的)
二. Scriptable Draw
現在,我們知道了動態物體Culling的結果就是RenderNodeQueue。Unity接下來就要進行繪製了,先介紹一些繪製過程中的函式。
CommandBuffer.Blit(); CommandBuffer.DrawMesh(); ScriptableRenderContext.DrawRenderers(); ScriptableRenderContext.DrawShadows(); ......
這些函式有什麼關係呢?我們寫著往下看
ExecuteCommandBuffer & DrawRenderers
有4個跟Command相關的List:
dynamic_array<ShadowDrawingSettings> m_DrawShadowCommands;
dynamic_array<DrawRenderersCommand> m_DrawRenderersCommands;
dynamic_array<RenderingCommandBuffer*> m_COmmandBuffers;
dynamic_array<Command> m_Commands;
呼叫DrawRenderers時,會產生1個DrawRenderersCommand新增到對應List中,同時也會產生一個Commands新增到對應List中,用來記錄這個Command的型別及其在List中的下標。
呼叫ExecuteCommandBuffer時,會產生1個CommandBuffer新增到對應List中,同上。
m_Commands儲存了所有command佇列中的每一個command的型別和下標
三. Scriptable Render Loop
在插入Command之後,繼續呼叫ScriptableRenderContext.Submit()
就會執行Render Loop
(1) PrepareDrawRenderersCommand()
我們先看Renderer,material,pass之間的關係
一個Renderer可以有多個材質( 有很多材質插槽 ),一個材質可以有多個Pass
在Culling的過程中,我們已經得到了RenderNodeQueue。然後我們要通過sort決定Draw的順序
【執行過程】
通過RenderNode找到Materials,通過每一個Material找到Pass,通過每一個Pass生成一個
ScriptableLoopObjectData
,這個SctiptableLoopObjectData
中有一個標識,標識它是不是SRP batcher相容的。對所有的
ScriptableLoopObjectData
進行排序
(2) Scriptable Render Loop
【CommandBuffer】
遍歷提交的m_Commands,找到所有的command的型別和下標,然後Execute
【ScriptableLoopObjectData】
遍歷
ScriptableLoopObjectData
,找到相容SRP batcher的,然後扔到SRP batcher渲染器進行渲染,剩餘的會交給傳統的Draw渲染路徑進行渲染。
(5) SRP batcher
SRP Batcher就是
-
把呼叫draw call前,一大堆CPU的設定工作給一口氣處理了,增加了效率。
-
把材質的屬性資料直接永久放入到顯示卡的CBUFFER裡,那隻要資料不變,CPU就可以
不需要把這些資料重新做設定工作。節省了CPU呼叫,增加了效率。
- 用專用的程式碼將引擎的屬性(比如objects transform)直接放入到GPU視訊記憶體,這個專用的程式碼是不是更快更強呢,
官方是這樣麼說的,用的詞語是quickly,就是快。
- SRP Batcher並沒有減少drawcalls,而僅僅是提高了效率。相當於一個人減肥了,減去了多餘的脂肪和水分,但是器官結構啥的一個沒少。總之就是有用。
SRP Batcher的工作原理
SRP Batcher誕生的原因:
在一個Drawcall被一個新的material使用的時候,有很多工作要做。
所以如果場景有越多的materials,就會有越多的CPU必須使用去設定GPU 資料。
傳統的方法是減少DrawCalls的數量去優化CPU渲染效能。
因為Unity必須在呼叫drawcall前設定很多東西。
並且真正的CPU消耗來自那些設定工作,而不是GPU drawcall本身。
Drawcall只是一些Unity向GPU command buffer傳送的bytes。
補充
-
SRP batcher是工作在CPU層面的,它做的事情就是減少SetPass Call。Unity在很久以前就把Draw Call和SetPass Call做了區分:Draw Call本身就是呼叫一個圖形的API,它本身的開銷並不耗。而開銷高是高在我們做切換渲染狀態的時候要提前為顯示卡準備非常多的資料,也就是SetPass Call的工作,準備這些資料往往來說是開銷比較高的。評判標準:不管是預設管線還是SRP,SetPass Call最好都不要超過150,Draw Call的話可以高一些。
-
Vaulkan:SRP batcher在CPU層面的開銷,比較可以關注的一個點是android上的vaulkan,它已經越來越成熟,有不少專案在立項階段把vaulkan作為首選的API。其實使用了vaulkan的話,會有一個明顯的發現就是,vaulkan在CPU上的開銷要遠遠小於OpenGL。所以推薦!!!
-
SRP batcher和GPU Instance用的技術是差不多,如果大家是想繪製單一的物體(像草這樣的),推薦大家使用GPU Instance。但是如果想做正常的場景渲染,比如說場景裡的material多於5個,SRP batcher的速度要比我們手動做GPU Instance要划算的多的。