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

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

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

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

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

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

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

全部評論


暫無留言,趕緊搶占沙發(fā)
熱門資訊

專訪|王氏教育集團康海威老師:國民手游《王者榮耀》曹操高低模打造者...

繪學霸規(guī)則玩法大全

20個CG大佬必備的行業(yè)網(wǎng)站

韓國畫師“pilyeon”的這組線稿,都把我看哭了!...

全國春茶地圖出爐 春茶有哪些?

天賦與努力并存的漫畫家loundraw作品欣賞

米哈游:新作《崩壞:星穹鐵道》,預(yù)計登錄移動和PC市場...

王氏教育王康慧受邀于《教育正能量》欄目解答什么是社會需要的人才?...

30歲做副業(yè)可以學什么技術(shù)?
