@author: 黑袍小道
隨緣查看
說明
由於搬山的渲染這部分擔心自己理解錯誤,故而搬移官方下,后面整個完成再反過來更新
(這當且僅當做Unreal的幫助文檔)。
圖形編程
模塊
渲染器代碼存在於其自身的模塊中。此模塊將編譯為非單塊版本的一個 dll 文件。這可以使迭代更快,因為在渲染代碼變更時無需重新鏈接整個應用程序。渲染器模塊取決於引擎,因為其擁有許多向引擎的回調。然而當引擎需要調用渲染器中的某些代碼時,這會通過某個接口來完成,通常為 IRendererModule 或 FSceneInterface。
場景代表
在 UE4 中,渲染器所見的場景由基本組件和 FScene 中存儲的多種其他結構的列表定義。將維護一個基元的八叉樹,用於加速空間查詢。
主要場景類
UE4 中擁有和游戲線程並行運行的渲染線程。
主要的類為:
類 |
描述 |
UWorld |
包含多個可交互的 Actor 和組件的世界場景。關卡可以流送進入和退出世界場景,且程序中可以同時有多個世界場景處於激活狀態。 |
ULevel |
一同加載/卸載並保存在同一地圖文件中的 Actor 和組件合集。 |
USceneComponent |
需要添加到 FScene 中的任意對象的基礎類,如光照、網格體、霧等。 |
UPrimitiveComponent |
可渲染或進行物理交互的任意資源的基礎類。也可以作為可視性剔除的粒度和渲染屬性規范(投射陰影等)。與所有 UObjects 一樣,游戲線程擁有所有變量和狀態,渲染線程不應直接對其進行訪問。 |
ULightComponent |
代表光源。渲染器負責計算和添加其對場景的貢獻。 |
FScene |
UWorld 的渲染器版本。對象僅在其被添加到 FScene(注冊組件時調用)后才會存在於渲染器中。渲染線程擁有 FScene 中的所有狀態,游戲線程無法直接對其進行修改。 |
FPrimitiveSceneProxy |
UPrimitiveComponent 的渲染器版本,為渲染線程映射 UPrimitiveComponent 狀態。存在於引擎模塊中,用於划分為子類以支持不同類型的基元(骨架、剛體、BSP 等)。實現某些非常重要的函數,如 GetViewRelevance、DrawDynamicElements 等。 |
FPrimitiveSceneInfo |
內部渲染器狀態(FRendererModule 實現專有),對應於 UPrimitiveComponent 和 FPrimitiveSceneProxy。存在於渲染器模塊中,因此引擎看不到它。 |
FSceneView |
單個視圖到一個 FScene 的引擎代表。視圖可以通過對 FSceneRenderer::Render 的不同調用的不同視圖來渲染(多編輯器視口)或通過對 FSceneRenderer::Render 的同一調用中的多個視圖來渲染(分屏游戲)。為每個幀構建新視圖。 |
FViewInfo |
視圖的內部渲染器代表,存在於渲染器模塊中。 |
FSceneViewState |
ViewState 存儲有關在多個幀中需要的某個視圖的私有渲染器信息。在游戲中,每個 ULocalPlayer 只有一個視圖狀態。 |
FSceneRenderer |
為每個幀創建的類,用於封裝跨幀的臨時對象。 |
下面按其所在的模塊列出了各種主類。
引擎模塊 |
渲染器模塊 |
UWorld |
FScene |
UPrimitiveComponent / FPrimitiveSceneProxy |
FPrimitiveSceneInfo |
FSceneView |
FViewInfo |
ULocalPlayer |
FSceneViewState |
ULightComponent / FLightSceneProxy |
FLightSceneInfo |
以及相同類(按哪個線程對其狀態擁有所有權進行排列)。
游戲線程 |
渲染線程 |
UWorld |
FScene |
UPrimitiveComponent |
FPrimitiveSceneProxy / FPrimitiveSceneInfo |
|
FSceneView / FViewInfo |
ULocalPlayer |
FSceneViewState |
ULightComponent |
FLightSceneProxy / FLightSceneInfo |
材質類
類 |
描述 |
FMaterial |
連接用於渲染的材質的接口。可用於訪問材質屬性(如混合模式)。包含被渲染器用於檢索個體着色器的着色器地圖。 |
FMaterialResource |
UMaterial 的 FMaterial 接口實現。 |
FMaterialRenderProxy |
材質在渲染線程上的代表。可用於訪問 FMaterial 接口和各個標量、向量和紋理參數。 |
UMaterialInterface |
[abstract] 用於材質功能的游戲線程接口。用於檢索用於渲染的 FMaterialRenderProxy 和用作來源的 UMaterial。 |
UMaterial |
材質資源。授權為節點圖形。計算用於着色、設置混合模式等的材質屬性。 |
UMaterialInstance |
[abstract] UMaterial 的實例。使用 UMaterial 中的節點圖形,但提供不同參數(標量、向量、紋理、靜態切換)。每個實例都有一個父項 UMaterialInterface。因此,材質實例的父項可能是 UMaterial 或另一個 UMaterialInstance。這會形成一個鏈,最終通往 UMaterial。 |
UMaterialInstanceConstant |
只能在編輯器中修改的 UMaterialInstance。可以提供標量、向量、紋理和靜態開關參數。 |
UMaterialInstanceDynamic |
可以在運行時修改的 UMaterialInstance。可提供標量、向量和紋理參數。無法提供靜態開關參數,且無法成為另一 UMaterialInstance 的父項。 |
基元組件和代理
基元(就是Prim)組件是可視性和相關性確定的基本單位。例如,遮蔽和視錐剔除都是按基元進行的。因此在設計系統時,考慮組件的大小十分重要。每個組件都有一個邊界,用於多種操作,如剔除、陰影投射和光照影響確定。
組件只有在注冊之后才會對場景(以及渲染器)可見。
更改組件屬性的游戲線程代碼必須調用組件上的 MarkRenderStateDirty(),將更改傳播到渲染線程。
FPrimitiveSceneProxy 和 FPrimitiveSceneInfo
FPrimitiveSceneProxy 是 UPrimitiveComponent 的渲染線程版本,用於根據組件類型划分子類。
它存在於引擎模塊中,並在渲染通道中調用函數。
FPrimitiveSceneInfo 是基元組件狀態,為渲染器模塊私有。
重要的 FPrimitiveSceneProxy 方法
函數 |
描述 |
GetViewRelevance |
在幀的開始從 InitViews 調用,並返回填充的 FPrimitiveViewRelevance。 |
DrawDynamicElements |
調用,以便在某代理相關的任何通道中繪制該代理。僅在代理表示自己擁有動態相關性時調用。 |
DrawStaticElements |
調用以在基元與游戲線程相連時提交代理的 StaticMesh 元素。僅在代理表示自己擁有靜態相關性時調用。 |
場景渲染順序
渲染器按照其希望將數據整合給渲染目標的順序處理場景。例如,僅 Depth 的通道會比 Base 通道先渲染,先填充 Heirarchical Z (HiZ),從而降低基礎通道中的着色消耗。此順序是按通道函數在 C++ 中調用的順序靜態決定的。
相關性
FPrimitiveViewRelevance 是說明通道與基元相關的信息。基元可能有存在不同相關性的多個元素,因此 FPrimitiveViewRelevance 相當於一個所有元素的相關性的邏輯 OR。這表示基元可以同時存在不透明和透明相關性,或動態和靜態相關性;它們並非相互排斥。
FPrimitiveViewRelevance 還會顯示基元是否需要使用動態 (bDynamicRelevance) 和/或靜態 (bStaticRelevance) 渲染路徑。
繪制規則
繪制規則包括通過通道特定的着色器渲染網格體的邏輯。它們使用 FVertexFactory 接口來抽取網格體類型,並使用 FMaterial 接口來抽取材質詳情。在最底層,一條繪制規則會負責一組網格體材質着色器以及一個頂點工廠,將頂點工廠的緩沖區與渲染硬件接口 (RHI) 綁定,將網格體材質着色器與 RHI 綁定,設置適當的着色器參數,然后執行 RHI 繪制調用。
繪制規則方法
函數 |
描述 |
Constructor |
從給定的頂點工廠和材質着色器地圖,並存儲這些引用。 |
CreateBoundShaderState |
為繪制規則創建 RHI 邊界着色器狀態。 |
Matches/Compare |
提供排列繪制規則與靜態繪制列表中的其他項目的方法。Matches 必須比較 DrawShared 依賴的所有因素。 |
DrawShared |
設置在從 Matches 返回 True 的繪制規則之間一致的 RHI 狀態。例如,大多數繪制規則會為材質和頂點工廠排序,因此着色器參數只依賴可以設置的材質,並且可以綁定特定於該頂點工廠的頂點緩沖區。應盡可能在此處設置狀態,而非在 SetMeshRenderState 設置,因為 DrawShared 在靜態渲染路徑中調用較少。 |
SetMeshRenderState |
設置特定於此網格體的 RHI 狀態,或 DrawShared 中未設置的任何項目。這比 DrawShared 調用的次數多得多,因此此處性能非常重要。 |
DrawMesh |
實際發出 RHI 繪制調用。 |
渲染路徑
UE4 擁有動態路徑(能夠提供更多的控制,但轉換較慢)和靜態渲染路徑(緩存盡可能靠近 RHI 級別的場景轉換)。差異基本上是整體上的,因為它們都在最底層使用繪制規則。應確保各個渲染通道(繪制規則)在需要時能夠同時處理兩個渲染路徑。
動態渲染路徑
動態渲染路徑使用 TDynamicPrimitiveDrawer 並對每個要渲染的基元場景代理調用 DrawDynamicElements。需要使用動態路徑來渲染的一組基元通過 FViewInfo::VisibleDynamicPrimitives 來跟蹤。每個渲染通道都需要在此陣列上迭代,並調用各個基元上的 DrawDynamicElements。隨后,代理的 DrawDynamicElements 需按需要組合出多個 FMeshElements,並將其隨 DrawRichMesh 或 TDynamicPrimitiveDrawer::DrawMesh 提交。這樣最終會創建一個新的臨時繪制規則,調用 CreateBoundShaderState、DrawShared、SetMeshRenderState 以及 DrawMesh。
靜態渲染路徑
靜態渲染路徑通過靜態繪制列表實現。網格體在連接到場景時會插入到繪制列表中。在插入過程中,將調用代理上的 DrawStaticElements,以收取 FStaticMeshElements。隨后將隨 CreateBoundShaderState 的結果創建並存儲一個繪制規則實例。新的繪制示例將根據其 Compare 和 Matches 函數排序,並插入到繪制列表中的適當位置(參見 TStaticMeshDrawList::AddMesh)。在 InitViews 中,包含靜態繪制列表中的可見性數據的 bitarray 會初始化並傳遞到 TStaticMeshDrawList::DrawVisible(實際繪制繪制列表的地方)。DrawShared 只會對所有相互匹配的繪制規則調用一次,而 SetMeshRenderState 和 DrawMesh 會對每個 FStaticMeshElement(參見 TStaticMeshDrawList::DrawElement)調用。
靜態渲染路徑會將許多工作移動連接時間,這會大大加快渲染時的場景轉換。在渲染線程上針對靜態網格體時,靜態繪制列表的渲染會快 3 倍,從而允許場景中出現多得多的靜態網格體。由於靜態繪制列表會在連接時間緩存數據,因此它們僅能緩存與視圖無關的狀態。很少重新連接但經常需要渲染的基元非常適合靜態繪制列表。
靜態渲染路徑可能會暴露出 bug,因為它每個狀態桶只調用 DrawShared 一次。這些 bug 可能會很難發現,因為它們取決於場景中網格體的渲染順序和連接順序。特別的視圖模式(如僅光照、無光照等)會強制所有基元使用動態路徑,因此如果在強制動態渲染路徑時 bug 消失,則其很可能是由於某繪制規則的 DrawShared 和/或 Matches 函數的錯誤實現而出現的。
總體渲染順序
下面將說明從 FDeferredShadingSceneRenderer::Render 開始渲染一個幀時的控制流程:
操作 |
描述 |
GSceneRenderTargets.Allocate |
按需要重新分配全局場景渲染目標,使其對當前視圖足夠大。 |
InitViews |
通過多種剔除方法為視圖初始化基元可見性,設立此幀可見的動態陰影、按需要交叉陰影視錐與世界場景(對整個場景的陰影或預陰影)。 |
PrePass / Depth only pass |
RenderPrePass / FDepthDrawingPolicy。渲染遮擋物,對景深緩沖區僅輸出景深。該通道可以在多種模式下工作:禁用、僅遮蔽,或完全景深,具體取決於活動狀態的功能的需要。該通道通常的用途是初始化 Hierarchical Z 以降低 Base 通道的着色消耗(Base 通道的像素着色器消耗非常大)。 |
Base pass |
RenderBasePass / TBasePassDrawingPolicy。渲染不透明和遮蓋的材質,向 GBuffer 輸出材質屬性。光照圖貢獻和天空光照也會在此計算並加入場景顏色。 |
Issue Occlusion Queries / BeginOcclusionTests |
提出將用於下一幀的 InitViews 的延遲遮蔽查詢。這會通過渲染所查詢物體周圍的相鄰的框、有時還會將相鄰的框組合在一起以減少繪制調用來完成。 |
Lighting |
陰影圖將對各個光照渲染,光照貢獻會累加到場景顏色,並使用標准延遲和平鋪延遲着色。光照也會在透明光照體積中累加。 |
Fog |
霧和大氣在延遲通道中對不透明表面進行逐個像素計算。 |
Translucency |
透明度累加到屏外渲染目標,在其中它應用了逐個頂點的霧化,因而可以整合到場景中。光照透明度在一個通道中計算最終光照以正確融合。 |
Post Processing |
多種后期處理效果均通過 GBuffers 應用。透明度將合成到場景中。 |
以上是相當簡單概略的介紹。如需了解詳情,請通讀相關代碼或輸出的"profilegpu"日志。
渲染硬件接口 (RHI)
RHI 是平台特定的圖形 API 之上的一個薄層。UE4 中的 RHI 抽象層盡可能低,這樣大多數功能都能以與平台無關的代碼寫成,從而能夠在支持所需功能層級的任何平台上運行。
功能集將量化到 ERHIFeatureLevel 中,降低復雜程度。如果平台無法支持某個功能層級所需的全部功能,則其必須降低層級,直至能夠全部支持。
功能層級 |
描述 |
SM5 |
通常對應於 D3D11 Shader Model 5,但由於 OpenGL 4.3 限制,僅有 16 種紋理可以使用。支持曲面細分、計算着色器和立方體貼圖陣列。支持延遲着色路徑。 |
SM4 |
對應 D3D11 Shader Model 4,這與 SM5 基本相同,但沒有曲面細分、計算着色器和立方體貼圖陣列。支持延遲着色路徑。不支持眼適應,因為其使用計算着色器。 |
ES2 |
對應大多數 OpenGL ES2 移動設備支持的功能。使用削減向前着色路徑。 |
渲染狀態分組
渲染狀態根據其影響的流程部分而分組。例如,RHISetDepthState 可設置所有與景深緩沖相關的狀態。
渲染狀態默認值
由於渲染狀態數量眾多,要在每次繪制之前對它們全部設置一遍是不現實的。為此,UE4 具有隱性設置的一組狀態,它們被認為是設置為了默認值(因此在變更后必須恢復為默認值),另外還有一組少得多的狀態需要顯性設置。沒有隱性默認值的狀態有:
- RHISetRenderTargets
- RHISetBoundShaderState
- RHISetDepthState
- RHISetBlendState
- RHISetRasterizerState
-
由 RHISetBoundShaderState 設置的着色器的任何依賴性
其他所有狀態均視為已設置為其默認值(即相關 TStaticState 的定義,如默認的蠟紙模板狀態由 RHISetStencilState(TStaticStencilState<>::GetRHI()) 設置。