【游戲開發】UE4渲染引擎模塊講解
本文的內容是從渲染引擎的宏觀功能上羅列UE4的覆蓋面和劃分方式,尚不會涉及到具體每個功能模塊的實現細節。本文在討論渲染模塊的時候還假設大家均具備這些圖形引擎常識:渲染API的功能范疇、如何組織基礎的渲染管線、夸平臺圖形引擎需要基礎框架支持的最小集。
先從頂層來看一次完整的渲染

給渲染器輸入以原始的幾何和材質數據,渲染器把幾何和材質數據轉換為渲染API所支持的數據、渲染狀態、Shader及Shader參數并由這些數據組裝為一個RenderPipeline,然后執行該RenderPipeline,得到渲染結果后交換到渲染的目標Context上去(如Windows下的一個窗口,Android下的一個View等)。一個3D渲染引擎的核心工作就是組織好這一宏觀上的工作流,使其最大化利用目標平臺的硬件資源(CPU,GPU,內存,硬盤或閃存等)和特性,使其使用最便利、性能最優,效果最佳。
UE4的渲染系統也不例外,所以我們的渲染功能的識別方式的基于以上基本過程和傳統的3D引擎功能劃分來做。UE4的模塊(Module)和我們將要討論的渲染功能模塊不存在一一對應關系,可能UE4的一兩個類即實現一個功能塊,或一個UE4的模塊(Module)除了包含數個渲染相關的功能。
UE4場景和場景管理(Scene 、SceneManager)
在UE4中不存在傳統引擎中的嚴格一一對應的Scene和SceneManager,它的實現是散落在許多類中。
傳統引擎中的Scene一般表達一個渲染用的世界。這個概念在UE4中有兩個類和它對應:用于游戲線程中的UWorld類和用于渲染線程中的FScene類.UE4中的中UWorld和FScene有一一對應關系,UWorld用于游戲線程,用于用戶的主動操作(如創建、刪除世界中的物件等),而FScene則隱藏于渲染線程,由UWorld和世界中的對象被動操作。
在游戲過程中,一般只存在一個UWorld實例(在過渡的時候可能有兩個),但在編輯器形態下,一般會存在許多個UWorld對象——一般來說,一個UWorld對象表達一個單獨的編輯器窗口。
UE4和其它支持大世界的引擎一樣支持游戲場景中的物體動態加載和卸載。但它對于大世界的拆分方式是比較獨特的——UE4的場景的劃分模式不是基于物件級而是基于子關卡級來做。在UE4中,一個UWorld由一個一直存在的持久關卡(ULevel類)和多個動態加載卸載的子關卡組成。UE4中這種動態加載卸載的子關卡叫做流關卡(StreamingLevel ,ULevelStreaming類),且場景中的具體物件都是放置在關卡或流關卡中而不是直接位于UWorld中。
UE4中的流關卡的加、卸載策略實現是由UWorldComposition類來負責的。這是一個基于視點距離和流關卡卡包圍盒的簡單的加載策略實現。
用于渲染線程的FScene不具備復雜的場景管理功能,它有一些數組用于各類管理場景可渲染對象和燈光,它有兩個Octree結構用于空間的快速查詢——一個用于燈光,另一個用于其它的可渲染對象,它還有一個DrawList用于Cache各個渲染Pass的指令。
UE4場景中的物體(SceneObject)
當我們在UE編輯器中往場景里拖一個NPC,或放置一個燈光,一個后處理盒(PostProcess Volume)的時候,我們都是往該關卡中添加了一個AActor子類實例,UE4關卡和流關卡中每一個獨立物件由一個AActor及其子類的對象實例來建模表達。
AActor及其子類本身并不直接持有渲染所需的數據,AActor基于組合模式設計,可持有數個UActorComponent實例,具體的渲染相關的數據均在UActorComponent及其子類的實例中。
USceneComponent見名知義,它是UActorCompoent子類里可用于場景中的組件基類。USceneComponent有兩個主要作用:它包含Transform數據,它可以支持Attachment。
UPrimitiveComponent是USceneComponent的子類,它是所有可渲染組件的基類。它包含一系列的幾何數據,而做為附贈品,它同時也可以做為碰撞數據使用。
ULightComponentBase是USceneComponent的另一個子類,它是所有燈光組件的基類。
渲染相關的主要Component類結構層次
UE4要渲染API封裝
UE4中的渲染API封裝是個獨立的模塊(Module),他們把它命名為RHI(Render Hardware Interface)。RHI的接口定義上傾向于向最新的渲染API靠近(如DX12和Vulkan),它除了提供渲染API提供的主要接口轉發外,還對CommandList,ShaderCache、StateCache和GpuProfiler做了基本的封裝。
RHI的轉發實現在RHICommandList.h文件里,可以看到其實現除了基本的條件判斷,大都是直接 轉調渲染API實現的RHI子模塊里的渲染指令。
UE4具體的實現了以下RHI模塊的封裝
D3D11RHI ,基于D3D11 Feature的RHI封裝
D3D12RHI,基于D3D12 Feature的RHI封裝
MetalRHI,基于Metal 1和2的RHI封裝
OpenGLGLDrv ,它同時實現了Windows,Linux,Android,Ios,Web等各個平臺的Opengl,包含GL3,GL4.x和GLES2,GLES3和H5上的Feature.
EmptyRHI和NullDrv,這兩個都是對RHI的空實現
UE4的材質系統
UE4對材質系統的封裝可以理解為RenderPipiline輸入的所有數據中除了幾何體數據之外的所有其它數據。它包括渲染所需要選擇的光照模型、光照自身的照射分布函數、材質模型及該材質模型所需要的輸入參數,渲染狀態數據,為各種頂點格式和渲染分支生成的Shader,以及一個提供給用戶編輯態使用的節點圖等等。
UE4中可用于渲染的材質分為兩種:一種是材質模板(UMaterial),另一個是基于材質模板的材質實例(UMaterialInstance).這兩貨都是UMaterialInterface的子類。只有UMaterial材質模板帶有可編輯的節點圖并可拒此生成對應的Shader組合,而UMaterialInstance材質實例則只需要引用UMaterial對應的Shader.UMaterialInstance只能修改材質模板暴露出來的材質參數。
對渲染層來說,一般并不需要區分材質實例和材質模板本身。
FMaterialResource是FMaterail的子類,用于UMaterial的渲染,具體來說FMaterialResource負責為各個渲染API和材質所支持的各種質量等級生成對應的Shader組合。所以,每個UMaterial都會包含多個FMaterialResource。

FMaterialRenderProxy是FMaterial用于渲染線程的代理,它可以透過FMaterail和UMaterialInterface訪問到Shader、渲染狀態,光照模型等所有用戶設置好的材質參數。
UE4的材質中光照模型是不可定制的,所以在不魔改源碼的前提下,你無法修改其光照模型,比如你想實現一個NPR的Ramp給光照分層時。
UE4中Shader生成
FShader是UE4中所有Shader的基類,它有兩個主要的子類
FGlobalShader:全局Shader,會自動注冊到全局ShaderCache中
FMaterialShader:用于材質(編輯器)的Shader,所有的后處理、UI、用于模型渲染的Shader都是它的子類。
UE4 Shader生成分兩部分,第一部分是把材質編輯器中的節點圖編譯成HLSL代碼,這一部分是通過FHLSLMaterialTranslator來完成的。
UE4 Shader生成的第二部分是把HLSL生成多平臺的Shader代碼,如Windows上的HLSL,Android上的GLSL,IOS上的MetalShader,簡單的流程是這樣:
如果是目標平臺是HLSL相關的平臺,則使用ShaderCompilerCommon模塊編譯出HLSL AST,再適配到不同的SM Feature上。
如果目標平臺是非HLSL相關的平臺,則先通過Hlslcc模塊(在源碼的ThirdParty中)把HLSL編譯成基于Mesa自定義的GLSL ByteCode的AST,對該AST再使用GLSLOptimizer進行優化,并把對應的AST通過不同平臺的Shader編譯后端把Mesa GLSL ByteCode生成不同的Shader源碼。
第二部分編譯是通過啟動ShaderCompilerWorker實用程序并行編譯。
ShaderCompilerWorker的只是簡單封裝了一下就轉調了IShaderFormat的各個子類的CompileShader,而ShaderFormat則會調用對應的xxxxFrontend(FOpenGLFrontend)進行具體的Shader生成。
基本生成流程如下圖

從上面的介紹可以看到UE4的Shader跨平臺方案,和U3D一樣,使用的字節碼的方案,不過一個用的是HLSL BC,一個使用的是Mesa BC。Shader Cross Compile在有了Spir-v之后或者大家都往其遷移是更靠譜的方式——畢竟這是個有強大開源組織在維護、升級和推動的天然跨平臺的字節碼,而且其Optimizier也在持續維護,要比目前的UE使用的glsloptimizer可維護性會更好。
PSO Caching(Pipeline State Object Caching)
UE4用新的PSO Caching用來替代原來的FShaderCache。原來的FShaderCache實現的是對Shader代碼(或二進制的ByteCode)進行Cache.新的PSO Caching則是ShaderCache的超集,它不僅Cache了渲染所用的Shader代碼,同時也Cache了渲染狀態.PSO Caching的設計在很大程度是貼合了新的渲染API的方向,向Vulkan的Pipeline Cache致敬
PSO Caching會把渲染狀態、頂點聲明、Primitive類型、RenderTarget像素格式等等的數據保存到文件中,或是從文件中加載它們——因為這些渲染狀態數字類數據大都是一系列的Int或枚舉類數據,空間大小占用有限,故沒有做額外的處理。
但PSO Caching中不會直接保存Shader代碼(不管是源代碼或是編譯好的二進制Shader),也不保存Shader的路徑,它保存的是Shader路徑的SHA1 Hash做為Shader唯一的索引,真正的Shader是由FShaderCodeLibrary管理。
Unreal Lightmass
Lightmass模塊是UE4的烘焙渲染器,是一個單獨的可執行文件。它工作在CPU上而非GPU上,配合UE自帶的Swarm Agent和Swarm Coordinate實用程序,Lightmass還能實現分布式的烘焙。除了光照圖,還烘焙陰影、AO,BentNormal、可見性、Mesh距離場等等。
Lightmass是一個基于簡化版本的Photon mapping實現的渲染器,雖然有大量的示例證明它能渲染的和離線渲染器一樣好。但我這半吊子離線民科還是強烈的感受到,實際上它的實現傳統的離線渲染器相距甚遠,實現的思路并沒有考慮到離線渲染所關注的最大問題:海量資源的管理調度。
相對于暴力Path Tracing來說,Lightmass是有偏的。
相對于Jensen的提出Photon Mapping時的構想來說,Lightmass的實現是個閹割版。它失去了重構Caustic的可能;它不區分鏡面和非鏡面反射,Photon Trace的結果只有一份Photon Mapping;它內部的材質模型是單一的;它有實現諸如蒙特卡羅,Irrandiance Cachiing,Final Gather 等Photon Mapping的優化手法,但是許多實現都不是非常完善。
Lightmass中的光子被存在八叉樹中,Jensen實現是基于數組的KD-Tree。
一整個場景中參與投影的Mesh在Lightmass里會被組織為一個碩大的Mesh,再使用KD-Tree對其進行劃分以加速Ray Cast求交。UE4有實現一套自己的Mesh KD-Tree構建,但它默認的并不使用自己的這份Kd-tree,而是用的Intel的Embree庫來表達場景快速求交。
Lightmass對幾何體的光柵化是基于平底三角形拆分進行光柵化。
UE4烘焙的基本工作流程

Render Path
UE4目前支持3種渲染路徑:移動端簡單的Forward Render Path,PC/主機端的Deffered Render Path和Forward 。在FrameGraph尚未完善情況下,每種渲染器對應的渲染Pass是人肉組裝的。
UE4用于組織渲染路徑實現h/cpp文件的后綴均為Renderer,每個Renderer均由數個渲染Pass順序級聯而成,對于具體每個Pass的渲染實現和某個單獨的功能性的渲染實現,UE4中的h/cpp文件后綴則是Rendering,如FogRendering,DepthRendering等等
Renderer的基類是FSceneRenderer,當你想實現一個自己的渲染路徑時,它的核心接口只有一個:

雖然該接口名為Render,但實際上它的工作不止是渲染,它同時負責了更新部分數據、裁剪、遮擋剔除,組織渲染指令等工作。這就容易在和GPU進行渲染指令同步時出現斷供和積壓的情況,為了應對渲染線程在干這些臟活累活時的所帶來的額外延遲,UE4一方面盡可能的把一些耗時大且可并行的任務扔到線程里去實現(如可見性計算,生成渲染指令),另外一方面還專門針對GPU同步的提交實現了一個RHI線程。
UE4渲染數據和命令組織、可視性計算是通過InitViews函數實現的,這不是FSceneRenderer的接口,但是FSceneRenderer的兩個子類均有一個一樣功能實現。

FDeferredShadingSceneRenderer用于其它非移動平臺。它內部的實現是同時集成了延遲渲染和Forward 。在4.23開發版本中還看到有實現單獨的Deffered Cluster Pass。關于Deffered Shading Renderer具體的渲染過程,每個Pass的功能分析,網上資料較為詳實可觀了。
UE4中用于移動端的渲染器叫FMobileSceneRenderer,它實現了一個傳統的Forward Renderer。MobileRendeer最核心的Pass是MobileBasePass ,它的具體實現分兩部分,CPU端的數據組織,Shader及其Permutation管理在MobileBasePassRendering.h/cpp中,Shader本身的實現則在MobileBasePassVertexShader.usf和MobileBasePassPixelShader.usf中。
UE4的Shader Permutation是基于Pass和預編譯宏來組合的,MobileBasePassPixelShader.usf也不利外,它實現了一個mesh著色的基本過程:從光照圖、Shadowmap采樣和計算、反射球采樣、天光、到實時燈光、Fog混合等。在硬件支持的前提下,它還基于Frame Buffer Fetch實現了Alpha Blend。
UE4同時支持硬件遮擋剔除和軟件遮擋剔除,只是它的軟件遮擋剔除目前僅用于ES2相關的設備。
UE4 Render Path的實現包含在“Renderer”這一模塊中。
Instancing
在Batch和Instancing這兩項減少DrawCall的技術方案選型上,和U3D不同,UE4選的是Instancing,所以UE4的Batch功能是比較弱的,你在場景中放100個一模一樣的Box,在沒有Instancing時,它就是100個DrawCall。
UE4目前有三種Mesh相關的Instancing實現:
UInstancedStaticMeshComponent(ISM),靜態的模型Instance,即我們在書上和其它引擎中最常見的Instance形式——只有位置相異而mesh和材質均完全相同的物體可以合并成一個Actor,在理想情況下只提交一次DrawCall。ISM好處是DrawCall少,壞處是LOD計算,裁剪和OC等等都是按一個對象來做,往往ISM的Drawcall減少了,但提交渲染的三角形卻更多了。
UHierarchicalInstancedStaticMeshComponent(HISM),UE4中的HISM和ISM不同之處是它是基于分層實現。目的是為了滿足一個Mesh有大量實例時分區域進行裁剪、計算LOD。UE中的植被就是HISM的子類。看到HISM大量的Console Variable都直接叫Foliagexxx...可以猜到的是HISM大概一開始是為植被系統所準備的。
HISM的實現有兩部分,一部分是構建分層(自動化的,每次修改它的instance個數、位置等都會觸發)并保存到文件中,另一部分則是基于分層的可見性計算、LOD和渲染組織。
HISM的分層結構是一個KD-Tree,不過它的生成不是基于啟發式平衡的結構去做,而是直接用的最長軸做為當前拆分軸,且簡單粗暴的從長軸中間分開成左右子樹,葉節點所包含的Instance數或三角形數由外部控制。
Dynamic Instance ,UE4在4.22中支持的Dynamic Instance和上述的ISM/HISM雖同為Instancing,但實現上是完全不相關的。ISM/HISM的實現是靜態的,需要顯示的在場景中對相同物件強制打包到一個PrimitiveComponent下面,屬于編輯階段需要確定的數據組織功能。
而Dynamic Instancing則是在渲染指令組織的時候,發現相同的mesh pipeline state自動組裝的。
Dynamic Instance實現的核心是UE4實現了一個GPU Scene——用一個Buffer來存儲全場景每個Primitive的Transform等信息,這樣在組裝出Instance Buffer之后只需用PrimitiveID就可以訪問到自己的位置相關數據,從而實現Instance渲染了。
Dynmic Instance在4.22上的移動端沒有實現,但其實只要稍微修改一下UE4使用的RWStructureBuffer為RWBuffer,并且把GPU Scene相關的檢測和更新修正,就可以在手機端ES3.1 Feature上正常使用。
轉載聲明:本文來源于網絡,不作任何商業用途。

全部評論


暫無留言,趕緊搶占沙發
熱門資訊

你心中的打虎英雄是怎樣的?第十三屆王座杯現面向全國征稿~...

什么!參加王座杯能瓜分25000元?先讓旺旺噠我康康!...

當代”畢加索“插畫家John Battalgazi幾何圖形作品...

超人氣治愈系藝術家吉田誠治場景參考

嚴禁刷票警告!!!

《八月未央》豆瓣評分4.0、讓人三觀崩塌的年度爛片?...

王座杯人氣獎投票即將開始!請準備好你寶貴的1票~...

@所有人!第19屆王座杯CG數字藝術大賽主題征集活動開始啦!...

28歲沒學歷應該去學什么技術?
