Unreal Engine 4.25 渲染模塊分析


1. 概述

UE4 的渲染相關模塊分布在 Engine, Renderer 和 RenderCore 。

Game Thread 和 Render Thread 一般不觸碰對方的數據。Game Thread 里的類對應到 Render Thread 里基本遵循:UXX 在 Game Thread 里,對應的 FXX 在 Render Thread 里。 e.g. 代表光源的 ULightComponent & FLightSceneProxy 。

2. 工具推薦

  • RenderDoc :看各種渲染命令、數據。誰用誰舒服 👍 x N。
  • HLSL Tool for Visual Studio : ush / usf 文件高亮(需要設置)。

3. SceneRenderer

移動渲染器 MobileShadingRenderer 和延遲渲染器 DeferredShadingRenderer 都公有繼承自 FSceneRenderer :

FSceFSceneRenderer

父類 FSceneRenderer 里有一個純虛函數 Render ,即子類必須實現的渲染入口:

virtual void Render(FRHICommandListImmediate& RHICmdList) = 0;

Debug 引擎源碼時,在編輯器上點擊 Play Preview ,選擇 Mobile Preview 就會進到 MobileShadingRenderer::Render 里,而選擇 Windows Preview 則進到 DeferredShadingRenderer::Render 里。代碼在 Engine/Source/Runtime/Renderer/Private 下。

3.1 進到哪個 SceneRenderer ?

// SceneRendering.cpp
// FRendererModule::BeginRenderingViewFamily() => CreateSceneRenderer()
   
FSceneRenderer* FSceneRenderer::CreateSceneRenderer(const FSceneViewFamily* InViewFamily, FHitProxyConsumer* HitProxyConsumer)
{
	// ...
	if (ShadingPath == EShadingPath::Deferred)
	{
		SceneRenderer = new FDeferredShadingSceneRenderer(InViewFamily, HitProxyConsumer);
	}
	else 
	{
		check(ShadingPath == EShadingPath::Mobile);
		SceneRenderer = new FMobileSceneRenderer(InViewFamily, HitProxyConsumer);
	}
	return SceneRenderer;
}

FDeferredShadingSceneRenderer::Render()

FMobileSceneRenderer::Render()

4. DeferredShadingRenderer

4.1 相關文件:


DeferredShadingRenderer.h class DeferredShadingRenderer
DeferredShadingRenderer.cpp Render()

BasePassRendering.cpp RenderBasePass(), class FBasePassMeshProcessor
LightRendering.cpp RenderLights()

BasePassVertexShader.usf Main()
BasePassPixelShader.usf FPixelShaderInOut_MainPS()

DeferredShadingCommon.ush EncodeGBuffer()
DeferredLightPixelShaders.usf DeferredLightPixelMain()
DeferredLightingCommon.ush GetDynamicLighting()


4.2 前向渲染 vs 延遲渲染:

Forward

Deferred

嗯,說白了就是原本 1 個 Pass 畫完,現在分成兩個 Pass —— 第 1 個 Pass 先把 Material 里那些東西畫到 GBuffers 里,第二個 Pass 再從 GBuffers 里讀出這些數據做 Lighting 。

那么 GBuffers 里放什么東西?一般來說, 引用 [2] :

A common case example is a 5 texture GBuffer, A through E. GBufferA.rgb = World Normal, with PerObjectGBufferData filling the alpha channel. GBufferB.rgba = Metallic, Specular, Roughness, ShadingModelID. GBufferC.rgb is the BaseColor with GBufferAO filling the alpha channel. GBufferD is dedicated to custom data and GBufferE is for precomputed shadow factors.

好了,RenderDoc 要出場了。

RenderDoc_DeferredRendering

整個 DeferredRenderer 的渲染流程如上圖所示。其中, BasePass 就是延遲渲染的 Pass 1 (GBuffer Pass),下面一些的 Lights 就是 Pass 2 (Lighting Pass)。BasePass 之前的 BeginRenderingGBuffer 把 RenderTargets 設置成了 GBuffers ,然后 Clear。

RenderDoc_GBuffers

接下來的 BasePass 就繪制到 GBuffers 上了。然后到 Lighting 里,RenderDoc 顯示出的 DX11 繪制命令,那幾個連續的 PSSetShaderResources 就是在設置 GBuffers 作為參數,獲得 BasePass 畫好的 GBuffers 。

RenderDoc_DeferredRendering2

4.3 延遲渲染的主要步驟

DeferredRenderer

4.4 Shader side

UE4 里的 Shader 是 HLSL 代碼和 material graph 的結合。創建一個 material 會編譯出很多的 shader permutation 。產生 shader permutation 的大量的條件編譯語句是 usf / ush 難讀的原因之一(而使得 UE4 的 C++ 代碼難讀的原因之一是大量的宏技巧)。

  • BasePass VS
  • BasePass PS
  • DeferredLight VS
  • DeferredLight PS

5. MobileShadingRenderer

5.1 相關文件:


SceneRendering.h class FMobileSceneRenderer

MobileShadingRenderer.cpp Render()

MobileBasePassRendering.cpp RenderMobileBasePass()

MobileBasePassVertexShaders.usf Main()

MobileBasePassPixelShaders.usf Main()


5.2 移動渲染的主要步驟

移動渲染器單純就是前向渲染。

RenderDoc_MobileRendering

MobileRender

5.3 Shader side

:因為這些 shader 的 permutaion 過多,把 RenderDoc 里最后確定的那個版本拷下來看的,並做了一些可讀性處理。

5.3.1 MobileBasePassPixelShader.usf

MobileBasePassPixelShaders.usf

Main()

參數如下:

// ?
FVertexFactoryInterpolantsVSToPS Interpolants
// ?
FSharedMobileBasePassInterpolants  BasePassInterpolants
// 當前 pixel 的 screen coord 
in float4 SvPosition : SV_Position
// is front face?
in bool bIsFrontFace : SV_IsFrontFace
// 當前 pixel 輸出的 color
out half4 OutColor : SV_Target0

干了啥事?按照順序:

/*
1. ResolvedView = ResolveView();

2. ShadingModelContext
   - FMaterialPixelParameters = GetMaterialPixelParameters(Interpolants, SvPosition);
   - CalcMaterialParametersEx() 和 GetMaterialCoverageAndClipping() => PixelMaterialInputs
   - PixelMaterialInputs => 設置 ShadingModelContext.XXX
   - InitShadingModelContext(ShadingModelContext, MaterialParameters)
   
3. Diffuse

   - ShadingModelContext.DiffuseColor
   - DiffuseGI = max(half3(0, 0, 0), DotSH3(PointIndirectLighting, DiffuseTransferSH));
   - View_IndirectLightingColorScale

4. MaterialAO

   - MaterialAO = GetMaterialAmbientOcclusion(PixelMaterialInputs);

5. MobileDirectionalLight

   - Shadow = GetPrimaryPrecomputedShadowMask(Interpolants).r;
   - Shadow = Shadow * MobileDirectionalLightCSM(ScreenPosition.xy, SceneDepth);
   - MobileDirectionalLight_DirectionalLightColor.rgb
   - FMobileDirectLighting Lighting = MobileIntegrateBxDF(ShadingModelContext, NoL, RoL, MaterialParameters.CameraVector, MaterialParameters.WorldNormal, H, NoH);

6. Specular

   - SpecularIBL = GetImageBasedReflectionLighting(MaterialParameters, ShadingModelContext.Roughness, IndirectIrradiance);
   - ShadingModelContext.SpecularColor

7. DynamicLights

   - for (int i = 0; i < NumDynamicPointLights; i++) { ... }

8. Emissive

   -  GetMaterialEmissive(PixelMaterialInputs);

9. lerp(Color, ShadingModelContext.DiffuseColor + ShadingModelContext.SpecularColor, ResolvedView.UnlitViewmodeMask);

10. VertexFog

11. ResolvedView.PreExposure
*/
Constant Buffers
  • MobileDirectionalLight

    在 RenderMobileBassPass 里的 UpdateDirectionalLightUniformBuffers() 設置給 View.MobileDirectionalLightUniformBuffers 。本質上是 Scene.MobileDirectionalLights[] 里面的方向光源。

  • IndirectLightingCache

    InitViews() 里會用 Scene->IndirectLightingCache 更新 View ,然后在 UpdatePrimitiveIndirectLightingCacheBuffers() 里更新 PrimitiveSceneInfo->UpdateIndirectLightingCacheBuffer();

  • $Globals

    里面有心心念念的 NumDynamicPointLightsLightPositionAndInvRadius

    MobileBasePassRendering.cpp 里的 FMobileBasePassMovableLightInfo 設置了這些參數

    FPrimitiveSceneProxy->GetPrimitiveSceneInfo()->LightList->GetLight()->Proxy->GetLightShaderParameters() 里面得到光源信息,然后賦給 LightPositionAndInvRadius

    MobileBasePass.cpp 里的 GetShaderBindings() Set dynamic point lights

    MobileBasePass.h 里有 NumDynamicPointLightsParamete

5.3.2 MobileBasePassPixelShader.usf (4.26)

切換 RT 變成 GBuffer 了

// Store the results in local variables and reuse instead of calling the functions multiple times.
	FGBufferData GBuffer = (FGBufferData)0;
	GBuffer.WorldNormal = MaterialParameters.WorldNormal;
	GBuffer.BaseColor = GetMaterialBaseColor(PixelMaterialInputs);
	GBuffer.Metallic = GetMaterialMetallic(PixelMaterialInputs);
	GBuffer.Specular = GetMaterialSpecular(PixelMaterialInputs);
	GBuffer.Roughness = GetMaterialRoughness(PixelMaterialInputs);
	GBuffer.ShadingModelID = GetMaterialShadingModel(PixelMaterialInputs);
	half MaterialAO = GetMaterialAmbientOcclusion(PixelMaterialInputs);

// ------------------------------------------------------------------
#if DEFERRED_SHADING_PATH
    float4 OutGBufferD;
	float4 OutGBufferE;
	float4 OutGBufferF;
	float4 OutGBufferVelocity = 0;

	GBuffer.IndirectIrradiance = IndirectIrradiance;
	GBuffer.PrecomputedShadowFactors.r = GetPrimaryPrecomputedShadowMask(Interpolants).r;

	EncodeGBuffer(GBuffer, OutGBufferA, OutGBufferB, OutGBufferC, OutGBufferD, OutGBufferE, OutGBufferF, OutGBufferVelocity);
	OutSceneDepthAux = SvPosition.z;

5.3.3 MobileDeferredShading.usf

對方向光:

void MobileDeferredShadingPS(
	noperspective float4 UVAndScreenPos : TEXCOORD0, 
	float4 SvPosition : SV_POSITION, 
	out half4 OutColor : SV_Target0)
{
}

對其他光:

void MobileRadialLightPS(
	float4 InScreenPosition : TEXCOORD0,
	float4 SVPos			: SV_POSITION,
	out half4 OutColor		: SV_Target0
)
/*
1. 從 MobileSceneTextures FetchGBuffer() 
2. FGBufferData = DecodeGBufferMobile()
3. 從 DeferredLightUniforms 里面得到 LightData 
4. 填充 ShadingModelContext
5. GetDirectLighting
*/

6. MobileDeferredShadingPass (4.26)

MobileDeferredShadingPass.cpp

MobileDeferredShadingPass

MobileDeferredShadingPass

MobileDeferredShadingPass(RHICmdList, Scene, View, SortedLightSet)

  • (?) 似乎沒卵用:創建 MobileSceneTexture 然后綁成全局的 (SCOPED_UNIFORM_BUFFER_GLOBAL_BINDINGS)
  • SetViewport()
  • RenderDirectLight()
  • 挨個對 SortedLightSet 里的光源調用 RenderLocalLight()

RenderDirectLight

RenderDirectLight(RHICmdList, RHICmdList, Scene, View, LightSceneInfo)

PS 對應 FMobileDeferredShadingPS:

RenderLocalLight

RenderLocalLight(RHICmdList, RHICmdList, Scene, View, LightSceneInfo)

PS 對應 FMobileRadialLightPS :

  • 不是點光或者聚光燈就 return

  • SetLocalLightRasterizerAndDepthState

  • 填充 FMobileRadialLightPS 的 PermutationVector,綁定 VS 和 PS

  • SetGraphicsPipelineState

  • 填充 FMobileRadialLightPS 的 PassParameters (ViewUB 和 DeferredLightUniforms)

  • SetShaderParameters

  • VertexShader->SetParameters

  • StencilingGeometry::DrawXXX(RHICmdList)

有用鏈接

[0] UE4調試源碼正確方式

[1] 關於 UE4 更詳細點的概述 Unreal Source ExplainedGDC Europe 2014 : Unreal Engine 4 for Programmers

[2] 介紹 UE4.22 之前版本的渲染的系列文章 Unreal Engine 4 Rendering ,之后的版本 FXXXDrawingPolicy 被 FXXXMeshProcessor 代替掉了,見 Mesh Drawing Pipeline Conversion Guide for Unreal Engine 4.22

[3] UE4 材質系統的概念 Intro to Materials: Overview


免責聲明!

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



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