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