【Unity Rendering】Unity SRP管線的底層執行過程


本文未經允許禁止轉載
作者:Heskey0
B站:https://space.bilibili.com/455965619
郵箱:3495759699@qq.com


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就是RenderNodeRenderNode在內存中是連續的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;
  1. 調用DrawRenderers時,會產生1個DrawRenderersCommand添加到對應List中,同時也會產生一個Commands添加到對應List中,用來記錄這個Command的類型及其在List中的下標。

  2. 調用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的順序

【執行過程】

  1. 通過RenderNode找到Materials,通過每一個Material找到Pass,通過每一個Pass生成一個ScriptableLoopObjectData,這個SctiptableLoopObjectData中有一個標識,標識它是不是SRP batcher兼容的。

  2. 對所有的ScriptableLoopObjectData進行排序

(2) Scriptable Render Loop

【CommandBuffer】

遍歷提交的m_Commands,找到所有的command的類型和下標,然后Execute

【ScriptableLoopObjectData】

遍歷ScriptableLoopObjectData,找到兼容SRP batcher的,然后扔到SRP batcher渲染器進行渲染,剩余的會交給傳統的Draw渲染路徑進行渲染。

(5) SRP batcher

SRP Batcher就是

  1. 把調用draw call前,一大堆CPU的設置工作給一口氣處理了,增加了效率。

  2. 把材質的屬性數據直接永久放入到顯卡的CBUFFER里,那只要數據不變,CPU就可以

不需要把這些數據重新做設置工作。節省了CPU調用,增加了效率。

  1. 用專用的代碼將引擎的屬性(比如objects transform)直接放入到GPU顯存,這個專用的代碼是不是更快更強呢,

官方是這樣么說的,用的詞語是quickly,就是快。

  1. 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。

補充

  1. SRP batcher是工作在CPU層面的,它做的事情就是減少SetPass Call。Unity在很久以前就把Draw Call和SetPass Call做了區分:Draw Call本身就是調用一個圖形的API,它本身的開銷並不耗。而開銷高是高在我們做切換渲染狀態的時候要提前為顯卡准備非常多的數據,也就是SetPass Call的工作,准備這些數據往往來說是開銷比較高的。評判標准:不管是默認管線還是SRP,SetPass Call最好都不要超過150,Draw Call的話可以高一些。

  2. Vaulkan:SRP batcher在CPU層面的開銷,比較可以關注的一個點是android上的vaulkan,它已經越來越成熟,有不少項目在立項階段把vaulkan作為首選的API。其實使用了vaulkan的話,會有一個明顯的發現就是,vaulkan在CPU上的開銷要遠遠小於OpenGL。所以推薦!!!

  3. SRP batcher和GPU Instance用的技術是差不多,如果大家是想繪制單一的物體(像草這樣的),推薦大家使用GPU Instance。但是如果想做正常的場景渲染,比如說場景里的material多於5個,SRP batcher的速度要比我們手動做GPU Instance要划算的多的。


本文未經允許禁止轉載
作者:Heskey0
B站:https://space.bilibili.com/455965619
郵箱:3495759699@qq.com


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM