主頁 >  其他 > 剖析虛幻渲染體系(05)- 光源和陰影

剖析虛幻渲染體系(05)- 光源和陰影

2021-06-09 16:57:27 其他

目錄
  • 5.1 本篇概述
    • 5.1.1 本篇內容
    • 5.1.2 基礎概念
  • 5.2 Shader體系
    • 5.2.1 Shader概覽
    • 5.2.2 Shader模塊層級
    • 5.2.3 Shader基礎模塊
      • 5.2.3.1 Platform.ush
      • 5.2.3.2 Common.ush
      • 5.2.3.3 Definitions.ush
      • 5.2.3.4 ShadingCommon.ush
      • 5.2.3.5 BasePassCommon.ush
      • 5.2.3.6 BRDF.ush
      • 5.2.3.7 VertexFactoryCommon.ush
      • 5.2.3.8 BasePassVertexCommon.ush
      • 5.2.3.9 ShadingModels.ush
      • 5.2.3.10 DeferredShadingCommon.ush
      • 5.2.3.11 DeferredLightingCommon.ush
      • 5.2.3.12 ShadingModelsMaterial.ush
      • 5.2.3.13 LocalVertexFactoryCommon.ush
      • 5.2.3.14 LocalVertexFactory.ush
  • 5.3 BasePass
    • 5.3.1 BasePass渲染流程
    • 5.3.2 BasePass渲染狀態
    • 5.3.3 BasePass Shader
      • 5.3.3.1 BasePassVertexShader
      • 5.3.3.2 BasePassPixelShader
    • 5.3.4 BasePass總結
  • 5.4 UE的光源
    • 5.4.1 光源概述
    • 5.4.2 光源演算法
      • 5.4.2.1 BRDF概述
      • 5.4.2.2 特殊光源
    • 5.4.3 光源分配和裁剪
      • 5.4.3.1 光源基礎概念
      • 5.4.3.2 GatherAndSortLights
      • 5.4.3.3 ComputeLightGrid
  • 5.5 LightingPass
    • 5.5.1 LightingPass渲染流程
    • 5.5.2 LightingPass渲染狀態
    • 5.5.3 LightingPass Shader
      • 5.5.3.1 DeferredLightVertexShader
      • 5.5.3.2 DeferredLightPixelShader
    • 5.5.4 LightingPass總結
  • 5.6 UE的陰影
    • 5.6.1 陰影概述
    • 5.6.2 陰影基礎型別
    • 5.6.3 陰影初始化
      • 5.6.3.1 InitDynamicShadows
      • 5.6.3.2 CreateWholeSceneProjectedShadow
      • 5.6.3.3 AddViewDependentWholeSceneShadowsForView
      • 5.6.3.4 SetupInteractionShadows
      • 5.6.3.5 CreatePerObjectProjectedShadow
      • 5.6.3.6 InitProjectedShadowVisibility
      • 5.6.3.7 UpdatePreshadowCache
      • 5.6.3.8 GatherShadowPrimitives
      • 5.6.3.9 FGatherShadowPrimitivesPacket
      • 5.6.3.10 AllocateShadowDepthTargets
      • 5.6.3.11 GatherShadowDynamicMeshElements
      • 5.6.3.12 陰影初始化總結
    • 5.6.4 陰影渲染
      • 5.6.4.1 RenderShadowDepthMaps
      • 5.6.4.2 FProjectedShadowInfo::RenderDepth
      • 5.6.4.3 FProjectedShadowInfo::RenderTranslucencyDepths
      • 5.6.4.4 RenderShadowDepthMapAtlases
    • 5.6.5 陰影應用
      • 5.6.5.1 RenderLights
      • 5.6.5.2 RenderShadowProjections
      • 5.6.5.3 FProjectedShadowInfo::RenderProjection
      • 5.6.5.4 FShadowProjectionNoTransformVS和TShadowProjectionPS
      • 5.6.5.5 RenderLight
      • 5.6.5.6 陰影Shader邏輯
      • 5.6.5.7 陰影應用總結
    • 5.6.6 UE陰影總結
  • 5.7 本篇總結
    • 5.7.1 本篇思考
  • 特別說明
  • 參考文獻

 

 

5.1 本篇概述

5.1.1 本篇內容

本篇主要闡述UE的以下內容:

  • Shader代碼及主要模塊,
  • UE的光源,
  • UE的陰影,
  • BasePass的機制和實作,包含C++和Shader,
  • LightingPass的機制和實作,包含C++和Shader,

其中BasePass和LightingPass在上一篇中已經粗略地淺析過,本篇繼續深入地剖析,

本篇文章將首次引入mermaid語法,它是類似于\(\LaTeX\)的以文本描述作為畫圖工具的語法,簡單、輕便、小巧,高清無損畫質,和Markdown搭配,渾然天成,

graph TD A[Unreal Engine] --> B(Rendering System) B --> C{RHI} C -->|One| D[DirectX] C -->|Two| E[Metal] C -->|Three| F[Vulkan]

利用mermaid語法做出的矢量圖例,開啟文本繪圖新紀元,

前方高能預警,本篇篇幅達到5萬多字,是目前本系列文章最長的一篇,需做好心理準備,

5.1.2 基礎概念

本篇涉及的部分渲染基礎概念及決議如下表:

概念 縮寫 中文譯名 決議
Shader - 著色器、著色代碼 用于GPU執行的代碼片段,常見的型別有VS(頂點著色器)和PS(像素著色器),也泛指Shader代碼檔案,
Vertex Shader VS 頂點著色器 在GPU的頂點處理階段執行的代碼片段,常用于處理頂點變換,
Pixel Shader PS 像素著色器 OpenGL中叫片元著色(Fragment Shader),專門執行于像素處理階段的代碼片段,用于處理光柵化后的像素,如光照計算等,
High Level Shading Language HLSL 高級著色語言 DirectX的專用Shader語言,Vulkan、OpenGL的被稱為GLSL,Metal的被成為MSL,

 

5.2 Shader體系

由于后面的章節會直接進行shader代碼分析,所以本章先對UE的Shader體系做個簡潔的分析,以便大家能夠很好地過度到后續章節,

5.2.1 Shader概覽

UE的內置Shader檔案在Engine\Shaders目錄下:

所有Shader檔案總數超過了600個,其中Shader頭檔案的后綴是ush,可被其它Shader檔案include,實作檔案是usf,通常不可被其它Shader檔案include,下面是Shader目錄及部分模塊一覽:

Shaders/
  Private/
    BasePassCommon.ush
    BasePassPixelShader.usf
    BasePassVertexCommon.ush
    BasePassVertexShader.usf
    BRDF.ush
    CapsuleLight.ush
    CapsuleLightIntegrate.ush
    CapsuleShadowShaders.usf
    Common.ush
    CommonViewUniformBuffer.ush
    DeferredLightingCommon.ush
    DeferredLightPixelShaders.usf
    DeferredLightVertexShaders.usf
    DeferredShadingCommon.ush
    Definitions.usf
    LightGridCommon.ush
    LightGridInjection.usf
    LocalVertexFactory.ush
    MaterialTemplate.ush
    RectLight.ush
    RectLightIntegrate.ush
    ShadingCommon.ush
    ShadingModels.ush
    ShadingModelsMaterial.ush
    ShadowDepthCommon.ush
    ShadowDepthPixelShader.usf
    ShadowDepthVertexShader.usf
    ShadowProjectionCommon.ush
    ShadowProjectionPixelShader.usf
    ShadowProjectionVertexShader.usf
    SHCommon.ush
    VertexFactoryCommon.ush
    (......)
  Public/
    FP16Math.ush
    Platform.ush
    ShaderVersion.ush
    Platform/
      D3D/
        D3DCommon.ush
      Metal/
        MetalCommon.ush
        MetalSubpassSupport.ush
      Vulkan/
        VulkanCommon.ush
        VulkanSubpassSupport.ush
  Shared/
    (......)
  StandaloneRenderer/
    D3D/
      GammaCorrectionCommon.hlsl
      SlateDefaultVertexShader.hlsl
      (......)
    OpenGL/
      SlateElementPixelShader.glsl
      SlateVertexShader.glsl
      (......)

5.2.2 Shader模塊層級

第一梯隊的shader模塊是最底層最基礎的模塊,這些模塊不會參考其它模塊,但會被其它很多模塊參考,這些模塊主要有:

  • BasePassCommon.ush
  • BRDF.ush
  • CapsuleLight.ush
  • Common.ush
  • CommonViewUniformBuffer.ush
  • Definitions.usf
  • FP16Math.ush
  • Platform.ush
  • ShaderVersion.ush
  • LightGridCommon.ush
  • LocalVertexFactoryCommon.ush
  • ShadingCommon.ush
  • ShadowDepthCommon.ush
  • ShadowProjectionCommon.ush
  • SHCommon.ush
  • (......)

第二梯隊的重要或基礎模塊會參考第一梯隊的基礎模塊,但也會被其它梯隊或模塊參考:

  • BasePassVertexCommon.ush
  • CapsuleLightIntegrate.ush
  • DeferredLightingCommon.ush
  • DeferredShadingCommon.ush
  • LocalVertexFactory.ush
  • MaterialTemplate.ush
  • RectLight.ush
  • RectLightIntegrate.ush
  • ShadingModels.ush
  • ShadingModelsMaterial.ush
  • VertexFactoryCommon.ush
  • (......)

最后是第三梯隊的模塊,重要模塊的實作,會參考第一、第二梯隊的模塊:

  • BasePassPixelShader.usf
  • BasePassVertexShader.usf
  • CapsuleShadowShaders.usf
  • DeferredLightPixelShaders.usf
  • DeferredLightVertexShaders.usf
  • ShadowProjectionPixelShader.usf
  • ShadowProjectionVertexShader.usf
  • (......)

5.2.3 Shader基礎模塊

本小節選取部分基礎或重要的shader模塊,分析它們的介面、型別、定義,以便讓大家對UE的內置shader模塊有個大致的理解,了解UE自帶了哪些模塊,擁有哪些內置介面,在面對后續的shader代碼剖析時更加了然于胸,

5.2.3.1 Platform.ush

主要定義了跟圖形API(DirectX、OpenGL、Vulkan、Metal)和FEATURE_LEVEL相關的宏、變數及工具類介面,部分代碼如下:

// FEATURE_LEVEL的宏定義
#define FEATURE_LEVEL_ES2_REMOVED    1
#define FEATURE_LEVEL_ES3_1            2
#define FEATURE_LEVEL_SM3            3
#define FEATURE_LEVEL_SM4            4
#define FEATURE_LEVEL_SM5            5
#define FEATURE_LEVEL_MAX            6

// 初始化*臺相關的宏.
#ifndef COMPILER_HLSLCC
#define COMPILER_HLSLCC 0
#endif

#ifndef COMPILER_HLSL
#define COMPILER_HLSL 0
#endif

#ifndef COMPILER_GLSL
#define COMPILER_GLSL 0
#endif

#ifndef COMPILER_GLSL_ES3_1
#define COMPILER_GLSL_ES3_1 0
#endif

#ifndef COMPILER_METAL
#define COMPILER_METAL 0
#endif

#ifndef SM5_PROFILE
#define SM5_PROFILE 0
#endif

(......)

// 浮點數精度
#ifndef FORCE_FLOATS
#define FORCE_FLOATS 0
#endif

#if (!(ES3_1_PROFILE || METAL_PROFILE) || FORCE_FLOATS)
    #define half float
    #define half1 float1
    #define half2 float2
    #define half3 float3
    #define half4 float4
    #define half3x3 float3x3
    #define half4x4 float4x4 
    #define half4x3 float4x3 
    #define fixed float
    #define fixed1 float1
    #define fixed2 float2
    #define fixed3 float3
    #define fixed4 float4
    #define fixed3x3 float3x3
    #define fixed4x4 float4x4
    #define fixed4x3 float4x3
#endif

// PROFILE宏定義
#if PS4_PROFILE
    #define FEATURE_LEVEL FEATURE_LEVEL_SM5

#elif SM5_PROFILE
    // SM5 = full dx11 features (high end UE4 rendering)
    #define FEATURE_LEVEL FEATURE_LEVEL_SM5

#elif SM4_PROFILE
    #define FEATURE_LEVEL FEATURE_LEVEL_SM4

(......)

// 分支陳述句.
#ifndef UNROLL
#define UNROLL
#endif

#ifndef UNROLL_N
#define UNROLL_N(N)
#endif

#ifndef LOOP
#define LOOP
#endif

#ifndef BRANCH
#define BRANCH
#endif

#ifndef FLATTEN
#define FLATTEN
#endif

(......)

// 工具類介面(編譯不支持時)
#if !COMPILER_SUPPORTS_MINMAX3

float min3( float a, float b, float c );
float max3( float a, float b, float c );
float2 min3( float2 a, float2 b, float2 c );
float2 max3( float2 a, float2 b, float2 c );

(......)

#endif

// 解壓資料.
#if !defined(COMPILER_SUPPORTS_UNPACKBYTEN)
float UnpackByte0(uint v) { return float(v & 0xff); }
float UnpackByte1(uint v) { return float((v >> 8) & 0xff); }
float UnpackByte2(uint v) { return float((v >> 16) & 0xff); }
float UnpackByte3(uint v) { return float(v >> 24); }
#endif // !COMPILER_SUPPORTS_UNPACKBYTEN

// 封裝部分宏.
#ifndef SNORM
#if COMPILER_HLSLCC || PS4_PROFILE
    #define SNORM 
    #define UNORM
#else
    #define SNORM snorm
    #define UNORM unorm
#endif
#endif

#ifndef INFINITE_FLOAT
    #if COMPILER_HLSLCC
        #define INFINITE_FLOAT 3.402823e+38
    #else
        #define INFINITE_FLOAT 1.#INF
    #endif
#endif

5.2.3.2 Common.ush

此模塊主要包含了圖形API或Feature Level相關的宏、型別、區域變數、靜態變數、基礎工具介面等,具體如下:

// 型別定義或封裝
#if PIXELSHADER
    #define MaterialFloat half
    #define MaterialFloat2 half2
    #define MaterialFloat3 half3
    #define MaterialFloat4 half4
    #define MaterialFloat3x3 half3x3
    #define MaterialFloat4x4 half4x4 
    #define MaterialFloat4x3 half4x3 
#else
    // Material translated vertex shader code always uses floats, 
    // Because it's used for things like world position and UVs
    #define MaterialFloat float
    #define MaterialFloat2 float2
    #define MaterialFloat3 float3
    #define MaterialFloat4 float4
    #define MaterialFloat3x3 float3x3
    #define MaterialFloat4x4 float4x4 
    #define MaterialFloat4x3 float4x3 
#endif

#if POST_PROCESS_ALPHA
    #define SceneColorLayout float4
    #define CastFloat4ToSceneColorLayout(x) (x)
    #define SetSceneColorLayoutToFloat4(dest,value) dest = (value)
#else
    #define SceneColorLayout float3
    #define CastFloat4ToSceneColorLayout(x) ((x).rgb)
    #define SetSceneColorLayoutToFloat4(dest,value) dest.rgb = (value).rgb
#endif

struct FScreenVertexOutput
{
#if METAL_PROFILE || COMPILER_GLSL_ES3_1
    noperspective float2 UV : TEXCOORD0;
#else
    noperspective MaterialFloat2 UV : TEXCOORD0;
#endif
    float4 Position : SV_POSITION;
};

struct FPixelShaderIn
{
    float4 SvPosition;
    uint Coverage;
    bool bIsFrontFace;
};

struct FPixelShaderOut
{
    float4 MRT[8];
    uint Coverage;
    float Depth;
};

// 宏、常量定義
#define USE_GLOBAL_CLIP_PLANE (PLATFORM_SUPPORTS_GLOBAL_CLIP_PLANE && PROJECT_ALLOW_GLOBAL_CLIP_PLANE && !MATERIAL_DOMAIN_POSTPROCESS && !MATERIAL_DOMAIN_UI)

#define POSITIVE_INFINITY (asfloat(0x7F800000))
#define NEGATIVE_INFINITY (asfloat(0xFF800000))

const static MaterialFloat PI = 3.1415926535897932f;
const static float MaxHalfFloat = 65504.0f;
const static float Max10BitsFloat = 64512.0f;

#define POW_CLAMP 0.000001f

// 通用變數定義
Texture2D        LightAttenuationTexture;
SamplerState    LightAttenuationTextureSampler;

// 基礎、工具介面
float  ClampToHalfFloatRange(float  X);
float2 ClampToHalfFloatRange(float2 X);
float3 ClampToHalfFloatRange(float3 X);
float4 ClampToHalfFloatRange(float4 X);
float2 Tile1Dto2D(float xsize, float idx);
MaterialFloat Luminance( MaterialFloat3 LinearColor );
MaterialFloat length2(MaterialFloat2 v);
MaterialFloat length2(MaterialFloat3 v);
MaterialFloat length2(MaterialFloat4 v);
uint Mod(uint a, uint b);
uint2 Mod(uint2 a, uint2 b);
uint3 Mod(uint3 a, uint3 b);
MaterialFloat UnClampedPow(MaterialFloat X, MaterialFloat Y);
MaterialFloat2 UnClampedPow(MaterialFloat2 X, MaterialFloat2 Y);
MaterialFloat3 UnClampedPow(MaterialFloat3 X, MaterialFloat3 Y);
MaterialFloat4 UnClampedPow(MaterialFloat4 X, MaterialFloat4 Y);
MaterialFloat ClampedPow(MaterialFloat X,MaterialFloat Y);
MaterialFloat2 ClampedPow(MaterialFloat2 X,MaterialFloat2 Y);
MaterialFloat3 ClampedPow(MaterialFloat3 X,MaterialFloat3 Y);
MaterialFloat4 ClampedPow(MaterialFloat4 X,MaterialFloat4 Y);
MaterialFloat PositiveClampedPow(MaterialFloat X,MaterialFloat Y);
MaterialFloat2 PositiveClampedPow(MaterialFloat2 X,MaterialFloat2 Y);
MaterialFloat3 PositiveClampedPow(MaterialFloat3 X,MaterialFloat3 Y);
MaterialFloat4 PositiveClampedPow(MaterialFloat4 X,MaterialFloat4 Y);
uint ReverseBits32( uint bits );
uint ReverseBitsN(uint Bitfield, const uint BitCount);

// 數學
float Square( float x );
float2 Square( float2 x );
float3 Square( float3 x );
float4 Square( float4 x );
float Pow2( float x );
float2 Pow2( float2 x );
float3 Pow2( float3 x );
float4 Pow2( float4 x );
float Pow3( float x );
float2 Pow3( float2 x );
float3 Pow3( float3 x );
float4 Pow3( float4 x );
float Pow4( float x );
float2 Pow4( float2 x );
float3 Pow4( float3 x );
float4 Pow4( float4 x );
float Pow5( float x );
float2 Pow5( float2 x );
float3 Pow5( float3 x );
float4 Pow5( float4 x );
float Pow6( float x );
float2 Pow6( float2 x );
float3 Pow6( float3 x );
float4 Pow6( float4 x );
MaterialFloat AtanFast( MaterialFloat x );

// 求導
float DDX(float Input);
float2 DDX(float2 Input);
float3 DDX(float3 Input);
float4 DDX(float4 Input);
float DDY(float Input);
float2 DDY(float2 Input);
float3 DDY(float3 Input);
float4 DDY(float4 Input);

// 資料編碼解碼
MaterialFloat EncodeLightAttenuation(MaterialFloat InColor);
MaterialFloat4 EncodeLightAttenuation(MaterialFloat4 InColor);
MaterialFloat4 RGBTEncode(MaterialFloat3 Color);
MaterialFloat3 RGBTDecode(MaterialFloat4 RGBT);
MaterialFloat4 RGBMEncode( MaterialFloat3 Color );
MaterialFloat4 RGBMEncodeFast( MaterialFloat3 Color );
MaterialFloat3 RGBMDecode( MaterialFloat4 rgbm, MaterialFloat MaxValue );
MaterialFloat3 RGBMDecode( MaterialFloat4 rgbm );
MaterialFloat4 RGBTEncode8BPC(MaterialFloat3 Color, MaterialFloat Range);
MaterialFloat3 RGBTDecode8BPC(MaterialFloat4 RGBT, MaterialFloat Range);
uint DecodeRTWriteMask(float2 ScreenPos, Texture2D<uint> RTWriteMaskTexture, uint NumEncodedTextures);
float2 EncodeVelocityToTexture(float2 In);
float2 DecodeVelocityFromTexture(float2 In);
float DecodePackedTwoChannelValue(float2 PackedHeight);
float DecodeHeightValue(float InValue);
float DecodePackedHeight(float2 PackedHeight);

// 紋理采樣
aterialFloat4 Texture1DSample(Texture1D Tex, SamplerState Sampler, float UV);
MaterialFloat4 Texture2DSample(Texture2D Tex, SamplerState Sampler, float2 UV);
MaterialFloat Texture2DSample_A8(Texture2D Tex, SamplerState Sampler, float2 UV);
MaterialFloat4 Texture3DSample(Texture3D Tex, SamplerState Sampler, float3 UV);
MaterialFloat4 TextureCubeSample(TextureCube Tex, SamplerState Sampler, float3 UV);
MaterialFloat4 Texture2DArraySample(Texture2DArray Tex, SamplerState Sampler, float3 UV);
MaterialFloat4 Texture1DSampleLevel(Texture1D Tex, SamplerState Sampler, float UV, MaterialFloat Mip);
MaterialFloat4 Texture2DSampleLevel(Texture2D Tex, SamplerState Sampler, float2 UV, MaterialFloat Mip);
MaterialFloat4 Texture2DSampleBias(Texture2D Tex, SamplerState Sampler, float2 UV, MaterialFloat MipBias);
MaterialFloat4 Texture2DSampleGrad(Texture2D Tex, SamplerState Sampler, float2 UV, MaterialFloat2 DDX, MaterialFloat2 DDY);
MaterialFloat4 Texture3DSampleLevel(Texture3D Tex, SamplerState Sampler, float3 UV, MaterialFloat Mip);
MaterialFloat4 Texture3DSampleBias(Texture3D Tex, SamplerState Sampler, float3 UV, MaterialFloat MipBias);
MaterialFloat4 Texture3DSampleGrad(Texture3D Tex, SamplerState Sampler, float3 UV, MaterialFloat3 DDX, MaterialFloat3 DDY);
MaterialFloat4 TextureCubeSampleLevel(TextureCube Tex, SamplerState Sampler, float3 UV, MaterialFloat Mip);
MaterialFloat TextureCubeSampleDepthLevel(TextureCube TexDepth, SamplerState Sampler, float3 UV, MaterialFloat Mip);
MaterialFloat4 TextureCubeSampleBias(TextureCube Tex, SamplerState Sampler, float3 UV, MaterialFloat MipBias);
MaterialFloat4 TextureCubeSampleGrad(TextureCube Tex, SamplerState Sampler, float3 UV, MaterialFloat3 DDX, MaterialFloat3 DDY);
MaterialFloat4 TextureExternalSample(TextureExternal Tex, SamplerState Sampler, float2 UV);
MaterialFloat4 TextureExternalSampleGrad(TextureExternal Tex, SamplerState Sampler, float2 UV, ...);
MaterialFloat4 TextureExternalSampleLevel(TextureExternal Tex, SamplerState Sampler, float2 UV, MaterialFloat Mip);
MaterialFloat4 Texture1DSample_Decal(Texture1D Tex, SamplerState Sampler, float UV);
MaterialFloat4 Texture2DSample_Decal(Texture2D Tex, SamplerState Sampler, float2 UV);
MaterialFloat4 Texture3DSample_Decal(Texture3D Tex, SamplerState Sampler, float3 UV);
MaterialFloat4 TextureCubeSample_Decal(TextureCube Tex, SamplerState Sampler, float3 UV);
MaterialFloat4 TextureExternalSample_Decal(TextureExternal Tex, SamplerState Sampler, float2 UV);
MaterialFloat4 Texture2DArraySampleLevel(Texture2DArray Tex, SamplerState Sampler, float3 UV, MaterialFloat Mip);
MaterialFloat4 Texture2DArraySampleBias(Texture2DArray Tex, SamplerState Sampler, float3 UV, MaterialFloat MipBias);
MaterialFloat4 Texture2DArraySampleGrad(Texture2DArray Tex, SamplerState Sampler, float3 UV, ...);

// 特殊采樣,噪點
float4 PseudoVolumeTexture(Texture2D Tex, SamplerState TexSampler, float3 inPos, float2 xysize, float numframes, ...);
float AntialiasedTextureMask( Texture2D Tex, SamplerState Sampler, float2 UV, float ThresholdConst, int Channel );
float Noise3D_Multiplexer(int Function, float3 Position, int Quality, bool bTiling, float RepeatSize);
MaterialFloat MaterialExpressionNoise(float3 Position, float Scale, int Quality, int Function, ...);
MaterialFloat4 MaterialExpressionVectorNoise(MaterialFloat3 Position, int Quality, int Function, bool bTiling, float TileSize);
    
// 光照計算
MaterialFloat PhongShadingPow(MaterialFloat X, MaterialFloat Y);

// 資料轉換
float ConvertTangentUnormToSnorm8(float Input);
float2 ConvertTangentUnormToSnorm8(float2 Input);
float3 ConvertTangentUnormToSnorm8(float3 Input);
float4 ConvertTangentUnormToSnorm8(float4 Input);
float ConvertTangentUnormToSnorm16(float Input);
float2 ConvertTangentUnormToSnorm16(float2 Input);
float3 ConvertTangentUnormToSnorm16(float3 Input);
float4 ConvertTangentUnormToSnorm16(float4 Input);
float ConvertTangentSnormToUnorm8(float Input);
float2 ConvertTangentSnormToUnorm8(float2 Input);
float3 ConvertTangentSnormToUnorm8(float3 Input);
float4 ConvertTangentSnormToUnorm8(float4 Input);
float ConvertTangentSnormToUnorm16(float Input);
float2 ConvertTangentSnormToUnorm16(float2 Input);
float3 ConvertTangentSnormToUnorm16(float3 Input);
float4 ConvertTangentSnormToUnorm16(float4 Input);

// 坐標、空間轉換
float2 CalcScreenUVFromOffsetFraction(float4 ScreenPosition, float2 OffsetFraction);
float4 GetPerPixelLightAttenuation(float2 UV);
float ConvertFromDeviceZ(float DeviceZ);
float ConvertToDeviceZ(float SceneDepth);
float2 ScreenPositionToBufferUV(float4 ScreenPosition);
float2 SvPositionToBufferUV(float4 SvPosition);
float3 SvPositionToTranslatedWorld(float4 SvPosition);
float3 SvPositionToResolvedTranslatedWorld(float4 SvPosition);
float3 SvPositionToWorld(float4 SvPosition);
float4 SvPositionToScreenPosition(float4 SvPosition);
float4 SvPositionToResolvedScreenPosition(float4 SvPosition);
float2 SvPositionToViewportUV(float4 SvPosition);
float2 BufferUVToViewportUV(float2 BufferUV);
float2 ViewportUVToBufferUV(float2 ViewportUV);
float2 ViewportUVToScreenPos(float2 ViewportUV);
float2 ScreenPosToViewportUV(float2 ScreenPos);
float3 ScreenToViewPos(float2 ViewportUV, float SceneDepth);
MaterialFloat2 ScreenAlignedPosition( float4 ScreenPosition );
MaterialFloat2 ScreenAlignedUV( MaterialFloat2 UV );
MaterialFloat2 GetViewportCoordinates(MaterialFloat2 InFragmentCoordinates);

MaterialFloat3 TransformTangentVectorToWorld(MaterialFloat3x3 TangentToWorld, MaterialFloat3 InTangentVector);
MaterialFloat3 TransformWorldVectorToTangent(MaterialFloat3x3 TangentToWorld, MaterialFloat3 InWorldVector);
float3 TransformWorldVectorToView(float3 InTangentVector);
void GenerateCoordinateSystem(float3 ZAxis, out float3 XAxis, out float3 YAxis);

// 幾何體互動: 求交, 距離等.
float2 LineBoxIntersect(float3 RayOrigin, float3 RayEnd, float3 BoxMin, float3 BoxMax);
MaterialFloat ComputeDistanceFromBoxToPoint(MaterialFloat3 Mins, MaterialFloat3 Maxs, MaterialFloat3 InPoint);
MaterialFloat ComputeSquaredDistanceFromBoxToPoint(MaterialFloat3 BoxCenter, MaterialFloat3 BoxExtent, MaterialFloat3 InPoint);
float ComputeDistanceFromBoxToPointInside(float3 BoxCenter, float3 BoxExtent, float3 InPoint);
bool RayHitSphere(float3 RayOrigin, float3 UnitRayDirection, float3 SphereCenter, float SphereRadius);
bool RaySegmentHitSphere(float3 RayOrigin, float3 UnitRayDirection, float RayLength, float3 SphereCenter, float SphereRadius);
float2 RayIntersectSphere(float3 RayOrigin, float3 RayDirection, float4 Sphere);
MaterialFloat GetBoxPushout(MaterialFloat3 Normal,MaterialFloat3 Extent);

// 繪制介面
void DrawRectangle(in float4 InPosition, in float2 InTexCoord, out float4 OutPosition, out float2 OutTexCoord);
void DrawRectangle(in float4 InPosition, in float2 InTexCoord, out float4 OutPosition, out float4 OutUVAndScreenPos);
void DrawRectangle(in float4 InPosition, out float4 OutPosition);

(.....)

由此可知,common的shader模塊封裝了大量的基礎型別、介面、變數等,以上只是展示了部分介面,而且為了簡潔去掉了實作代碼,如果想看實作的童鞋自行查閱UE的原始碼,

5.2.3.3 Definitions.ush

此模塊主要是預先定義了一些常見的宏,防止其它模塊參考時出現語法錯誤,部分宏如下:

#ifndef MATERIAL_TWOSIDED
#define    MATERIAL_TWOSIDED                                0
#endif

#ifndef MATERIAL_TANGENTSPACENORMAL
#define    MATERIAL_TANGENTSPACENORMAL                        0
#endif

#ifndef MATERIAL_TWOSIDED_SEPARATE_PASS
#define    MATERIAL_TWOSIDED_SEPARATE_PASS                    0
#endif

#ifndef MATERIALBLENDING_MASKED
#define MATERIALBLENDING_MASKED                            0
#endif

#ifndef MATERIALBLENDING_TRANSLUCENT
#define MATERIALBLENDING_TRANSLUCENT                    0
#endif

#ifndef TRANSLUCENT_SHADOW_WITH_MASKED_OPACITY
#define TRANSLUCENT_SHADOW_WITH_MASKED_OPACITY            0
#endif

#ifndef MATERIAL_SHADINGMODEL_DEFAULT_LIT
#define MATERIAL_SHADINGMODEL_DEFAULT_LIT                0
#endif

#ifndef MATERIAL_SHADINGMODEL_SUBSURFACE
#define MATERIAL_SHADINGMODEL_SUBSURFACE                0
#endif

#ifndef MATERIAL_SHADINGMODEL_UNLIT
#define    MATERIAL_SHADINGMODEL_UNLIT                        0
#endif

#ifndef MATERIAL_SINGLE_SHADINGMODEL
#define    MATERIAL_SINGLE_SHADINGMODEL                    0
#endif

#ifndef HAS_PRIMITIVE_UNIFORM_BUFFER
#define    HAS_PRIMITIVE_UNIFORM_BUFFER                    0
#endif

#ifndef GBUFFER_HAS_VELOCITY
#define GBUFFER_HAS_VELOCITY                            0
#endif

#ifndef GBUFFER_HAS_TANGENT
#define GBUFFER_HAS_TANGENT                                0
#endif

#define PC_D3D                                            SM5_PROFILE

(......)

5.2.3.4 ShadingCommon.ush

此模塊主要是定義了材質所有著色模型,并提供了少量相關的工具類介面:

// 材質著色模型, 每種型別都有對應的光照演算法和流程.
#define SHADINGMODELID_UNLIT                0
#define SHADINGMODELID_DEFAULT_LIT            1
#define SHADINGMODELID_SUBSURFACE            2
#define SHADINGMODELID_PREINTEGRATED_SKIN    3
#define SHADINGMODELID_CLEAR_COAT            4
#define SHADINGMODELID_SUBSURFACE_PROFILE    5
#define SHADINGMODELID_TWOSIDED_FOLIAGE        6
#define SHADINGMODELID_HAIR                    7
#define SHADINGMODELID_CLOTH                8
#define SHADINGMODELID_EYE                    9
#define SHADINGMODELID_SINGLELAYERWATER        10
#define SHADINGMODELID_THIN_TRANSLUCENT        11
#define SHADINGMODELID_NUM                    12
#define SHADINGMODELID_MASK                    0xF        // ShadingModelID只占用了GBuffer的4bit.

// 除了ShadingsModelID之外的4bit用作其它用途.
#define HAS_ANISOTROPY_MASK                (1 << 4)
#define SKIP_PRECSHADOW_MASK            (1 << 5)
#define ZERO_PRECSHADOW_MASK            (1 << 6)
#define SKIP_VELOCITY_MASK                (1 << 7)

// 頭發反射組件(R, TT, TRT, Local Scattering, Global Scattering, Multi Scattering,...)
#define HAIR_COMPONENT_R            0x1u
#define HAIR_COMPONENT_TT            0x2u
#define HAIR_COMPONENT_TRT            0x4u
#define HAIR_COMPONENT_LS            0x8u 
#define HAIR_COMPONENT_GS            0x10u
#define HAIR_COMPONENT_MULTISCATTER    0x20u
#define HAIR_COMPONENT_TT_MODEL      0x40u

// 著色模型除錯顏色.
float3 GetShadingModelColor(uint ShadingModelID);

// 非導體的反射F0.
float DielectricSpecularToF0(float Specular)
{
    return 0.08f * Specular;
}

// 非導體的F0轉換到IOR(折射率).
float DielectricF0ToIor(float F0)
{
    return 2.0f / (1.0f - sqrt(F0)) - 1.0f;
}

// 非導體的IOR(折射率)轉換到F0.
float DielectricIorToF0(float Ior)
{
    const float F0Sqrt = (Ior-1)/(Ior+1);
    const float F0 = F0Sqrt*F0Sqrt;
    return F0;
}

// 計算物體表面的F0.
float3 ComputeF0(float Specular, float3 BaseColor, float Metallic)
{
    return lerp(DielectricSpecularToF0(Specular).xxx, BaseColor, Metallic.xxx);
}

需要注意的是,UE默認的ShadingModelID只占用4bit,最多16個,而目前UE內置著色模型已占用了13個,意味著自定義的ShadingModel最多只能3個了,

5.2.3.5 BasePassCommon.ush

此模塊定義了BasePass的一些變數、宏定義、插值結構體和工具類介面:

// 透明物體BasePass的定義
#if MATERIALBLENDING_ANY_TRANSLUCENT
    #define ForwardLightData TranslucentBasePass.Shared.Forward
    #define ReflectionStruct TranslucentBasePass.Shared.Reflection
    #define PlanarReflectionStruct TranslucentBasePass.Shared.PlanarReflection
    #define FogStruct TranslucentBasePass.Shared.Fog
    #define ActualSSProfilesTexture TranslucentBasePass.Shared.SSProfilesTexture
// 不透明物體BasePass的定義
#else
    #define ForwardLightData OpaqueBasePass.Shared.Forward
    #define ReflectionStruct OpaqueBasePass.Shared.Reflection
    #define PlanarReflectionStruct OpaqueBasePass.Shared.PlanarReflection
    #define FogStruct OpaqueBasePass.Shared.Fog
    #define ActualSSProfilesTexture OpaqueBasePass.Shared.SSProfilesTexture
#endif 

// BasePass相關的宏定義
#undef NEEDS_LIGHTMAP_COORDINATE
#define NEEDS_LIGHTMAP_COORDINATE        (HQ_TEXTURE_LIGHTMAP || LQ_TEXTURE_LIGHTMAP)
#define TRANSLUCENCY_NEEDS_BASEPASS_FOGGING    (MATERIAL_ENABLE_TRANSLUCENCY_FOGGING && MATERIALBLENDING_ANY_TRANSLUCENT && !MATERIAL_USES_SCENE_COLOR_COPY)
#define OPAQUE_NEEDS_BASEPASS_FOGGING        (!MATERIALBLENDING_ANY_TRANSLUCENT && FORWARD_SHADING)

#define NEEDS_BASEPASS_VERTEX_FOGGING        (TRANSLUCENCY_NEEDS_BASEPASS_FOGGING && !MATERIAL_COMPUTE_FOG_PER_PIXEL || OPAQUE_NEEDS_BASEPASS_FOGGING && PROJECT_VERTEX_FOGGING_FOR_OPAQUE)
#define NEEDS_BASEPASS_PIXEL_FOGGING        (TRANSLUCENCY_NEEDS_BASEPASS_FOGGING && MATERIAL_COMPUTE_FOG_PER_PIXEL || OPAQUE_NEEDS_BASEPASS_FOGGING && !PROJECT_VERTEX_FOGGING_FOR_OPAQUE)

#define NEEDS_BASEPASS_PIXEL_VOLUMETRIC_FOGGING        (MATERIALBLENDING_ANY_TRANSLUCENT || FORWARD_SHADING)

#define NEEDS_LIGHTMAP                        (NEEDS_LIGHTMAP_COORDINATE)

#define USES_GBUFFER                        (FEATURE_LEVEL >= FEATURE_LEVEL_SM4 && (MATERIALBLENDING_SOLID || MATERIALBLENDING_MASKED) && !SIMPLE_FORWARD_SHADING && !FORWARD_SHADING)

(......)

// 插值結構體的父類.
struct FSharedBasePassInterpolants
{
    //for texture-lightmapped translucency we can pass the vertex fog in its own interpolator
#if NEEDS_BASEPASS_VERTEX_FOGGING
    float4 VertexFog        : TEXCOORD7;
#endif

#if !TESSELLATION_SUPPORTED
    // Note: TEXCOORD8 is unused

    #if USE_WORLD_POSITION_EXCLUDING_SHADER_OFFSETS
    float3 PixelPositionExcludingWPO : TEXCOORD9;
    #endif
#endif

#if TRANSLUCENCY_PERVERTEX_LIGHTING_VOLUME
    float3 AmbientLightingVector : TEXCOORD12;
#endif

#if TRANSLUCENCY_PERVERTEX_LIGHTING_VOLUME && TRANSLUCENCY_LIGHTING_VOLUMETRIC_PERVERTEX_DIRECTIONAL
    float3 DirectionalLightingVector : TEXCOORD13;
#endif

#if TRANSLUCENCY_PERVERTEX_FORWARD_SHADING
    float3 VertexDiffuseLighting : TEXCOORD12;
#endif

#if PRECOMPUTED_IRRADIANCE_VOLUME_LIGHTING
    #if TRANSLUCENCY_LIGHTING_VOLUMETRIC_PERVERTEX_NONDIRECTIONAL
        float3 VertexIndirectAmbient : TEXCOORD14;
    #elif TRANSLUCENCY_LIGHTING_VOLUMETRIC_PERVERTEX_DIRECTIONAL
        float4 VertexIndirectSH[3] : TEXCOORD14;
    #endif
#endif

#if WRITES_VELOCITY_TO_GBUFFER
    // .xy is clip position, pre divide by w; .w is clip W; .z is 0 or 1 to mask out the velocity output
    float4 VelocityPrevScreenPosition : VELOCITY_PREV_POS;
    #if WRITES_VELOCITY_TO_GBUFFER_USE_POS_INTERPOLATOR
        float4 VelocityScreenPosition : VELOCITY_POS;
    #endif
#endif
};

#if TESSELLATION_SUPPORTED
    // VS -> PS的插值結構體.
    struct FBasePassInterpolantsVSToPS : FSharedBasePassInterpolants
    {
        // Note: TEXCOORD8 is unused

        #if USE_WORLD_POSITION_EXCLUDING_SHADER_OFFSETS
        float3 PixelPositionExcludingWPO : TEXCOORD9;
        #endif
    };
    // VS -> DS(domain shader)的插值結構體.
    struct FBasePassInterpolantsVSToDS : FSharedBasePassInterpolants
    {
        #if USE_WORLD_POSITION_EXCLUDING_SHADER_OFFSETS
        float3 WorldPositionExcludingWPO : TEXCOORD9;
        #endif
    };
#else
    #define FBasePassInterpolantsVSToPS FSharedBasePassInterpolants
#endif

// 采樣器封裝.
#if SUPPORTS_INDEPENDENT_SAMPLERS
    #define SharedAmbientInnerSampler View.SharedBilinearClampedSampler
    #define SharedAmbientOuterSampler View.SharedBilinearClampedSampler
    #define SharedDirectionalInnerSampler View.SharedBilinearClampedSampler
    #define SharedDirectionalOuterSampler View.SharedBilinearClampedSampler
#else
    #define SharedAmbientInnerSampler TranslucentBasePass.TranslucencyLightingVolumeAmbientInnerSampler
    #define SharedAmbientOuterSampler TranslucentBasePass.TranslucencyLightingVolumeAmbientOuterSampler
    #define SharedDirectionalInnerSampler TranslucentBasePass.TranslucencyLightingVolumeDirectionalInnerSampler
    #define SharedDirectionalOuterSampler TranslucentBasePass.TranslucencyLightingVolumeDirectionalOuterSampler
#endif

// 工具類介面.
void ComputeVolumeUVs(float3 WorldPosition, float3 LightingPositionOffset, out float3 InnerVolumeUVs, out float3 OuterVolumeUVs, out float FinalLerpFactor);
float4 GetAmbientLightingVectorFromTranslucentLightingVolume(float3 InnerVolumeUVs, float3 OuterVolumeUVs, float FinalLerpFactor);
float3 GetDirectionalLightingVectorFromTranslucentLightingVolume(float3 InnerVolumeUVs, float3 OuterVolumeUVs, float FinalLerpFactor);

5.2.3.6 BRDF.ush

雙向反射分布函式模塊,提供了很多基礎光照演算法及相關輔助介面:

// BxDF背景關系, 存盤了常用向量之間的點乘.
struct BxDFContext
{
    float NoV;
    float NoL;
    float VoL;
    float NoH;
    float VoH;
    float XoV;
    float XoL;
    float XoH;
    float YoV;
    float YoL;
    float YoH;
};

// 初始化BxDF背景關系
void Init( inout BxDFContext Context, half3 N, half3 V, half3 L );

// 初始化BxDF背景關系, 包含了X, Y兩個向量(切線和切線和法線的叉乘向量).
void Init( inout BxDFContext Context, half3 N, half3 X, half3 Y, half3 V, half3 L )
{
    Context.NoL = dot(N, L);
    Context.NoV = dot(N, V);
    Context.VoL = dot(V, L);
    float InvLenH = rsqrt( 2 + 2 * Context.VoL );
    Context.NoH = saturate( ( Context.NoL + Context.NoV ) * InvLenH );
    Context.VoH = saturate( InvLenH + InvLenH * Context.VoL );

    Context.XoV = dot(X, V);
    Context.XoL = dot(X, L);
    Context.XoH = (Context.XoL + Context.XoV) * InvLenH;
    Context.YoV = dot(Y, V);
    Context.YoL = dot(Y, L);
    Context.YoH = (Context.YoL + Context.YoV) * InvLenH;
}

// 球形最大的NoH.
void SphereMaxNoH( inout BxDFContext Context, float SinAlpha, bool bNewtonIteration );

// 蘭伯特漫反射.
float3 Diffuse_Lambert( float3 DiffuseColor )
{
    return DiffuseColor * (1 / PI);
}

// Burley漫反射.
float3 Diffuse_Burley( float3 DiffuseColor, float Roughness, float NoV, float NoL, float VoH )
{
    float FD90 = 0.5 + 2 * VoH * VoH * Roughness;
    float FdV = 1 + (FD90 - 1) * Pow5( 1 - NoV );
    float FdL = 1 + (FD90 - 1) * Pow5( 1 - NoL );
    return DiffuseColor * ( (1 / PI) * FdV * FdL );
}

// OrenNayar漫反射.
float3 Diffuse_OrenNayar( float3 DiffuseColor, float Roughness, float NoV, float NoL, float VoH )
{
    float a = Roughness * Roughness;
    float s = a;// / ( 1.29 + 0.5 * a );
    float s2 = s * s;
    float VoL = 2 * VoH * VoH - 1;        // double angle identity
    float Cosri = VoL - NoV * NoL;
    float C1 = 1 - 0.5 * s2 / (s2 + 0.33);
    float C2 = 0.45 * s2 / (s2 + 0.09) * Cosri * ( Cosri >= 0 ? rcp( max( NoL, NoV ) ) : 1 );
    return DiffuseColor / PI * ( C1 + C2 ) * ( 1 + Roughness * 0.5 );
}

// Gotanda漫反射.
float3 Diffuse_Gotanda( float3 DiffuseColor, float Roughness, float NoV, float NoL, float VoH );

// Blinn法線分布.
float D_Blinn( float a2, float NoH )
{
    float n = 2 / a2 - 2;
    return (n+2) / (2*PI) * PhongShadingPow( NoH, n );        // 1 mad, 1 exp, 1 mul, 1 log
}

// Beckmann法線分布.
float D_Beckmann( float a2, float NoH )
{
    float NoH2 = NoH * NoH;
    return exp( (NoH2 - 1) / (a2 * NoH2) ) / ( PI * a2 * NoH2 * NoH2 );
}

// GGX法線分布.
float D_GGX( float a2, float NoH )
{
    float d = ( NoH * a2 - NoH ) * NoH + 1;    // 2 mad
    return a2 / ( PI*d*d );                    // 4 mul, 1 rcp
}

// GGX各向異性法線分布.
float D_GGXaniso( float ax, float ay, float NoH, float XoH, float YoH )
{
    float a2 = ax * ay;
    float3 V = float3(ay * XoH, ax * YoH, a2 * NoH);
    float S = dot(V, V);

    return (1.0f / PI) * a2 * Square(a2 / S);
}

// 反轉的法線分布函式.
float D_InvBlinn( float a2, float NoH )
{
    float A = 4;
    float Cos2h = NoH * NoH;
    float Sin2h = 1 - Cos2h;
    //return rcp( PI * (1 + A*m2) ) * ( 1 + A * ClampedPow( Sin2h, 1 / m2 - 1 ) );
    return rcp( PI * (1 + A*a2) ) * ( 1 + A * exp( -Cos2h / a2 ) );
}

float D_InvBeckmann( float a2, float NoH )
{
    float A = 4;
    float Cos2h = NoH * NoH;
    float Sin2h = 1 - Cos2h;
    float Sin4h = Sin2h * Sin2h;
    return rcp( PI * (1 + A*a2) * Sin4h ) * ( Sin4h + A * exp( -Cos2h / (a2 * Sin2h) ) );
}

float D_InvGGX( float a2, float NoH )
{
    float A = 4;
    float d = ( NoH - a2 * NoH ) * NoH + a2;
    return rcp( PI * (1 + A*a2) ) * ( 1 + 4 * a2*a2 / ( d*d ) );
}

// 以下是可見性函式, 論文中常被成為幾何(G)項.
// 隱式可見性函式.
float Vis_Implicit()
{
    return 0.25;
}

// Neumann可見性函式.
float Vis_Neumann( float NoV, float NoL )
{
    return 1 / ( 4 * max( NoL, NoV ) );
}

// Kelemen可見性函式.
float Vis_Kelemen( float VoH )
{
    // constant to prevent NaN
    return rcp( 4 * VoH * VoH + 1e-5);
}

// Schlick可見性函式.
float Vis_Schlick( float a2, float NoV, float NoL )
{
    float k = sqrt(a2) * 0.5;
    float Vis_SchlickV = NoV * (1 - k) + k;
    float Vis_SchlickL = NoL * (1 - k) + k;
    return 0.25 / ( Vis_SchlickV * Vis_SchlickL );
}

// Smith可見性函式.
float Vis_Smith( float a2, float NoV, float NoL )
{
    float Vis_SmithV = NoV + sqrt( NoV * (NoV - NoV * a2) + a2 );
    float Vis_SmithL = NoL + sqrt( NoL * (NoL - NoL * a2) + a2 );
    return rcp( Vis_SmithV * Vis_SmithL );
}

// SmithJoint*似的可見性函式.
float Vis_SmithJointApprox( float a2, float NoV, float NoL )
{
    float a = sqrt(a2);
    float Vis_SmithV = NoL * ( NoV * ( 1 - a ) + a );
    float Vis_SmithL = NoV * ( NoL * ( 1 - a ) + a );
    return 0.5 * rcp( Vis_SmithV + Vis_SmithL );
}

// SmithJoint可見性函式.
float Vis_SmithJoint(float a2, float NoV, float NoL) 
{
    float Vis_SmithV = NoL * sqrt(NoV * (NoV - NoV * a2) + a2);
    float Vis_SmithL = NoV * sqrt(NoL * (NoL - NoL * a2) + a2);
    return 0.5 * rcp(Vis_SmithV + Vis_SmithL);
}

// SmithJoint各向異性可見性函式.
float Vis_SmithJointAniso(float ax, float ay, float NoV, float NoL, float XoV, float XoL, float YoV, float YoL)
{
    float Vis_SmithV = NoL * length(float3(ax * XoV, ay * YoV, NoV));
    float Vis_SmithL = NoV * length(float3(ax * XoL, ay * YoL, NoL));
    return 0.5 * rcp(Vis_SmithV + Vis_SmithL);
}

// 布料可見性分布函式.
float Vis_Cloth( float NoV, float NoL )
{
    return rcp( 4 * ( NoL + NoV - NoL * NoV ) );
}

// 無菲涅爾函式.
float3 F_None( float3 SpecularColor )
{
    return SpecularColor;
}

// Schlick菲涅爾函式.
float3 F_Schlick( float3 SpecularColor, float VoH )
{
    float Fc = Pow5( 1 - VoH );                    // 1 sub, 3 mul
    return saturate( 50.0 * SpecularColor.g ) * Fc + (1 - Fc) * SpecularColor;
}

// 完整的菲涅爾函式.
float3 F_Fresnel( float3 SpecularColor, float VoH )
{
    float3 SpecularColorSqrt = sqrt( clamp( float3(0, 0, 0), float3(0.99, 0.99, 0.99), SpecularColor ) );
    float3 n = ( 1 + SpecularColorSqrt ) / ( 1 - SpecularColorSqrt );
    float3 g = sqrt( n*n + VoH*VoH - 1 );
    return 0.5 * Square( (g - VoH) / (g + VoH) ) * ( 1 + Square( ((g+VoH)*VoH - 1) / ((g-VoH)*VoH + 1) ) );
}

// 環境相關的BRDF
void ModifyGGXAnisotropicNormalRoughness(float3 WorldTangent, float Anisotropy, inout float Roughness, inout float3 N, float3 V);
void GetAnisotropicRoughness(float Alpha, float Anisotropy, out float ax, out float ay);
half3 EnvBRDF( half3 SpecularColor, half Roughness, half NoV );
half3 EnvBRDFApprox( half3 SpecularColor, half Roughness, half NoV );
half EnvBRDFApproxNonmetal( half Roughness, half NoV );
void EnvBRDFApproxFullyRough(inout half3 DiffuseColor, inout half3 SpecularColor);
void EnvBRDFApproxFullyRough(inout half3 DiffuseColor, inout half SpecularColor);

以上介面void Init( inout BxDFContext Context, half3 N, half3 X, half3 Y, half3 V, half3 L )中的向量XY代表著世界空間的切線和切線和法線的垂直向量,使用案例:

half3 X = GBuffer.WorldTangent;
half3 Y = normalize(cross(N, X));
Init(Context, N, X, Y, V, L);

回顧一下Cook-Torrance的BRDF公式:

\[f_{cook-torrance} = \frac{D(h)\cdot F(l,h)\cdot G(l,v,h)}{4(n \cdot l)(n \cdot v)} \]

上面代碼中,D_開頭的介面對應著Cook-Torrance的D項,,F_開頭的介面對應著Cook-Torrance的F項,Vis_開頭的介面對應著Cook-Torrance的G項,更多詳細剖析可參考筆者的另一篇關于PBR的文章由淺入深學習PBR的原理和實作及相關章節:3.1.4 雙向反射分布函式(BRDF),

5.2.3.7 VertexFactoryCommon.ush

此模塊主要定義了頂點變換相關的輔助介面:

// 頂點變換
float3 TransformLocalToWorld(float3 LocalPosition, uint PrimitiveId);
float4 TransformLocalToWorld(float3 LocalPosition);
float4 TransformLocalToTranslatedWorld(float3 LocalPosition, uint PrimitiveId);
float4 TransformLocalToTranslatedWorld(float3 LocalPosition);
float3 RotateLocalToWorld(float3 LocalDirection, uint PrimitiveId);
float3 RotateLocalToWorld(float3 LocalDirection);
float3 RotateWorldToLocal(float3 WorldDirection);

// 下面兩個介面和DeferredShadingCommon.ush的UnitVectorToOctahedron和OctahedronToUnitVector名字不一樣, 但實作的功能是一樣的.
float2 UnitToOct( float3 N );
float3 OctToUnit( float2 Oct );

(......)

5.2.3.8 BasePassVertexCommon.ush

此模塊定義了一些BasePass通用的結構體、宏:

#include "Common.ush"

#if MATERIALBLENDING_TRANSLUCENT || MATERIALBLENDING_ADDITIVE || MATERIALBLENDING_MODULATE
    #define SceneTexturesStruct TranslucentBasePass.SceneTextures
#endif 

#include "/Engine/Generated/Material.ush"
#include "BasePassCommon.ush"
#include "/Engine/Generated/VertexFactory.ush"

#if NEEDS_BASEPASS_VERTEX_FOGGING
    #include "HeightFogCommon.ush"
    #if BASEPASS_ATMOSPHERIC_FOG
        #include "AtmosphereCommon.ush"
    #endif
#endif

// 從VS傳到PS/DS的結構體.
struct FBasePassVSToPS
{
    FVertexFactoryInterpolantsVSToPS FactoryInterpolants;
    FBasePassInterpolantsVSToPS BasePassInterpolants;
    float4 Position : SV_POSITION;
};

#if USING_TESSELLATION    
    struct FBasePassVSToDS
    {
        FVertexFactoryInterpolantsVSToDS FactoryInterpolants;
        FBasePassInterpolantsVSToDS BasePassInterpolants;
        float4 Position : VS_To_DS_Position;
        OPTIONAL_VertexID_VS_To_DS
    };
    
    #define FBasePassVSOutput FBasePassVSToDS
    #define VertexFactoryGetInterpolants VertexFactoryGetInterpolantsVSToDS
    #define FPassSpecificVSToDS FBasePassVSToDS
    #define FPassSpecificVSToPS FBasePassVSToPS
#else
    #define FBasePassVSOutput FBasePassVSToPS
    #define VertexFactoryGetInterpolants VertexFactoryGetInterpolantsVSToPS
#endif

5.2.3.9 ShadingModels.ush

此模塊主要是著色模型以及光照計算相關的型別和輔助介面:

// 區域光資料.
struct FAreaLight
{
    float        SphereSinAlpha;
    float        SphereSinAlphaSoft;
    float        LineCosSubtended;

    float3        FalloffColor;

    FRect        Rect;
    FRectTexture Texture;
    bool        bIsRect;
};

// 直接光資料.
struct FDirectLighting
{
    float3    Diffuse;
    float3    Specular;
    float3    Transmission;
};

// 陰影資料, 用于存盤陰影投射結果.
struct FShadowTerms
{
    float    SurfaceShadow;
    float    TransmissionShadow;
    float    TransmissionThickness;
    FHairTransmittanceData HairTransmittance;
};

// ---- 光照輔助介面 ----
// 能量歸一化.
float EnergyNormalization( inout float a2, float VoH, FAreaLight AreaLight );
// GGX高光.
float3 SpecularGGX(float Roughness, float Anisotropy, float3 SpecularColor, ...);
// GGX雙層高光.
float3 DualSpecularGGX(float AverageRoughness, float Lobe0Roughness, float Lobe1Roughness,...);
bool IsAreaLight(FAreaLight AreaLight);
float New_a2( float a2, float SinAlpha, float VoH );
float ApproximateHG(float cosJ, float g);
float3 CalcThinTransmission(float NoL, float NoV, FGBufferData GBuffer);
void GetProfileDualSpecular(FGBufferData GBuffer, out float AverageToRoughness0, ...);
bool IsAreaLight(FAreaLight AreaLight);
float New_a2( float a2, float SinAlpha, float VoH );

// 折射相關.
float RefractBlend(float VoH, float Eta);
float RefractBlendClearCoatApprox(float VoH);
float3 Refract(float3 V, float3 H, float Eta);
BxDFContext RefractClearCoatContext(BxDFContext Context);

// ---- Shading Model光照 ----
float3 SimpleShading( float3 DiffuseColor, float3 SpecularColor, float Roughness, float3 L, float3 V, half3 N );
FDirectLighting DefaultLitBxDF( FGBufferData GBuffer, half3 N, half3 V, half3 L, ...);
FDirectLighting HairBxDF(FGBufferData GBuffer, half3 N, half3 V, half3 L, ...);
FDirectLighting ClearCoatBxDF( FGBufferData GBuffer, half3 N, half3 V, half3 L, ... );
FDirectLighting SubsurfaceProfileBxDF( FGBufferData GBuffer, half3 N, half3 V, half3 L, ... );
FDirectLighting ClothBxDF( FGBufferData GBuffer, half3 N, half3 V, half3 L, ... );
FDirectLighting SubsurfaceBxDF( FGBufferData GBuffer, half3 N, half3 V, half3 L, ... );
FDirectLighting TwoSidedBxDF( FGBufferData GBuffer, half3 N, half3 V, half3 L, ... );
FDirectLighting EyeBxDF( FGBufferData GBuffer, half3 N, half3 V, half3 L,  );
FDirectLighting PreintegratedSkinBxDF( FGBufferData GBuffer, half3 N, half3 V, half3 L, ... );
// 集成光照, 會根據ShadingModelID呼叫上面不同的介面.
FDirectLighting IntegrateBxDF( FGBufferData GBuffer, half3 N, half3 V, half3 L, ... );
FDirectLighting EvaluateBxDF( FGBufferData GBuffer, half3 N, half3 V, half3 L, ... );

5.2.3.10 DeferredShadingCommon.ush

此模塊主要定義了延遲光照著色的通用的介面、宏、變數、型別:

// 顏色空間轉換
float3 RGBToYCoCg( float3 RGB );
float3 YCoCgToRGB( float3 YCoCg );
    
// 向量壓縮和解壓(單位向量, 八面體, 半八面體)
float2 UnitVectorToOctahedron( float3 N );
float3 OctahedronToUnitVector( float2 Oct );
float2 UnitVectorToHemiOctahedron( float3 N );
float3 HemiOctahedronToUnitVector( float2 Oct );

// 資料壓縮和解壓
float3 Pack1212To888( float2 x );
float2 Pack888To1212( float3 x );
float Encode71(float Scalar, uint Mask);
float Decode71(float Scalar, out uint Mask);

// 法線編解碼
float3 EncodeNormal( float3 N );
float3 DecodeNormal( float3 N );
void EncodeNormal( inout float3 N, out uint Face );
void DecodeNormal( inout float3 N, in uint Face );

// 顏色, 次表面顏色等資料的編解碼.
float3 EncodeBaseColor(float3 BaseColor);
float3 DecodeBaseColor(float3 BaseColor);
float3 EncodeSubsurfaceColor(float3 SubsurfaceColor);
float3 EncodeSubsurfaceProfile(float SubsurfaceProfile);
float SubsurfaceDensityFromOpacity(float Opacity);
float EncodeIndirectIrradiance(float IndirectIrradiance);
float DecodeIndirectIrradiance(float IndirectIrradiance);
float4 EncodeWorldTangentAndAnisotropy(float3 WorldTangent, float Anisotropy);
float ComputeAngleFromRoughness( float Roughness, const float Threshold = 0.04f );
float ComputeRoughnessFromAngle( float Angle, const float Threshold = 0.04f );
float AddAngleToRoughness( float Angle, float Roughness );
float EncodeShadingModelIdAndSelectiveOutputMask(uint ShadingModelId, uint SelectiveOutputMask);
uint DecodeShadingModelId(float InPackedChannel);
uint DecodeSelectiveOutputMask(float InPackedChannel);

// 檢測介面.
bool IsSubsurfaceModel(int ShadingModel);
bool UseSubsurfaceProfile(int ShadingModel);
bool HasCustomGBufferData(int ShadingModelID);
bool HasAnisotropy(int SelectiveOutputMask);
bool CastContactShadow(FGBufferData GBufferData);
bool HasDynamicIndirectShadowCasterRepresentation(FGBufferData GBufferData);

// GBuffer資料的結構體, 是幾何資料的最大集合.
struct FGBufferData
{
    float3 WorldNormal;
    float3 WorldTangent;
    float3 DiffuseColor;
    float3 SpecularColor;
    float3 BaseColor;
    float Metallic;
    float Specular;
    float4 CustomData;
    float IndirectIrradiance;
    float4 PrecomputedShadowFactors;
    float Roughness;
    float Anisotropy;
    float GBufferAO;
    uint ShadingModelID;
    uint SelectiveOutputMask;
    float PerObjectGBufferData;
    float CustomDepth;
    uint CustomStencil;
    float Depth;
    float4 Velocity;
    float3 StoredBaseColor;
    float StoredSpecular;
    float StoredMetallic;
};

// 螢屏空間資料, 包含了GBuffer和AO.
struct FScreenSpaceData
{
    // GBuffer (material attributes from forward rendering pass)
    FGBufferData GBuffer;
    float AmbientOcclusion;
};

// GBuffer資料操作介面.
void SetGBufferForUnlit(out float4 OutGBufferB);
void EncodeGBuffer(FGBufferData GBuffer, out float4 OutGBufferA, ...);
FGBufferData DecodeGBufferData(float4 InGBufferA, ...);
FGBufferData GetGBufferDataUint(uint2 PixelPos, bool bGetNormalizedNormal);
FScreenSpaceData GetScreenSpaceData(float2 UV, bool bGetNormalizedNormal)
float3 ExtractSubsurfaceColor(FGBufferData BufferData)
uint ExtractSubsurfaceProfileInt(FGBufferData BufferData)
uint GetShadingModelId(float2 UV);
void AdjustBaseColorAndSpecularColorForSubsurfaceProfileLighting(inout float3 BaseColor, ...);

// 棋盤采樣.
bool CheckerFromPixelPos(uint2 PixelPos);
bool CheckerFromSceneColorUV(float2 UVSceneColor);

需要注意的是核心結構體FGBufferData,它是通用的結構體,可用于BasePass和LightingPass的VS和PS之間,也可用于前向和延遲渲染中,是最大資料集合體,在某些情況下,部分屬性才有效,具體見ShadingModelsMaterial.ush的SetGBufferForShadingModel

5.2.3.11 DeferredLightingCommon.ush

此模塊定義了延遲光照相關的通用的介面、宏、變數、型別等,

// 單個延遲光源資料(實際也可用于前向渲染),
struct FDeferredLightData
{
    float3 Position;
    float  InvRadius;
    float3 Color;
    float  FalloffExponent;
    float3 Direction;
    float3 Tangent;
    float SoftSourceRadius;
    float2 SpotAngles;
    float SourceRadius;
    float SourceLength;
    float SpecularScale;
    float ContactShadowLength;
    float ContactShadowNonShadowCastingIntensity;
    float2 DistanceFadeMAD;
    float4 ShadowMapChannelMask;
    bool ContactShadowLengthInWS;
    bool bInverseSquared;
    bool bRadialLight;
    bool bSpotLight;
    bool bRectLight;
    uint ShadowedBits;
    float RectLightBarnCosAngle;
    float RectLightBarnLength;

    FHairTransmittanceData HairTransmittance;
};

// 簡單延遲光源資料,模擬簡易的光源,常用于特定的簡單的著色模型中,以加速和限制特性基,
struct FSimpleDeferredLightData
{
    float3 Position;
    float  InvRadius;
    float3 Color;
    float  FalloffExponent;
    bool bInverseSquared;
};

// 根據光源和攝像機的資料計算漸隱程度,(0表示比漸隱**面更*,1比漸隱遠*面更遠,
float DistanceFromCameraFade(float SceneDepth, FDeferredLightData LightData, float3 WorldPosition, float3 CameraPosition);

// ---- 陰影相關的介面 ----
// 陰影射線檢測. 如果沒有檢測到, 則回傳負數. 如果射線擊中了動態陰影投射者, 則bOutHitCastDynamicShadow為true.
float ShadowRayCast(float3 RayOriginTranslatedWorld, float3 RayDirection, float RayLength, int NumSteps, float StepOffset, out bool bOutHitCastContactShadow );
// 計算指定光源的陰影.
void GetShadowTerms(FGBufferData GBuffer, FDeferredLightData LightData, float3 WorldPosition, float3 L, float4 LightAttenuation, float Dither, inout FShadowTerms Shadow);

// 獲取光源對應的幾何形狀.
FRect GetRect(float3 ToLight, FDeferredLightData LightData);
FCapsuleLight GetCapsule( float3 ToLight, FDeferredLightData LightData );

// ---- 光照計算介面 ----
// 獲取指定光源的直接光, 光照結果拆分了漫反射和高光項.
FDeferredLightingSplit GetDynamicLightingSplit(float3 WorldPosition, float3 CameraVector, FGBufferData GBuffer, ...);
// 獲取指定光源的直接光.
float4 GetDynamicLighting(float3 WorldPosition, float3 CameraVector, FGBufferData GBuffer, ...);
// 獲取指定簡單光源的直接光.
float3 GetSimpleDynamicLighting(float3 WorldPosition, float3 CameraVector, float3 WorldNormal, ...);

5.2.3.12 ShadingModelsMaterial.ush

提供了根據材質和指定引數設定GBuffe的介面:

#define SUBSURFACE_PROFILE_OPACITY_THRESHOLD 1

void SetGBufferForShadingModel(
    in out FGBufferData GBuffer, 
    in const FMaterialPixelParameters MaterialParameters,
    const float Opacity,
    const half3 BaseColor,
    const half  Metallic,
    const half  Specular,
    const float Roughness,
    const float Anisotropy,
    const float3 SubsurfaceColor,
    const float SubsurfaceProfile,
    const float Dither,
    const uint ShadingModel)
{
    GBuffer.WorldNormal = MaterialParameters.WorldNormal;
    GBuffer.WorldTangent = MaterialParameters.WorldTangent;
    GBuffer.BaseColor = BaseColor;
    GBuffer.Metallic = Metallic;
    GBuffer.Specular = Specular;
    GBuffer.Roughness = Roughness;
    GBuffer.Anisotropy = Anisotropy;
    GBuffer.ShadingModelID = ShadingModel;

    (......)
    
#if MATERIAL_SHADINGMODEL_SUBSURFACE
    else if (ShadingModel == SHADINGMODELID_SUBSURFACE)
    {
        GBuffer.CustomData.rgb = EncodeSubsurfaceColor(SubsurfaceColor);
        GBuffer.CustomData.a = Opacity;
    }
#endif
#if MATERIAL_SHADINGMODEL_PREINTEGRATED_SKIN
    else if (ShadingModel == SHADINGMODELID_PREINTEGRATED_SKIN)
    {
        GBuffer.CustomData.rgb = EncodeSubsurfaceColor(SubsurfaceColor);
        GBuffer.CustomData.a = Opacity;
    }
#endif
    
    (......)
}

5.2.3.13 LocalVertexFactoryCommon.ush

區域頂點工廠通用模塊,定義了頂點工廠的資料插值結構體及部分輔助介面:

// 頂點工廠VS -> PS的插值.
struct FVertexFactoryInterpolantsVSToPS
{
    TANGENTTOWORLD_INTERPOLATOR_BLOCK

#if INTERPOLATE_VERTEX_COLOR
    half4    Color : COLOR0;
#endif

#if USE_INSTANCING
    // x = per-instance random, y = per-instance fade out amount, z = hide/show flag, w dither fade cutoff
    float4  PerInstanceParams : COLOR1;
#endif

#if NUM_TEX_COORD_INTERPOLATORS
    float4    TexCoords[(NUM_TEX_COORD_INTERPOLATORS+1)/2]    : TEXCOORD0;
#elif USE_PARTICLE_SUBUVS
    float4    TexCoords[1] : TEXCOORD0;
#endif

#if NEEDS_LIGHTMAP_COORDINATE
    float4    LightMapCoordinate : TEXCOORD4;
#endif

#if INSTANCED_STEREO
    nointerpolation uint EyeIndex : PACKED_EYE_INDEX;
#endif
#if VF_USE_PRIMITIVE_SCENE_DATA
    nointerpolation uint PrimitiveId : PRIMITIVE_ID;
    #if NEEDS_LIGHTMAP_COORDINATE
        nointerpolation uint LightmapDataIndex : LIGHTMAP_ID;
    #endif
#endif
#if VF_STRAND_HAIR || VF_CARDS_HAIR
    nointerpolation uint HairPrimitiveId    : HAIR_PRIMITIVE_ID; // Control point ID
    float2 HairPrimitiveUV                    : HAIR_PRIMITIVE_UV; // U: parameteric distance between the two surrounding control points. V: parametric distance along the width.
#endif
};

// UV
#if NUM_TEX_COORD_INTERPOLATORS || USE_PARTICLE_SUBUVS
float2 GetUV(FVertexFactoryInterpolantsVSToPS Interpolants, int UVIndex);
void SetUV(inout FVertexFactoryInterpolantsVSToPS Interpolants, int UVIndex, float2 InValue);
#endif

// 顏色
float4 GetColor(FVertexFactoryInterpolantsVSToPS Interpolants);
void SetColor(inout FVertexFactoryInterpolantsVSToPS Interpolants, float4 InValue);

// 光照圖坐標
void SetLightmapDataIndex(inout FVertexFactoryInterpolantsVSToPS Interpolants, uint LightmapDataIndex);
#if NEEDS_LIGHTMAP_COORDINATE
void GetLightMapCoordinates(FVertexFactoryInterpolantsVSToPS Interpolants, out float2 LightmapUV0, out float2 LightmapUV1, out uint LightmapDataIndex);
void GetShadowMapCoordinate(FVertexFactoryInterpolantsVSToPS Interpolants, out float2 ShadowMapCoordinate, out uint LightmapDataIndex);
void SetLightMapCoordinate(inout FVertexFactoryInterpolantsVSToPS Interpolants, float2 InLightMapCoordinate, float2 InShadowMapCoordinate);
#endif

// 切線
float4 GetTangentToWorld2(FVertexFactoryInterpolantsVSToPS Interpolants);
float4 GetTangentToWorld0(FVertexFactoryInterpolantsVSToPS Interpolants);
void SetTangents(inout FVertexFactoryInterpolantsVSToPS Interpolants, float3 InTangentToWorld0, float3 InTangentToWorld2, float InTangentToWorldSign);

// 圖元id.
uint GetPrimitiveId(FVertexFactoryInterpolantsVSToPS Interpolants);
void SetPrimitiveId(inout FVertexFactoryInterpolantsVSToPS Interpolants, uint PrimitiveId);

5.2.3.14 LocalVertexFactory.ush

區域頂點工廠模塊,定義了骨骼蒙皮、頂點著色器等相關的資料型別和介面:

#if GPUSKIN_PASS_THROUGH
#include "GpuSkinCommon.ush"
#endif

#include "/Engine/Generated/UniformBuffers/PrecomputedLightingBuffer.ush"

(......)

#if USE_INSTANCING
#if USE_DITHERED_LOD_TRANSITION
    float4 InstancingViewZCompareZero;  // w contains random lod scale 
    float4 InstancingViewZCompareOne;
    float4 InstancingViewZConstant;
    float4 InstancingWorldViewOriginZero;
    float4 InstancingWorldViewOriginOne;
#endif

float4 InstancingOffset;
float4 InstancingFadeOutParams;
uint InstanceOffset;
#endif    // USE_INSTANCING

(......)

#if MANUAL_VERTEX_FETCH
    #define VF_ColorIndexMask_Index 0
    #define VF_NumTexcoords_Index 1
    #define FV_LightMapIndex_Index 2
    #define VF_VertexOffset 3

    Buffer<float4> VertexFetch_InstanceOriginBuffer;
    Buffer<float4> VertexFetch_InstanceTransformBuffer;
    Buffer<float4> VertexFetch_InstanceLightmapBuffer;

    #if USE_INSTANCING && USE_INSTANCING_BONEMAP
        Buffer<float4> VertexFetch_InstancePrevTransformBuffer;
        Buffer<uint> VertexFetch_InstanceBoneMapBuffer;
    #endif
#endif //! MANUAL_VERTEX_FETCH

// 從系結的頂點Buffer中獲取的頂點輸入資料.
struct FVertexFactoryInput
{
    // 位置.
    float4    Position    : ATTRIBUTE0;

    // 切線和顏色
#if !MANUAL_VERTEX_FETCH
    #if METAL_PROFILE
        float3    TangentX    : ATTRIBUTE1;
        // TangentZ.w contains sign of tangent basis determinant
        float4    TangentZ    : ATTRIBUTE2;

        float4    Color        : ATTRIBUTE3;
    #else
        half3    TangentX    : ATTRIBUTE1;
        // TangentZ.w contains sign of tangent basis determinant
        half4    TangentZ    : ATTRIBUTE2;

        half4    Color        : ATTRIBUTE3;
    #endif
#endif

    // 紋理坐標.
#if NUM_MATERIAL_TEXCOORDS_VERTEX
    #if !MANUAL_VERTEX_FETCH
        #if GPUSKIN_PASS_THROUGH
            // These must match GPUSkinVertexFactory.usf
            float2    TexCoords[NUM_MATERIAL_TEXCOORDS_VERTEX] : ATTRIBUTE4;
            #if NUM_MATERIAL_TEXCOORDS_VERTEX > 4
                #error Too many texture coordinate sets defined on GPUSkin vertex input. Max: 4.
            #endif
        #else
            #if NUM_MATERIAL_TEXCOORDS_VERTEX > 1
                float4    PackedTexCoords4[NUM_MATERIAL_TEXCOORDS_VERTEX/2] : ATTRIBUTE4;
            #endif
            #if NUM_MATERIAL_TEXCOORDS_VERTEX == 1
                float2    PackedTexCoords2 : ATTRIBUTE4;
            #elif NUM_MATERIAL_TEXCOORDS_VERTEX == 3
                float2    PackedTexCoords2 : ATTRIBUTE5;
            #elif NUM_MATERIAL_TEXCOORDS_VERTEX == 5
                float2    PackedTexCoords2 : ATTRIBUTE6;
            #elif NUM_MATERIAL_TEXCOORDS_VERTEX == 7
                float2    PackedTexCoords2 : ATTRIBUTE7;
            #endif
        #endif
    #endif
#elif USE_PARTICLE_SUBUVS && !MANUAL_VERTEX_FETCH
    float2    TexCoords[1] : ATTRIBUTE4;
#endif

    // 實體化資料.
#if USE_INSTANCING && !MANUAL_VERTEX_FETCH
    float4 InstanceOrigin : ATTRIBUTE8;  // per-instance random in w 
    half4 InstanceTransform1 : ATTRIBUTE9;  // hitproxy.r + 256 * selected in .w
    half4 InstanceTransform2 : ATTRIBUTE10; // hitproxy.g in .w
    half4 InstanceTransform3 : ATTRIBUTE11; // hitproxy.b in .w
    float4 InstanceLightmapAndShadowMapUVBias : ATTRIBUTE12; 
#endif //USE_INSTANCING

    // 圖元id, 用于從GPU Scene訪問資料.
#if VF_USE_PRIMITIVE_SCENE_DATA
    uint PrimitiveId : ATTRIBUTE13;
#endif

    // 光照圖坐標.
#if NEEDS_LIGHTMAP_COORDINATE && !MANUAL_VERTEX_FETCH
    float2    LightMapCoordinate : ATTRIBUTE15;
#endif

    // 實體化ID.
#if USE_INSTANCING
    uint InstanceId    : SV_InstanceID;
#endif

    // 頂點ID.
#if GPUSKIN_PASS_THROUGH || MANUAL_VERTEX_FETCH
    uint VertexId : SV_VertexID;
#endif
};

#if RAYHITGROUPSHADER || COMPUTESHADER
#if GPUSKIN_PASS_THROUGH
Buffer<float> GPUSkinCachePositionBuffer;
#endif

#endif

// 計算著色器相關.
#if COMPUTESHADER
FVertexFactoryInput LoadVertexFactoryInputForDynamicUpdate(uint TriangleIndex, int VertexIndex, uint PrimitiveId);
#endif

// 只有位置資訊的頂點資料輸入.
struct FPositionOnlyVertexFactoryInput
{
    float4    Position    : ATTRIBUTE0;

#if USE_INSTANCING && !MANUAL_VERTEX_FETCH
    float4 InstanceOrigin : ATTRIBUTE8;  // per-instance random in w 
    half4 InstanceTransform1 : ATTRIBUTE9;  // hitproxy.r + 256 * selected in .w
    half4 InstanceTransform2 : ATTRIBUTE10; // hitproxy.g in .w
    half4 InstanceTransform3 : ATTRIBUTE11; // hitproxy.b in .w
#endif    // USE_INSTANCING

#if VF_USE_PRIMITIVE_SCENE_DATA
    uint PrimitiveId : ATTRIBUTE1;
#endif

#if USE_INSTANCING
    uint InstanceId    : SV_InstanceID;
#endif

#if MANUAL_VERTEX_FETCH
    uint VertexId : SV_VertexID;
#endif
};

// 僅包含位置和法線的頂點資料輸入.
struct FPositionAndNormalOnlyVertexFactoryInput
{
    float4    Position    : ATTRIBUTE0;
    float4    Normal        : ATTRIBUTE2;

#if USE_INSTANCING && !MANUAL_VERTEX_FETCH
    float4 InstanceOrigin : ATTRIBUTE8;  // per-instance random in w 
    half4 InstanceTransform1 : ATTRIBUTE9;  // hitproxy.r + 256 * selected in .w
    half4 InstanceTransform2 : ATTRIBUTE10; // hitproxy.g in .w
    half4 InstanceTransform3 : ATTRIBUTE11; // hitproxy.b in .w
#endif    // USE_INSTANCING

#if VF_USE_PRIMITIVE_SCENE_DATA
    uint PrimitiveId : ATTRIBUTE1;
#endif

#if  USE_INSTANCING
    uint InstanceId    : SV_InstanceID;
#endif

#if MANUAL_VERTEX_FETCH
    uint VertexId : SV_VertexID;
#endif
};

// 快取中間計算結果的頂點資料, 防止重復計算多次.
struct FVertexFactoryIntermediates
{
    half3x3 TangentToLocal;
    half3x3 TangentToWorld;
    half TangentToWorldSign;

    half4 Color;
#if USE_INSTANCING
        float4 InstanceOrigin;
        float4 InstanceTransform1;
        float4 InstanceTransform2;
        float4 InstanceTransform3;

        #if USE_INSTANCING_BONEMAP
            float4 InstancePrevOrigin;
            float4 InstancePrevTransform1;
            float4 InstancePrevTransform2;
            float4 InstancePrevTransform3;
        #endif

        float4 InstanceLightmapAndShadowMapUVBias;

    // x = per-instance random, y = per-instance fade out amount, z = hide/show flag, w dither fade cutoff
    float4 PerInstanceParams;
#endif    // USE_INSTANCING
    uint PrimitiveId;

    float3 PreSkinPosition;
};

// 獲取實體化資料介面.
#if USE_INSTANCING
float4x4 GetInstanceTransform(FVertexFactoryIntermediates Intermediates);
float4x4 GetInstancePrevTransform(FVertexFactoryIntermediates Intermediates);
float4x4 GetInstanceTransform(FPositionOnlyVertexFactoryInput Input);
float4x4 GetInstanceTransform(FPositionAndNormalOnlyVertexFactoryInput Input);
half3x3 GetInstanceToLocal3x3(FVertexFactoryIntermediates Intermediates);
float2 GetInstanceShadowMapBias(FVertexFactoryIntermediates Intermediates);
float2 GetInstanceLightMapBias(FVertexFactoryIntermediates Intermediates);
float GetInstanceSelected(FVertexFactoryIntermediates Intermediates);
float GetInstanceRandom(FVertexFactoryIntermediates Intermediates);
float3 GetInstanceOrigin(FVertexFactoryIntermediates Intermediates);
#endif    // USE_INSTANCING

// 從插值結構體中獲取材質引數.
FMaterialPixelParameters GetMaterialPixelParameters(FVertexFactoryInterpolantsVSToPS Interpolants, float4 SvPosition);
half3x3 CalcTangentToWorldNoScale(FVertexFactoryIntermediates Intermediates, half3x3 TangentToLocal);
FMaterialVertexParameters GetMaterialVertexParameters(FVertexFactoryInput Input, ...);

(......)

// 頂點資料計算和獲取介面.
float4 CalcWorldPosition(float4 Position, uint PrimitiveId);
half3x3 CalcTangentToLocal(FVertexFactoryInput Input, out float TangentSign);
half3x3 CalcTangentToWorld(FVertexFactoryIntermediates Intermediates, ...);
FVertexFactoryIntermediates GetVertexFactoryIntermediates(FVertexFactoryInput Input);
half3x3 VertexFactoryGetTangentToLocal( FVertexFactoryInput Input, ...);
float4 VertexFactoryGetWorldPosition(FVertexFactoryInput Input, ...);
float4 VertexFactoryGetRasterizedWorldPosition(FVertexFactoryInput Input, ...);
float3 VertexFactoryGetPositionForVertexLighting(FVertexFactoryInput Input, ...);
FVertexFactoryInterpolantsVSToPS VertexFactoryGetInterpolantsVSToPS(FVertexFactoryInput Input, ...);
float4 VertexFactoryGetWorldPosition(FPositionOnlyVertexFactoryInput Input);
float4 VertexFactoryGetWorldPosition(FPositionAndNormalOnlyVertexFactoryInput Input);
float3 VertexFactoryGetWorldNormal(FPositionAndNormalOnlyVertexFactoryInput Input);
float3 VertexFactoryGetWorldNormal(FVertexFactoryInput Input, ...);
float4 VertexFactoryGetPreviousWorldPosition(FVertexFactoryInput Input, ...);
float4 VertexFactoryGetInstanceHitProxyId(FVertexFactoryInput Input, ...);
float4 VertexFactoryGetTranslatedPrimitiveVolumeBounds(FVertexFactoryInterpolantsVSToPS Interpolants);
uint VertexFactoryGetPrimitiveId(FVertexFactoryInterpolantsVSToPS Interpolants);

(......)

由于頂點工廠需要兼容眾多型別,添加了大量宏來控制資料成員,導致代碼臃腫,可讀性變差,這也是Uber Shader(全能著色器)設計框架的弊端,

 

5.3 BasePass

本節主要詳細闡述BasePass的渲染流程、渲染狀態、Shader邏輯等,

5.3.1 BasePass渲染流程

上一篇也涉及到BasePass的渲染流程和部分核心邏輯,本小節簡單回顧一下,BasePass在FDeferredShadingSceneRenderer::Render是在PrePass之后LightingPass之前:

void FDeferredShadingSceneRenderer::Render(FRHICommandListImmediate& RHICmdList)
{
    (......)
    
    // 繪制場景深度.
    RenderPrePass(RHICmdList, ...);

    (......)
    
    // 渲染Base Pass.
    RenderBasePass(RHICmdList, ...);

    (......)
    
    // 渲染光源.
    RenderLights(RHICmdList, ...);
    
    (......)
}

下面是RenderBasePassRenderBasePassViewParallel并行渲染的邏輯:

bool FDeferredShadingSceneRenderer::RenderBasePass(FRHICommandListImmediate& RHICmdList, FExclusiveDepthStencil::Type BasePassDepthStencilAccess, IPooledRenderTarget* ForwardScreenSpaceShadowMask, bool bParallelBasePass, bool bRenderLightmapDensity)
{
    (......)
    
    FExclusiveDepthStencil::Type BasePassDepthStencilAccess_NoDepthWrite = FExclusiveDepthStencil::Type(BasePassDepthStencilAccess & ~FExclusiveDepthStencil::DepthWrite);
    // 并行模式
    if (bParallelBasePass)
    {
        // 繪制任務等待.
        FScopedCommandListWaitForTasks Flusher(CVarRHICmdFlushRenderThreadTasksBasePass.GetValueOnRenderThread() > 0 || CVarRHICmdFlushRenderThreadTasks.GetValueOnRenderThread() > 0, RHICmdList);
        
        // 遍歷所有view, 每個view渲染一次Base Pass.
        for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
        {
            FViewInfo& View = Views[ViewIndex];

            // Uniform Buffer
            TUniformBufferRef<FOpaqueBasePassUniformParameters> BasePassUniformBuffer;
            CreateOpaqueBasePassUniformBuffer(RHICmdList, View, ForwardScreenSpaceShadowMask, nullptr, nullptr, nullptr, BasePassUniformBuffer);

            // Render State
            FMeshPassProcessorRenderState DrawRenderState(View, BasePassUniformBuffer);
            SetupBasePassState(BasePassDepthStencilAccess, ViewFamily.EngineShowFlags.ShaderComplexity, DrawRenderState);

            const bool bShouldRenderView = View.ShouldRenderView();
            if (bShouldRenderView)
            {
                Scene->UniformBuffers.UpdateViewUniformBuffer(View);

                // 執行并行渲染.
                RenderBasePassViewParallel(View, RHICmdList, BasePassDepthStencilAccess, DrawRenderState);
            }

            FSceneRenderTargets::Get(RHICmdList).BeginRenderingGBuffer(RHICmdList, ERenderTargetLoadAction::ELoad, ERenderTargetLoadAction::ELoad, BasePassDepthStencilAccess, this->ViewFamily.EngineShowFlags.ShaderComplexity);
            RHICmdList.EndRenderPass();

            (......)
        }
    }

    (......)
}

void FDeferredShadingSceneRenderer::RenderBasePassViewParallel(FViewInfo& View, FRHICommandListImmediate& ParentCmdList, FExclusiveDepthStencil::Type BasePassDepthStencilAccess, const FMeshPassProcessorRenderState& InDrawRenderState)
{
    // 并行繪制的資料: 命令佇列, 背景關系, 渲染狀態等.
    FBasePassParallelCommandListSet ParallelSet(View, ParentCmdList, 
        CVarRHICmdBasePassDeferredContexts.GetValueOnRenderThread() > 0, 
        CVarRHICmdFlushRenderThreadTasksBasePass.GetValueOnRenderThread() == 0 && CVarRHICmdFlushRenderThreadTasks.GetValueOnRenderThread() == 0,
        this,
        BasePassDepthStencilAccess,
        InDrawRenderState);

    // 觸發并行繪制指令.
    View.ParallelMeshDrawCommandPasses[EMeshPass::BasePass].DispatchDraw(&ParallelSet, ParentCmdList);
}

RenderBasePass時依靠FScopedCommandListWaitForTasks等待繪制指令完成,下面是后者的實作代碼:

// Engine\Source\Runtime\RHI\Public\RHICommandList.h

// 立即重繪型別.
namespace EImmediateFlushType
{
    enum Type
    { 
        WaitForOutstandingTasksOnly = 0, // 只等待當前未完成的任務.
        DispatchToRHIThread,  // 發送到RHI執行緒.
        WaitForDispatchToRHIThread, // 等待發送到RHI執行緒.
        FlushRHIThread, // 重繪RHI執行緒到GPU.
        FlushRHIThreadFlushResources, // 重繪RHI執行緒且重繪資源.
        FlushRHIThreadFlushResourcesFlushDeferredDeletes // 重繪RHI執行緒且重繪資源且重繪延遲的資源洗掉指令.
    };
};

struct FScopedCommandListWaitForTasks
{
    FRHICommandListImmediate& RHICmdList; // 需要等待的RHICmdList.
    bool bWaitForTasks; // 是否等待任務.

    FScopedCommandListWaitForTasks(bool InbWaitForTasks, FRHICommandListImmediate& InRHICmdList)
        : RHICmdList(InRHICmdList)
        , bWaitForTasks(InbWaitForTasks)
    {
    }
    
    // 等待在解構式中執行.
    ~FScopedCommandListWaitForTasks()
    {
        if (bWaitForTasks)
        {
            // 如果是獨立的RHI執行緒, 則只等待當前未完成的任務.
            if (IsRunningRHIInSeparateThread())
            {
                RHICmdList.ImmediateFlush(EImmediateFlushType::WaitForOutstandingTasksOnly);
            }
            // 非獨立的RHI執行緒,直接重繪RHI執行緒.
            else
            {
                RHICmdList.ImmediateFlush(EImmediateFlushType::FlushRHIThread);
            }
        }
    }
};

至于是否需要等待BasePass渲染完成,由CVarRHICmdFlushRenderThreadTasksBasePass(r.RHICmdFlushRenderThreadTasksBasePass)或CVarRHICmdFlushRenderThreadTasks(r.RHICmdFlushRenderThreadTasks)兩個控制臺命令的其中一個來開啟,

5.3.2 BasePass渲染狀態

本節詳細闡述BasePass渲染時使用的各類渲染狀態、Shader系結及繪制引數,我們知道BasePass是通過FBasePassMeshProcessor來收集很多shader系結和繪制引數的,從Processor的程序中可以很容易知道,BasePass繪制時使用的VS和PS分別是TBasePassVSTBasePassPS

// Engine\Source\Runtime\Renderer\Private\BasePassRendering.cpp

void FBasePassMeshProcessor::Process(const FMeshBatch& RESTRICT MeshBatch, ...)
{
    (......)

    TMeshProcessorShaders<
        TBasePassVertexShaderPolicyParamType<LightMapPolicyType>,
        FBaseHS,
        FBaseDS,
        TBasePassPixelShaderPolicyParamType<LightMapPolicyType>> BasePassShaders;

    GetBasePassShaders<LightMapPolicyType>(MaterialResource,VertexFactory->GetType(), ...);
    
    (......)
}

// Engine\Source\Runtime\Renderer\Private\BasePassRendering.h

template <typename LightMapPolicyType>
void GetBasePassShaders(const FMaterial& Material, ...)
{
    (......)

    VertexShader = Material.GetShader<TBasePassVS<LightMapPolicyType, false> >(VertexFactoryType);
    PixelShader = Material.GetShader<TBasePassPS<LightMapPolicyType, false> >(VertexFactoryType);
    
    (......)
}

先分析TBasePassVS,它有兩個父類:

// Engine\Source\Runtime\Renderer\Private\BasePassRendering.h

template<typename LightMapPolicyType>
class TBasePassVertexShaderPolicyParamType : public FMeshMaterialShader, public LightMapPolicyType::VertexParametersType
{
protected:
    (......)

    TBasePassVertexShaderPolicyParamType(const FMeshMaterialShaderType::CompiledShaderInitializerType& Initializer): FMeshMaterialShader(Initializer)
    {
        LightMapPolicyType::VertexParametersType::Bind(Initializer.ParameterMap);
        ReflectionCaptureBuffer.Bind(Initializer.ParameterMap, TEXT("ReflectionCapture"));
    }

public:
    // 獲取Shader系結,
    void GetShaderBindings(const FScene* Scene, ERHIFeatureLevel::Type FeatureLevel, ...) const;
    void GetElementShaderBindings(const FShaderMapPointerTable& PointerTable, const FScene* Scene, ...) const;

    // 反射球Uniform Buffer.
    LAYOUT_FIELD(FShaderUniformBufferParameter, ReflectionCaptureBuffer);
};


template<typename LightMapPolicyType>
class TBasePassVertexShaderBaseType : public TBasePassVertexShaderPolicyParamType<LightMapPolicyType>
{
    (......)
    
public:
    static bool ShouldCompilePermutation(const FMeshMaterialShaderPermutationParameters& Parameters)
    {
        return LightMapPolicyType::ShouldCompilePermutation(Parameters);
    }

    static void ModifyCompilationEnvironment(const FMaterialShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
    {
        LightMapPolicyType::ModifyCompilationEnvironment(Parameters, OutEnvironment);
        Super::ModifyCompilationEnvironment(Parameters, OutEnvironment);
    }
};

// BasePass頂點著色器.
template<typename LightMapPolicyType, bool bEnableAtmosphericFog>
class TBasePassVS : public TBasePassVertexShaderBaseType<LightMapPolicyType>
{
    (......)
    
public:
    // 禁掉部分排列的shader編譯.
    static bool ShouldCompilePermutation(const FMeshMaterialShaderPermutationParameters& Parameters)
    {
        (......)
        
        return bShouldCache
            && (IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::SM5));
    }

    // 修改編譯環境: 宏定義.
    static void ModifyCompilationEnvironment(const FMaterialShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
    {
        Super::ModifyCompilationEnvironment(Parameters, OutEnvironment);
        OutEnvironment.SetDefine(TEXT("BASEPASS_ATMOSPHERIC_FOG"), !IsMetalMRTPlatform(Parameters.Platform) ? bEnableAtmosphericFog : 0);
    }
};

根據以上代碼,可知TBasePassVS提供了獲取Shader系結、更改編譯環境和只編譯指定的排列組合shader等介面,此外還擁有反射球、光照圖型別等屬性,下面是它的獲取shader系結的代碼分析:

// Engine\Source\Runtime\Renderer\Private\BasePassRendering.inl

// 獲取指定圖元的VS的shader系結,
template<typename LightMapPolicyType>
void TBasePassVertexShaderPolicyParamType<LightMapPolicyType>::GetShaderBindings(
    const FScene* Scene,
    ERHIFeatureLevel::Type FeatureLevel,
    const FPrimitiveSceneProxy* PrimitiveSceneProxy,
    const FMaterialRenderProxy& MaterialRenderProxy,
    const FMaterial& Material,
    const FMeshPassProcessorRenderState& DrawRenderState,
    const TBasePassShaderElementData<LightMapPolicyType>& ShaderElementData,
    FMeshDrawSingleShaderBindings& ShaderBindings) const
{
    // 先獲取FMeshMaterialShader的shader系結.
    FMeshMaterialShader::GetShaderBindings(Scene, FeatureLevel, PrimitiveSceneProxy, MaterialRenderProxy, Material, DrawRenderState, ShaderElementData, ShaderBindings);

    // 有場景實體, 則獲取場景的反射球Buffer.
    if (Scene)
    {
        FRHIUniformBuffer* ReflectionCaptureUniformBuffer = Scene->UniformBuffers.ReflectionCaptureUniformBuffer.GetReference();
        ShaderBindings.Add(ReflectionCaptureBuffer, ReflectionCaptureUniformBuffer);
    }
    // 沒有場景, 則創建一個默認的反射球Buffer.
    else
    {
        ShaderBindings.Add(ReflectionCaptureBuffer, DrawRenderState.GetReflectionCaptureUniformBuffer());
    }

    // 最后從光照圖型別中獲取shader系結.
    LightMapPolicyType::GetVertexShaderBindings(
        PrimitiveSceneProxy,
        ShaderElementData.LightMapPolicyElementData,
        this,
        ShaderBindings);
}

// 獲取指定FMeshBatchElement的VS的shader系結,
template<typename LightMapPolicyType>
void TBasePassVertexShaderPolicyParamType<LightMapPolicyType>::GetElementShaderBindings(
    const FShaderMapPointerTable& PointerTable,
    const FScene* Scene, 
    const FSceneView* ViewIfDynamicMeshCommand, 
    const FVertexFactory* VertexFactory,
    const EVertexInputStreamType InputStreamType,
    ERHIFeatureLevel::Type FeatureLevel,
    const FPrimitiveSceneProxy* PrimitiveSceneProxy,
    const FMeshBatch& MeshBatch,
    const FMeshBatchElement& BatchElement, 
    const TBasePassShaderElementData<LightMapPolicyType>& ShaderElementData,
    FMeshDrawSingleShaderBindings& ShaderBindings,
    FVertexInputStreamArray& VertexStreams) const
{
    // 直接呼叫FMeshMaterialShader的對應介面.
    FMeshMaterialShader::GetElementShaderBindings(PointerTable, Scene, ViewIfDynamicMeshCommand, VertexFactory, InputStreamType, FeatureLevel, PrimitiveSceneProxy, MeshBatch, BatchElement, ShaderElementData, ShaderBindings, VertexStreams);
}

下面開始分析TBasePassPS的代碼,跟VS類似,也有兩個父類:

// Engine\Source\Runtime\Renderer\Private\BasePassRendering.h

template<typename LightMapPolicyType>
class TBasePassPixelShaderPolicyParamType : public FMeshMaterialShader, public LightMapPolicyType::PixelParametersType
{
public:
    static void ModifyCompilationEnvironment(const FMaterialShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment);
    static bool ValidateCompiledResult(EShaderPlatform Platform, const FShaderParameterMap& ParameterMap, TArray<FString>& OutError);

    TBasePassPixelShaderPolicyParamType(const FMeshMaterialShaderType::CompiledShaderInitializerType& Initializer): FMeshMaterialShader(Initializer)
    {
        LightMapPolicyType::PixelParametersType::Bind(Initializer.ParameterMap);
        ReflectionCaptureBuffer.Bind(Initializer.ParameterMap, TEXT("ReflectionCapture"));

        (......)
    }

    // 獲取shader系結.
    void GetShaderBindings(
        const FScene* Scene,
        ERHIFeatureLevel::Type FeatureLevel,
        const FPrimitiveSceneProxy* PrimitiveSceneProxy,
        const FMaterialRenderProxy& MaterialRenderProxy,
        const FMaterial& Material,
        const FMeshPassProcessorRenderState& DrawRenderState,
        const TBasePassShaderElementData<LightMapPolicyType>& ShaderElementData,
        FMeshDrawSingleShaderBindings& ShaderBindings) const;

private:
    // ReflectionCapture緩沖區.
    LAYOUT_FIELD(FShaderUniformBufferParameter, ReflectionCaptureBuffer);
};

template<typename LightMapPolicyType>
class TBasePassPixelShaderBaseType : public TBasePassPixelShaderPolicyParamType<LightMapPolicyType>
{
public:
    static bool ShouldCompilePermutation(const FMeshMaterialShaderPermutationParameters& Parameters)
    {
        return LightMapPolicyType::ShouldCompilePermutation(Parameters);
    }

    static void ModifyCompilationEnvironment(const FMaterialShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
    {
        LightMapPolicyType::ModifyCompilationEnvironment(Parameters, OutEnvironment);
        Super::ModifyCompilationEnvironment(Parameters, OutEnvironment);
    }
};

// BasePass的像素著色器.
template<typename LightMapPolicyType, bool bEnableSkyLight>
class TBasePassPS : public TBasePassPixelShaderBaseType<LightMapPolicyType>
{
public:
    // 開啟指定排列的shader.
    static bool ShouldCompilePermutation(const FMeshMaterialShaderPermutationParameters& Parameters)
    {
        (......)
        return bCacheShaders
            && (IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::SM5))
            && TBasePassPixelShaderBaseType<LightMapPolicyType>::ShouldCompilePermutation(Parameters);
    }

    // 修改編譯環境.
    static void ModifyCompilationEnvironment(const FMaterialShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
    {
        OutEnvironment.SetDefine(TEXT("SCENE_TEXTURES_DISABLED"), Parameters.MaterialParameters.MaterialDomain != MD_Surface);
        OutEnvironment.SetDefine(TEXT("COMPILE_BASEPASS_PIXEL_VOLUMETRIC_FOGGING"), DoesPlatformSupportVolumetricFog(Parameters.Platform));
        OutEnvironment.SetDefine(TEXT("ENABLE_SKY_LIGHT"), bEnableSkyLight);
        OutEnvironment.SetDefine(TEXT("PLATFORM_FORCE_SIMPLE_SKY_DIFFUSE"), ForceSimpleSkyDiffuse(Parameters.Platform));

        TBasePassPixelShaderBaseType<LightMapPolicyType>::ModifyCompilationEnvironment(Parameters, OutEnvironment);
    }
};

TBasePassVS類似,TBasePassPS提供了獲取Shader系結、更改編譯環境和只編譯指定的排列組合shader等介面,此外還擁有反射球、光照圖型別等屬性,下面是它的獲取shader系結的代碼(由于和VS太過雷同,就不給出注釋了):

// Engine\Source\Runtime\Renderer\Private\BasePassRendering.inl

template<typename LightMapPolicyType>
void TBasePassPixelShaderPolicyParamType<LightMapPolicyType>::GetShaderBindings(
    const FScene* Scene,
    ERHIFeatureLevel::Type FeatureLevel,
    const FPrimitiveSceneProxy* PrimitiveSceneProxy,
    const FMaterialRenderProxy& MaterialRenderProxy,
    const FMaterial& Material,
    const FMeshPassProcessorRenderState& DrawRenderState,
    const TBasePassShaderElementData<LightMapPolicyType>& ShaderElementData,
    FMeshDrawSingleShaderBindings& ShaderBindings) const
{
    FMeshMaterialShader::GetShaderBindings(Scene, FeatureLevel, PrimitiveSceneProxy, MaterialRenderProxy, Material, DrawRenderState, ShaderElementData, ShaderBindings);

    if (Scene)
    {
        FRHIUniformBuffer* ReflectionCaptureUniformBuffer = Scene->UniformBuffers.ReflectionCaptureUniformBuffer.GetReference();
        ShaderBindings.Add(ReflectionCaptureBuffer, ReflectionCaptureUniformBuffer);
    }
    else
    {
        ShaderBindings.Add(ReflectionCaptureBuffer, DrawRenderState.GetReflectionCaptureUniformBuffer());
    }

    LightMapPolicyType::GetPixelShaderBindings(
        PrimitiveSceneProxy,
        ShaderElementData.LightMapPolicyElementData,
        this,
        ShaderBindings);
}

分析完BasePass的VS和PS,將注意力轉移到FDeferredShadingSceneRenderer::RenderBasePass,繼續分析BasePass其它的渲染狀態,由于上一篇的章節4.3.6 BasePass已經分析過RenderState和材質,這里就直接給出默認情況下的結果:

  • BlendState:TStaticBlendStateWriteMask<CW_RGBA, CW_RGBA, CW_RGBA, CW_RGBA, CW_RGBA, CW_RGBA, CW_NONE>,開啟了RGBA混合,
  • DepthStencilState:TStaticDepthStencilState<true, CF_DepthNearOrEqual>,開啟了深度寫入和測驗,比較函式為NearOrEqual,
  • Material:MeshBatch.MaterialRenderProxy->GetMaterialWithFallback(...),使用的是FMeshBatch收集到的材質,也就是網格自身的材質,
  • UniformBuffer
    • FOpaqueBasePassUniformParameters,BasePass的專用統一緩沖區,
    • Scene->UniformBuffers,場景相關的統一緩沖區,

其它未說明的渲染狀態和默認狀態一致,

5.3.3 BasePass Shader

上一篇已經分析過BasePass的繪制嵌套邏輯,最外層到內依次是場景、視圖、網格:

foreach(scene in scenes)
{
    foreach(view in views)
    {
        foreach(mesh in meshes)
        {
            DrawMesh(...); // 每次呼叫渲染就執行一次BasePassVertexShader和BasePassPixelShader的代碼.
        }
    }
}

意味著BasePassVertexShader和BasePassPixelShader被執行的次數是:

\[N_{scene} \cdot N_{view} \cdot N_{mesh} \]

這也側面說明了對FMeshDrawCommand進行排序的必要性,可以減少CPU和GPU的交換資料,減少渲染狀態切換,提升Cache命中率,提升實體化概率,降低Draw Call,不過,對于實時游戲而言,多數情況下,場景和視圖數量都是1,也就是VS和PS的執行次數只與網格數量有關,

后面兩個小節將進入BasePass的VS和PS的Shader邏輯進行剖析,

5.3.3.1 BasePassVertexShader

BasePassVertexShader的入口在BasePassVertexShader.usf:

#include "BasePassVertexCommon.ush"
#include "SHCommon.ush"

(......)

// Base Pass的主入口.
void Main(
    FVertexFactoryInput Input,
    OPTIONAL_VertexID
    out FBasePassVSOutput Output
#if USE_GLOBAL_CLIP_PLANE && !USING_TESSELLATION
    , out float OutGlobalClipPlaneDistance : SV_ClipDistance
#endif
#if INSTANCED_STEREO
    , uint InstanceId : SV_InstanceID
    #if !MULTI_VIEW
        , out float OutClipDistance : SV_ClipDistance1
    #else
        , out uint ViewportIndex : SV_ViewPortArrayIndex
    #endif
#endif
    )
{    
    (......)
    
    uint EyeIndex = 0;
    ResolvedView = ResolveView();

    // 獲取頂點的中間派生資料.
    FVertexFactoryIntermediates VFIntermediates = GetVertexFactoryIntermediates(Input);
    // 獲取世界空間的位置(不包含偏移).
    float4 WorldPositionExcludingWPO = VertexFactoryGetWorldPosition(Input, VFIntermediates);
    float4 WorldPosition = WorldPositionExcludingWPO;
    float4 ClipSpacePosition;

    // 區域空間的切線.
    float3x3 TangentToLocal = VertexFactoryGetTangentToLocal(Input, VFIntermediates);
    // 獲取材質的頂點相關引數.
    FMaterialVertexParameters VertexParameters = GetMaterialVertexParameters(Input, VFIntermediates, WorldPosition.xyz, TangentToLocal);

    // WorldPosition添加了材質偏移.
    {
        WorldPosition.xyz += GetMaterialWorldPositionOffset(VertexParameters);
    }

    (......)
    
    // 計算裁剪空間的位置.
    {
        float4 RasterizedWorldPosition = VertexFactoryGetRasterizedWorldPosition(Input, VFIntermediates, WorldPosition);
        ClipSpacePosition = INVARIANT(mul(RasterizedWorldPosition, ResolvedView.TranslatedWorldToClip));
        Output.Position = INVARIANT(ClipSpacePosition);
    }
    
    (......)
    
    // 全域裁剪*面距離.
#if USE_GLOBAL_CLIP_PLANE
    OutGlobalClipPlaneDistance = dot(ResolvedView.GlobalClippingPlane, float4(WorldPosition.xyz - ResolvedView.PreViewTranslation.xyz, 1));
#endif
    #if USE_WORLD_POSITION_EXCLUDING_SHADER_OFFSETS
        Output.BasePassInterpolants.PixelPositionExcludingWPO = WorldPositionExcludingWPO.xyz;
    #endif

    // 輸出需要插值的資料.
    Output.FactoryInterpolants = VertexFactoryGetInterpolants(Input, VFIntermediates, VertexParameters);

    (......)

    // 計算透明需要的霧顏色.
#if NEEDS_BASEPASS_VERTEX_FOGGING
    #if BASEPASS_ATMOSPHERIC_FOG
    Output.BasePassInterpolants.VertexFog = CalculateVertexAtmosphericFog(WorldPosition.xyz, ResolvedView.TranslatedWorldCameraOrigin);
    #else
    Output.BasePassInterpolants.VertexFog = CalculateHeightFog(WorldPosition.xyz - ResolvedView.TranslatedWorldCameraOrigin);
    #endif

    (......)
#endif

    // 透明物體的逐頂點光照.
#if TRANSLUCENCY_ANY_PERVERTEX_LIGHTING
    float3 WorldPositionForVertexLightingTranslated = VertexFactoryGetPositionForVertexLighting(Input, VFIntermediates, WorldPosition.xyz);
    float3 WorldPositionForVertexLighting = WorldPositionForVertexLightingTranslated - ResolvedView.PreViewTranslation.xyz;
#endif

    // 透明物體的兩種逐頂點光照: 逐頂點光照體積(TRANSLUCENCY_PERVERTEX_LIGHTING_VOLUME)和逐頂點前向著色(TRANSLUCENCY_PERVERTEX_FORWARD_SHADING).
#if TRANSLUCENCY_PERVERTEX_LIGHTING_VOLUME
    float4 VolumeLighting;
    float3 InterpolatedLighting = 0;

    float3 InnerVolumeUVs;
    float3 OuterVolumeUVs;
    float FinalLerpFactor;

    float3 LightingPositionOffset = 0;
    ComputeVolumeUVs(WorldPositionForVertexLighting, LightingPositionOffset, InnerVolumeUVs, OuterVolumeUVs, FinalLerpFactor);

    #if TRANSLUCENCY_LIGHTING_VOLUMETRIC_PERVERTEX_DIRECTIONAL
    
        Output.BasePassInterpolants.AmbientLightingVector = GetAmbientLightingVectorFromTranslucentLightingVolume(InnerVolumeUVs, OuterVolumeUVs, FinalLerpFactor).xyz;
        Output.BasePassInterpolants.DirectionalLightingVector = GetDirectionalLightingVectorFromTranslucentLightingVolume(InnerVolumeUVs, OuterVolumeUVs, FinalLerpFactor);

    #elif TRANSLUCENCY_LIGHTING_VOLUMETRIC_PERVERTEX_NONDIRECTIONAL

        Output.BasePassInterpolants.AmbientLightingVector = GetAmbientLightingVectorFromTranslucentLightingVolume(InnerVolumeUVs, OuterVolumeUVs, FinalLerpFactor).xyz;

    #endif
#elif TRANSLUCENCY_PERVERTEX_FORWARD_SHADING
    float4 VertexLightingClipSpacePosition = mul(float4(WorldPositionForVertexLightingTranslated, 1), ResolvedView.TranslatedWorldToClip);
    float2 SvPosition = (VertexLightingClipSpacePosition.xy / VertexLightingClipSpacePosition.w * float2(.5f, -.5f) + .5f) * ResolvedView.ViewSizeAndInvSize.xy;
    uint GridIndex = ComputeLightGridCellIndex((uint2)SvPosition, VertexLightingClipSpacePosition.w, EyeIndex);
    Output.BasePassInterpolants.VertexDiffuseLighting = GetForwardDirectLightingForVertexLighting(GridIndex, WorldPositionForVertexLighting, Output.Position.w, VertexParameters.TangentToWorld[2], EyeIndex);

#endif

    // 預計算輝照度體積光照
    #if PRECOMPUTED_IRRADIANCE_VOLUME_LIGHTING && TRANSLUCENCY_ANY_PERVERTEX_LIGHTING
        float3 BrickTextureUVs = ComputeVolumetricLightmapBrickTextureUVs(WorldPositionForVertexLighting);

        #if TRANSLUCENCY_LIGHTING_VOLUMETRIC_PERVERTEX_NONDIRECTIONAL
            FOneBandSHVectorRGB IrradianceSH = GetVolumetricLightmapSH1(BrickTextureUVs);
            Output.BasePassInterpolants.VertexIndirectAmbient = float3(IrradianceSH.R.V, IrradianceSH.G.V, IrradianceSH.B.V);
        #elif TRANSLUCENCY_LIGHTING_VOLUMETRIC_PERVERTEX_DIRECTIONAL
            // Need to interpolate directional lighting so we can incorporate a normal in the pixel shader
            FTwoBandSHVectorRGB IrradianceSH = GetVolumetricLightmapSH2(BrickTextureUVs);
            Output.BasePassInterpolants.VertexIndirectSH[0] = IrradianceSH.R.V;
            Output.BasePassInterpolants.VertexIndirectSH[1] = IrradianceSH.G.V;
            Output.BasePassInterpolants.VertexIndirectSH[2] = IrradianceSH.B.V;
        #endif
    #endif

    // 處理速度緩沖.
#if WRITES_VELOCITY_TO_GBUFFER
    {
        float4 PrevTranslatedWorldPosition = float4(0, 0, 0, 1);
        BRANCH
        if (GetPrimitiveData(VFIntermediates.PrimitiveId).OutputVelocity>0)
        {
            PrevTranslatedWorldPosition = VertexFactoryGetPreviousWorldPosition( Input, VFIntermediates );    
            VertexParameters = GetMaterialVertexParameters(Input, VFIntermediates, PrevTranslatedWorldPosition.xyz, TangentToLocal);
            PrevTranslatedWorldPosition.xyz += GetMaterialPreviousWorldPositionOffset(VertexParameters);
    
            #if !USING_TESSELLATION
                PrevTranslatedWorldPosition = mul(float4(PrevTranslatedWorldPosition.xyz, 1), ResolvedView.PrevTranslatedWorldToClip);
            #endif
        }

        (......)
        
        // compute the old screen pos with the old world position and the old camera matrix
        Output.BasePassInterpolants.VelocityPrevScreenPosition = PrevTranslatedWorldPosition; 
        // 保存速度螢屏空間的位置.
        #if WRITES_VELOCITY_TO_GBUFFER_USE_POS_INTERPOLATOR
        Output.BasePassInterpolants.VelocityScreenPosition = ClipSpacePosition;
        #endif
    }
#endif    // WRITES_VELOCITY_TO_GBUFFER

    OutputVertexID( Output );
}

Base Pass的頂點著色器雖然沒有計算動態光照,但也并沒有想象中的簡單:包含了處理中間派生資料、坐標轉換、裁剪*面、透明物體的霧效、透明物體的逐頂點光照,以及處理速度緩沖區等功能,

透明物體的逐頂點光照只作用于半透明物體,可在物體使用的材質屬性面板中指定:

下面將分析處理頂點中間派生資料GetVertexFactoryIntermediates、材質頂點引數GetMaterialVertexParameters、頂點插值資料VertexFactoryGetInterpolantsVSToPS等介面:

// Engine\Shaders\Private\LocalVertexFactory.ush

// 處理中間派生資料.
FVertexFactoryIntermediates GetVertexFactoryIntermediates(FVertexFactoryInput Input)
{
    FVertexFactoryIntermediates Intermediates;

    // 圖元ID(啟用GPU Scene才有效).
#if VF_USE_PRIMITIVE_SCENE_DATA
    Intermediates.PrimitiveId = Input.PrimitiveId;
#else
    Intermediates.PrimitiveId = 0;
#endif

    // 頂點顏色.
#if MANUAL_VERTEX_FETCH
    Intermediates.Color = LocalVF.VertexFetch_ColorComponentsBuffer[(LocalVF.VertexFetch_Parameters[VF_VertexOffset] + Input.VertexId) & LocalVF.VertexFetch_Parameters[VF_ColorIndexMask_Index]] FMANUALFETCH_COLOR_COMPONENT_SWIZZLE; // Swizzle vertex color.
#else
    Intermediates.Color = Input.Color FCOLOR_COMPONENT_SWIZZLE; // Swizzle vertex color.
#endif

    // 實體化資料.
#if USE_INSTANCING && MANUAL_VERTEX_FETCH && !USE_INSTANCING_BONEMAP
    uint InstanceId = GetInstanceId(Input.InstanceId);
    Intermediates.InstanceTransform1 = InstanceVF.VertexFetch_InstanceTransformBuffer[3 * (InstanceId + InstanceOffset) + 0];
    Intermediates.InstanceTransform2 = InstanceVF.VertexFetch_InstanceTransformBuffer[3 * (InstanceId + InstanceOffset) + 1];
    Intermediates.InstanceTransform3 = InstanceVF.VertexFetch_InstanceTransformBuffer[3 * (InstanceId + InstanceOffset) + 2];
    Intermediates.InstanceOrigin = InstanceVF.VertexFetch_InstanceOriginBuffer[(InstanceId + InstanceOffset)];
    Intermediates.InstanceLightmapAndShadowMapUVBias = InstanceVF.VertexFetch_InstanceLightmapBuffer[(InstanceId + InstanceOffset)];
#elif MANUAL_VERTEX_FETCH && USE_INSTANCING_BONEMAP
    uint InstanceIndex = VertexFetch_InstanceBoneMapBuffer[LocalVF.VertexFetch_Parameters[VF_VertexOffset] + Input.VertexId];
    Intermediates.InstanceTransform1 = VertexFetch_InstanceTransformBuffer[4 * InstanceIndex + 0];
    Intermediates.InstanceTransform2 = VertexFetch_InstanceTransformBuffer[4 * InstanceIndex + 1];
    Intermediates.InstanceTransform3 = VertexFetch_InstanceTransformBuffer[4 * InstanceIndex + 2];
    Intermediates.InstanceOrigin = VertexFetch_InstanceTransformBuffer[4 * InstanceIndex + 3];    

    Intermediates.InstancePrevTransform1 = VertexFetch_InstancePrevTransformBuffer[4 * InstanceIndex + 0];
    Intermediates.InstancePrevTransform2 = VertexFetch_InstancePrevTransformBuffer[4 * InstanceIndex + 1];
    Intermediates.InstancePrevTransform3 = VertexFetch_InstancePrevTransformBuffer[4 * InstanceIndex + 2];
    Intermediates.InstancePrevOrigin = VertexFetch_InstancePrevTransformBuffer[4 * InstanceIndex + 3];    

    Intermediates.InstanceLightmapAndShadowMapUVBias = float4(0,0,0,0);
#elif USE_INSTANCING
    Intermediates.InstanceTransform1 = Input.InstanceTransform1;
    Intermediates.InstanceTransform2 = Input.InstanceTransform2;
    Intermediates.InstanceTransform3 = Input.InstanceTransform3;
    Intermediates.InstanceOrigin = Input.InstanceOrigin;
    Intermediates.InstanceLightmapAndShadowMapUVBias = Input.InstanceLightmapAndShadowMapUVBias;
#endif

    // 切線及切線變換矩陣.
    float TangentSign;
    Intermediates.TangentToLocal = CalcTangentToLocal(Input, TangentSign);
    Intermediates.TangentToWorld = CalcTangentToWorld(Intermediates,Intermediates.TangentToLocal);
    Intermediates.TangentToWorldSign = TangentSign * GetPrimitiveData(Intermediates.PrimitiveId).InvNonUniformScaleAndDeterminantSign.w;

    // 實體化資料.
#if USE_INSTANCING && !USE_INSTANCING_BONEMAP
    // x = per-instance random
    // y = per-instance fade out factor
    // z = zero or one depending of if it is shown at all
    // w is dither cutoff 
    // PerInstanceParams.z stores a hide/show flag for this instance
    float SelectedValue = https://www.cnblogs.com/timlly/p/GetInstanceSelected(Intermediates);
    Intermediates.PerInstanceParams.x = GetInstanceRandom(Intermediates);
    float3 InstanceLocation = TransformLocalToWorld(GetInstanceOrigin(Intermediates), Intermediates.PrimitiveId).xyz;
    Intermediates.PerInstanceParams.y = 1.0 - saturate((length(InstanceLocation + ResolvedView.PreViewTranslation.xyz) - InstancingFadeOutParams.x) * InstancingFadeOutParams.y);
    // InstancingFadeOutParams.z,w are RenderSelected and RenderDeselected respectively.
    Intermediates.PerInstanceParams.z = InstancingFadeOutParams.z * SelectedValue + InstancingFadeOutParams.w * (1-SelectedValue);
    #if USE_DITHERED_LOD_TRANSITION
        float RandomLOD = InstancingViewZCompareZero.w * Intermediates.PerInstanceParams.x;
        float ViewZZero = length(InstanceLocation - InstancingWorldViewOriginZero.xyz) + RandomLOD;
        float ViewZOne = length(InstanceLocation - InstancingWorldViewOriginOne.xyz) + RandomLOD;
        Intermediates.PerInstanceParams.w = 
            dot(float3(ViewZZero.xxx > InstancingViewZCompareZero.xyz), InstancingViewZConstant.xyz) * InstancingWorldViewOriginZero.w +
            dot(float3(ViewZOne.xxx > InstancingViewZCompareOne.xyz), InstancingViewZConstant.xyz) * InstancingWorldViewOriginOne.w;
        Intermediates.PerInstanceParams.z *= abs(Intermediates.PerInstanceParams.w) < .999;
    #else
        Intermediates.PerInstanceParams.w = 0;
    #endif
#elif USE_INSTANCING && USE_INSTANCING_BONEMAP    
    Intermediates.PerInstanceParams.x = 0;    
    Intermediates.PerInstanceParams.y = 1;
    Intermediates.PerInstanceParams.z = 1;
    Intermediates.PerInstanceParams.w = 0;
#endif    // USE_INSTANCING

    // GPU蒙皮資料.
#if GPUSKIN_PASS_THROUGH
    uint PreSkinVertexOffset = LocalVF.PreSkinBaseVertexIndex + Input.VertexId * 3;
    Intermediates.PreSkinPosition.x = LocalVF.VertexFetch_PreSkinPositionBuffer[PreSkinVertexOffset + 0];
    Intermediates.PreSkinPosition.y = LocalVF.VertexFetch_PreSkinPositionBuffer[PreSkinVertexOffset + 1];
    Intermediates.PreSkinPosition.z = LocalVF.VertexFetch_PreSkinPositionBuffer[PreSkinVertexOffset + 2];
#else
    Intermediates.PreSkinPosition = Input.Position.xyz;
#endif

    return Intermediates;
}

// 獲取材質的頂點引數.
FMaterialVertexParameters GetMaterialVertexParameters(FVertexFactoryInput Input, FVertexFactoryIntermediates Intermediates, float3 WorldPosition, half3x3 TangentToLocal)
{
    FMaterialVertexParameters Result = (FMaterialVertexParameters)0;
    Result.WorldPosition = WorldPosition;
    Result.VertexColor = Intermediates.Color;

    // does not handle instancing!
    Result.TangentToWorld = Intermediates.TangentToWorld;

    // 實體化引數.
#if USE_INSTANCING
    Result.InstanceLocalToWorld = mul(GetInstanceTransform(Intermediates), GetPrimitiveData(Intermediates.PrimitiveId).LocalToWorld);
    Result.InstanceLocalPosition = Input.Position.xyz;
    Result.PerInstanceParams = Intermediates.PerInstanceParams;

    Result.InstanceId = GetInstanceId(Input.InstanceId); 
    
    #if USE_INSTANCING_BONEMAP
        Result.PrevFrameLocalToWorld = mul(GetInstancePrevTransform(Intermediates), GetPrimitiveData(Intermediates.PrimitiveId).PreviousLocalToWorld);
    #else
        Result.PrevFrameLocalToWorld = mul(GetInstanceTransform(Intermediates), GetPrimitiveData(Intermediates.PrimitiveId).PreviousLocalToWorld);
    #endif // USE_INSTANCING_BONEMAP
#else
    Result.PrevFrameLocalToWorld = GetPrimitiveData(Intermediates.PrimitiveId).PreviousLocalToWorld;
#endif    // USE_INSTANCING

    // 上一幀的蒙皮資料和法線.
    Result.PreSkinnedPosition = Intermediates.PreSkinPosition.xyz;
    Result.PreSkinnedNormal = TangentToLocal[2]; //TangentBias(Input.TangentZ.xyz);

    // 處理紋理坐標資料.
#if MANUAL_VERTEX_FETCH && NUM_MATERIAL_TEXCOORDS_VERTEX
        const uint NumFetchTexCoords = LocalVF.VertexFetch_Parameters[VF_NumTexcoords_Index];
        UNROLL
        for (uint CoordinateIndex = 0; CoordinateIndex < NUM_MATERIAL_TEXCOORDS_VERTEX; CoordinateIndex++)
        {
            // Clamp coordinates to mesh's maximum as materials can request more than are available
            uint ClampedCoordinateIndex = min(CoordinateIndex, NumFetchTexCoords-1);
            Result.TexCoords[CoordinateIndex] = LocalVF.VertexFetch_TexCoordBuffer[NumFetchTexCoords * (LocalVF.VertexFetch_Parameters[VF_VertexOffset] + Input.VertexId) + ClampedCoordinateIndex];
        }
#elif NUM_MATERIAL_TEXCOORDS_VERTEX
        #if GPUSKIN_PASS_THROUGH
            UNROLL
            for (int CoordinateIndex = 0; CoordinateIndex < NUM_MATERIAL_TEXCOORDS_VERTEX; CoordinateIndex++)
            {
                Result.TexCoords[CoordinateIndex] = Input.TexCoords[CoordinateIndex].xy;
            }
        #else
            #if NUM_MATERIAL_TEXCOORDS_VERTEX > 1
                UNROLL
                for(int CoordinateIndex = 0; CoordinateIndex < NUM_MATERIAL_TEXCOORDS_VERTEX-1; CoordinateIndex+=2)
                {
                    Result.TexCoords[CoordinateIndex] = Input.PackedTexCoords4[CoordinateIndex/2].xy;
                    if( CoordinateIndex+1 < NUM_MATERIAL_TEXCOORDS_VERTEX )
                    {
                        Result.TexCoords[CoordinateIndex+1] = Input.PackedTexCoords4[CoordinateIndex/2].zw;
                    }
                }
            #endif    // NUM_MATERIAL_TEXCOORDS_VERTEX > 1
            #if NUM_MATERIAL_TEXCOORDS_VERTEX % 2 == 1
                Result.TexCoords[NUM_MATERIAL_TEXCOORDS_VERTEX-1] = Input.PackedTexCoords2;
            #endif    // NUM_MATERIAL_TEXCOORDS_VERTEX % 2 == 1
        #endif
#endif  //MANUAL_VERTEX_FETCH && NUM_MATERIAL_TEXCOORDS_VERTEX

    // 圖元id.
    Result.PrimitiveId = Intermediates.PrimitiveId;
    return Result;
}

// 獲取頂點插值資料.
FVertexFactoryInterpolantsVSToPS VertexFactoryGetInterpolantsVSToPS(FVertexFactoryInput Input, FVertexFactoryIntermediates Intermediates, FMaterialVertexParameters VertexParameters)
{
    FVertexFactoryInterpolantsVSToPS Interpolants;
    Interpolants = (FVertexFactoryInterpolantsVSToPS)0;

    // 處理紋理資料.
#if NUM_TEX_COORD_INTERPOLATORS
    float2 CustomizedUVs[NUM_TEX_COORD_INTERPOLATORS];
    GetMaterialCustomizedUVs(VertexParameters, CustomizedUVs);
    GetCustomInterpolators(VertexParameters, CustomizedUVs);
    
    UNROLL
    for (int CoordinateIndex = 0; CoordinateIndex < NUM_TEX_COORD_INTERPOLATORS; CoordinateIndex++)
    {
        SetUV(Interpolants, CoordinateIndex, CustomizedUVs[CoordinateIndex]);
    }

#elif NUM_MATERIAL_TEXCOORDS_VERTEX == 0 && USE_PARTICLE_SUBUVS
    #if MANUAL_VERTEX_FETCH
        SetUV(Interpolants, 0, LocalVF.VertexFetch_TexCoordBuffer[LocalVF.VertexFetch_Parameters[VF_NumTexcoords_Index] * (LocalVF.VertexFetch_Parameters[VF_VertexOffset] + Input.VertexId)]);
    #else
        SetUV(Interpolants, 0, Input.TexCoords[0]);
    #endif
#endif

    // 光照圖相關資料: 坐標,索引,偏移,陰影坐標等.
#if NEEDS_LIGHTMAP_COORDINATE
    float2 LightMapCoordinate = 0;
    float2 ShadowMapCoordinate = 0;
    #if MANUAL_VERTEX_FETCH
        float2 LightMapCoordinateInput = LocalVF.VertexFetch_TexCoordBuffer[LocalVF.VertexFetch_Parameters[VF_NumTexcoords_Index] * (LocalVF.VertexFetch_Parameters[VF_VertexOffset] + Input.VertexId) + LocalVF.VertexFetch_Parameters[FV_LightMapIndex_Index]];
    #else
        float2 LightMapCoordinateInput = Input.LightMapCoordinate;
    #endif

    uint LightmapDataIndex = 0;

#if VF_USE_PRIMITIVE_SCENE_DATA
    LightmapDataIndex = GetPrimitiveData(Intermediates.PrimitiveId).LightmapDataIndex + LocalVF.LODLightmapDataIndex;
#endif

    float4 LightMapCoordinateScaleBias = GetLightmapData(LightmapDataIndex).LightMapCoordinateScaleBias;

    #if USE_INSTANCING
        LightMapCoordinate = LightMapCoordinateInput * LightMapCoordinateScaleBias.xy + GetInstanceLightMapBias(Intermediates);
    #else
        LightMapCoordinate = LightMapCoordinateInput * LightMapCoordinateScaleBias.xy + LightMapCoordinateScaleBias.zw;
    #endif
    #if STATICLIGHTING_TEXTUREMASK
        float4 ShadowMapCoordinateScaleBias = GetLightmapData(LightmapDataIndex).ShadowMapCoordinateScaleBias;

        #if USE_INSTANCING
            ShadowMapCoordinate = LightMapCoordinateInput * ShadowMapCoordinateScaleBias.xy + GetInstanceShadowMapBias(Intermediates);
        #else
            ShadowMapCoordinate = LightMapCoordinateInput * ShadowMapCoordinateScaleBias.xy + ShadowMapCoordinateScaleBias.zw;
        #endif
    #endif    // STATICLIGHTING_TEXTUREMASK

    SetLightMapCoordinate(Interpolants, LightMapCoordinate, ShadowMapCoordinate);
    SetLightmapDataIndex(Interpolants, LightmapDataIndex);
#endif    // NEEDS_LIGHTMAP_COORDINATE

    SetTangents(Interpolants, Intermediates.TangentToWorld[0], Intermediates.TangentToWorld[2], Intermediates.TangentToWorldSign);
    SetColor(Interpolants, Intermediates.Color);
#if USE_INSTANCING
    Interpolants.PerInstanceParams = Intermediates.PerInstanceParams;
#endif

    // 圖元id.
    SetPrimitiveId(Interpolants, Intermediates.PrimitiveId);

    return Interpolants;
}

5.3.3.2 BasePassPixelShader

BasePassPixelShader的入口在BasePassPixelShader.usf:

#include "Common.ush"

// 封裝不同宏定義下的基礎變數,
#if MATERIALBLENDING_TRANSLUCENT || MATERIALBLENDING_ADDITIVE || MATERIALBLENDING_MODULATE
    #define SceneTexturesStruct TranslucentBasePass.SceneTextures 
    #define EyeAdaptationStruct TranslucentBasePass
    #define SceneColorCopyTexture    TranslucentBasePass.SceneColorCopyTexture
    #define PreIntegratedGF            TranslucentBasePass.PreIntegratedGFTexture
    #if SUPPORTS_INDEPENDENT_SAMPLERS
        #define PreIntegratedGFSampler    View.SharedBilinearClampedSampler
        #define SceneColorCopySampler    View.SharedBilinearClampedSampler
    #else
        #define PreIntegratedGFSampler    TranslucentBasePass.PreIntegratedGFSampler
        #define SceneColorCopySampler    TranslucentBasePass.SceneColorCopySampler
    #endif
#else
    #define EyeAdaptationStruct OpaqueBasePass
#endif

(......)

#include "SHCommon.ush"
#include "BasePassCommon.ush"
#include "BRDF.ush"
#include "DeferredShadingCommon.ush"

(......)

// 法線曲率轉成粗糙度.
float NormalCurvatureToRoughness(float3 WorldNormal)
{
    float3 dNdx = ddx(WorldNormal);
    float3 dNdy = ddy(WorldNormal);
    float x = dot(dNdx, dNdx);
    float y = dot(dNdy, dNdy);
    float CurvatureApprox = pow(max(x, y), View.NormalCurvatureToRoughnessScaleBias.z);
    return saturate(CurvatureApprox * View.NormalCurvatureToRoughnessScaleBias.x + View.NormalCurvatureToRoughnessScaleBias.y);
}

#if TRANSLUCENT_SELF_SHADOWING
    #include "ShadowProjectionCommon.ush"     
#endif

#include "ShadingModelsMaterial.ush"
#if MATERIAL_SHADINGMODEL_HAIR || SIMPLE_FORWARD_DIRECTIONAL_LIGHT || MATERIAL_SHADINGMODEL_SINGLELAYERWATER
#include "ShadingModels.ush"
#endif

(......)

// 體積和透明光照.
#if TRANSLUCENCY_LIGHTING_SURFACE_LIGHTINGVOLUME || TRANSLUCENCY_LIGHTING_SURFACE_FORWARDSHADING || FORWARD_SHADING || MATERIAL_SHADINGMODEL_SINGLELAYERWATER
#include "ForwardLightingCommon.ush"
#endif
#if !FORWARD_SHADING
void GetVolumeLightingNonDirectional(float4 AmbientLightingVector, ...);
void GetVolumeLightingDirectional(float4 AmbientLightingVector, ...);
float3 GetTranslucencyVolumeLighting(FMaterialPixelParameters MaterialParameters, ...);
#endif

// 天空光.
#if SIMPLE_FORWARD_SHADING || PLATFORM_FORCE_SIMPLE_SKY_DIFFUSE
    #define GetEffectiveSkySHDiffuse GetSkySHDiffuseSimple
#else
    #define GetEffectiveSkySHDiffuse GetSkySHDiffuse
#endif
void GetSkyLighting(FMaterialPixelParameters MaterialParameters, ...);

// 間接光照.
#if SUPPORTS_INDEPENDENT_SAMPLERS
    #define ILCSharedSampler1 View.SharedBilinearClampedSampler
    #define ILCSharedSampler2 View.SharedBilinearClampedSampler
#else
    #define ILCSharedSampler1 IndirectLightingCache.IndirectLightingCacheTextureSampler1
    #define ILCSharedSampler2 IndirectLightingCache.IndirectLightingCacheTextureSampler2
#endif
void GetPrecomputedIndirectLightingAndSkyLight(FMaterialPixelParameters MaterialParameters, ...);

// 簡單前向光.
#if SIMPLE_FORWARD_DIRECTIONAL_LIGHT || MATERIAL_SHADINGMODEL_SINGLELAYERWATER
float3 GetSimpleForwardLightingDirectionalLight(FGBufferData GBuffer, ...);
#endif

// 像素深度偏移.
void ApplyPixelDepthOffsetForBasePass(inout FMaterialPixelParameters MaterialParameters, ...);
// 模擬AO多次反彈.
float3 AOMultiBounce( float3 BaseColor, float AO );
float DotSpecularSG( float Roughness, float3 N, float3 V, FSphericalGaussian LightSG );
// 應用環境法線.
void ApplyBentNormal( in FMaterialPixelParameters MaterialParameters, in float Roughness, ... );

// BasePass像素著色器主入口.
void FPixelShaderInOut_MainPS(
    FVertexFactoryInterpolantsVSToPS Interpolants,
    FBasePassInterpolantsVSToPS BasePassInterpolants,
    in FPixelShaderIn In,
    inout FPixelShaderOut Out)
{
    // 初始化GBuffer和view等資料.
    const uint EyeIndex = 0;
    ResolvedView = ResolveView();

    float4 OutVelocity = 0;
    float4 OutGBufferD = 0;
    float4 OutGBufferE = 0;
    
    // 獲取像素的材質引數(此處的材質就是材質輯器編輯出來的材質).
    FMaterialPixelParameters MaterialParameters = GetMaterialPixelParameters(Interpolants, In.SvPosition);
    FPixelMaterialInputs PixelMaterialInputs;

    // 光照圖虛擬紋理.
    VTPageTableResult LightmapVTPageTableResult = (VTPageTableResult)0.0f;
#if LIGHTMAP_VT_ENABLED
    {
        float2 LightmapUV0, LightmapUV1;
        uint LightmapDataIndex;
        GetLightMapCoordinates(Interpolants, LightmapUV0, LightmapUV1, LightmapDataIndex);
        LightmapVTPageTableResult = LightmapGetVTSampleInfo(LightmapUV0, LightmapDataIndex, In.SvPosition.xy);
    }
#endif
    
    // 光照圖和AO.
#if HQ_TEXTURE_LIGHTMAP && USES_AO_MATERIAL_MASK && !MATERIAL_SHADINGMODEL_UNLIT
    {
        float2 LightmapUV0, LightmapUV1;
        uint LightmapDataIndex;
        GetLightMapCoordinates(Interpolants, LightmapUV0, LightmapUV1, LightmapDataIndex);
        // Must be computed before BaseColor, Normal, etc are evaluated
        MaterialParameters.AOMaterialMask = GetAOMaterialMask(LightmapVTPageTableResult, LightmapUV0 * float2(1, 2), LightmapDataIndex, In.SvPosition.xy);
    }
#endif

    // 材質額外的引數.
#if USE_WORLD_POSITION_EXCLUDING_SHADER_OFFSETS
    {
        float4 ScreenPosition = SvPositionToResolvedScreenPosition(In.SvPosition);
        float3 TranslatedWorldPosition = SvPositionToResolvedTranslatedWorld(In.SvPosition);
        // 計算材質額外的引數.
        CalcMaterialParametersEx(MaterialParameters, PixelMaterialInputs, In.SvPosition, ScreenPosition, In.bIsFrontFace, TranslatedWorldPosition, BasePassInterpolants.PixelPositionExcludingWPO);
    }
#else
    {
        float4 ScreenPosition = SvPositionToResolvedScreenPosition(In.SvPosition);
        float3 TranslatedWorldPosition = SvPositionToResolvedTranslatedWorld(In.SvPosition);
        // 計算材質額外的引數.
        CalcMaterialParametersEx(MaterialParameters, PixelMaterialInputs, In.SvPosition, ScreenPosition, In.bIsFrontFace, TranslatedWorldPosition, TranslatedWorldPosition);
    }
#endif

    // 像素深度偏移.
#if OUTPUT_PIXEL_DEPTH_OFFSET
    ApplyPixelDepthOffsetForBasePass(MaterialParameters, PixelMaterialInputs, BasePassInterpolants, Out.Depth);
#endif

    // 處理像素clip.
#if !EARLY_Z_PASS_ONLY_MATERIAL_MASKING
    if (!bEditorWeightedZBuffering)
    {
#if MATERIALBLENDING_MASKED_USING_COVERAGE
        Out.Coverage = DiscardMaterialWithPixelCoverage(MaterialParameters, PixelMaterialInputs);
#else
        GetMaterialCoverageAndClipping(MaterialParameters, PixelMaterialInputs);
#endif
    }
#endif
    
    // 獲取材質的基礎屬性.
    half3 BaseColor = GetMaterialBaseColor(PixelMaterialInputs);
    half  Metallic = GetMaterialMetallic(PixelMaterialInputs);
    half  Specular = GetMaterialSpecular(PixelMaterialInputs);

    float MaterialAO = GetMaterialAmbientOcclusion(PixelMaterialInputs);
    float Roughness = GetMaterialRoughness(PixelMaterialInputs);
    float Anisotropy = GetMaterialAnisotropy(PixelMaterialInputs);
    uint ShadingModel = GetMaterialShadingModel(PixelMaterialInputs);

    half Opacity = GetMaterialOpacity(PixelMaterialInputs);

    // 0..1, SubsurfaceProfileId = int(x * 255)
    float SubsurfaceProfile = 0;

    float3 SubsurfaceColor = 0;
    
    // 次表面散射.
#if MATERIAL_SHADINGMODEL_SUBSURFACE || MATERIAL_SHADINGMODEL_PREINTEGRATED_SKIN || MATERIAL_SHADINGMODEL_SUBSURFACE_PROFILE || MATERIAL_SHADINGMODEL_TWOSIDED_FOLIAGE || MATERIAL_SHADINGMODEL_CLOTH || MATERIAL_SHADINGMODEL_EYE
    if (ShadingModel == SHADINGMODELID_SUBSURFACE || ShadingModel == SHADINGMODELID_PREINTEGRATED_SKIN || ShadingModel == SHADINGMODELID_SUBSURFACE_PROFILE || ShadingModel == SHADINGMODELID_TWOSIDED_FOLIAGE || ShadingModel == SHADINGMODELID_CLOTH || ShadingModel == SHADINGMODELID_EYE)
    {
        float4 SubsurfaceData = https://www.cnblogs.com/timlly/p/GetMaterialSubsurfaceData(PixelMaterialInputs);

        if (false) // Dummy if to make the ifdef logic play nicely
        {
        }
#if MATERIAL_SHADINGMODEL_SUBSURFACE || MATERIAL_SHADINGMODEL_PREINTEGRATED_SKIN || MATERIAL_SHADINGMODEL_TWOSIDED_FOLIAGE
        else if (ShadingModel == SHADINGMODELID_SUBSURFACE || ShadingModel == SHADINGMODELID_PREINTEGRATED_SKIN || ShadingModel == SHADINGMODELID_TWOSIDED_FOLIAGE)
        {
            SubsurfaceColor = SubsurfaceData.rgb * View.DiffuseOverrideParameter.w + View.DiffuseOverrideParameter.xyz;
        }
#endif
#if MATERIAL_SHADINGMODEL_CLOTH
        else if (ShadingModel == SHADINGMODELID_CLOTH)
        {
            SubsurfaceColor = SubsurfaceData.rgb;
        }
#endif

        SubsurfaceProfile = SubsurfaceData.a;
    }
#endif

    // 貼花資料.
    float DBufferOpacity = 1.0f;
#if USE_DBUFFER && MATERIALDECALRESPONSEMASK && !MATERIALBLENDING_ANY_TRANSLUCENT && !MATERIAL_SHADINGMODEL_SINGLELAYERWATER
    (......)
#endif

    const float BaseMaterialCoverageOverWater = Opacity;
    const float WaterVisibility = 1.0 - BaseMaterialCoverageOverWater;

    // 體積光照圖.
    float3 VolumetricLightmapBrickTextureUVs;
#if PRECOMPUTED_IRRADIANCE_VOLUME_LIGHTING
    VolumetricLightmapBrickTextureUVs = ComputeVolumetricLightmapBrickTextureUVs(MaterialParameters.AbsoluteWorldPosition);
#endif

    // 收集GBuffer資料.
    FGBufferData GBuffer = (FGBufferData)0;

    GBuffer.GBufferAO = MaterialAO;
    GBuffer.PerObjectGBufferData = GetPrimitiveData(MaterialParameters.PrimitiveId).PerObjectGBufferData;
    GBuffer.Depth = MaterialParameters.ScreenPosition.w;
    GBuffer.PrecomputedShadowFactors = GetPrecomputedShadowMasks(LightmapVTPageTableResult, Interpolants, MaterialParameters.PrimitiveId, MaterialParameters.AbsoluteWorldPosition, VolumetricLightmapBrickTextureUVs);

    const float GBufferDither = InterleavedGradientNoise(MaterialParameters.SvPosition.xy, View.StateFrameIndexMod8);
    // 設定前面收集到的引數到GBuffer中.
    SetGBufferForShadingModel(GBuffer,  MaterialParameters, Opacity,BaseColor, Metallic, Specular, Roughness, Anisotropy, SubsurfaceColor, SubsurfaceProfile, GBufferDither, ShadingModel);

#if USES_GBUFFER
    GBuffer.SelectiveOutputMask = GetSelectiveOutputMask();
    GBuffer.Velocity = 0;
#endif

    // 速度緩沖.
#if WRITES_VELOCITY_TO_GBUFFER
    BRANCH
    if (GetPrimitiveData(MaterialParameters.PrimitiveId).OutputVelocity > 0 || View.ForceDrawAllVelocities != 0)
    {
    #if WRITES_VELOCITY_TO_GBUFFER_USE_POS_INTERPOLATOR
        float3 Velocity = Calculate3DVelocity(BasePassInterpolants.VelocityScreenPosition, BasePassInterpolants.VelocityPrevScreenPosition);
    #else
        float3 Velocity = Calculate3DVelocity(MaterialParameters.ScreenPosition, BasePassInterpolants.VelocityPrevScreenPosition);
    #endif

        float4 EncodedVelocity = EncodeVelocityToTexture(Velocity);

        FLATTEN
        if (GetPrimitiveData(MaterialParameters.PrimitiveId).DrawsVelocity == 0.0 && View.ForceDrawAllVelocities == 0)
        {
            EncodedVelocity = 0.0;
        }

    #if USES_GBUFFER
        GBuffer.Velocity = EncodedVelocity;
    #else
        OutVelocity = EncodedVelocity;
    #endif
    }
#endif
    
    // 高光顏色.
    GBuffer.SpecularColor = ComputeF0(Specular, BaseColor, Metallic);

    // 法線曲率轉成粗糙度.
#if MATERIAL_NORMAL_CURVATURE_TO_ROUGHNESS
#if USE_WORLDVERTEXNORMAL_CENTER_INTERPOLATION
    float GeometricAARoughness = NormalCurvatureToRoughness(MaterialParameters.WorldVertexNormal_Center);
#else
    float GeometricAARoughness = NormalCurvatureToRoughness(MaterialParameters.TangentToWorld[2].xyz);
#endif
    GBuffer.Roughness = max(GBuffer.Roughness, GeometricAARoughness);

#if MATERIAL_SHADINGMODEL_CLEAR_COAT
    if (GBuffer.ShadingModelID == SHADINGMODELID_CLEAR_COAT)
    {
        GBuffer.CustomData.y = max(GBuffer.CustomData.y, GeometricAARoughness);
    }    
#endif
#endif

    // 后處理次表面散射.
#if POST_PROCESS_SUBSURFACE
    // SubsurfaceProfile applies the BaseColor in a later pass. Any lighting output in the base pass needs
    // to separate specular and diffuse lighting in a checkerboard pattern
    bool bChecker = CheckerFromPixelPos(MaterialParameters.SvPosition.xy);
    if (UseSubsurfaceProfile(GBuffer.ShadingModelID))
    {
        AdjustBaseColorAndSpecularColorForSubsurfaceProfileLighting(BaseColor, GBuffer.SpecularColor, Specular, bChecker);
    }
#endif
    
    // 漫反射顏色.
    GBuffer.DiffuseColor = BaseColor - BaseColor * Metallic;

    // 模擬環境BRDF*似的完全粗糙效果.
    #if !FORCE_FULLY_ROUGH
    if (View.RenderingReflectionCaptureMask)
    #endif
    {
        EnvBRDFApproxFullyRough(GBuffer.DiffuseColor, GBuffer.SpecularColor);
    }

    // 環境法線.
    float3 BentNormal = MaterialParameters.WorldNormal;
    // Clear Coat Bottom Normal
    BRANCH if( GBuffer.ShadingModelID == SHADINGMODELID_CLEAR_COAT && CLEAR_COAT_BOTTOM_NORMAL)
    {
        const float2 oct1 = ((float2(GBuffer.CustomData.a, GBuffer.CustomData.z) * 2) - (256.0/255.0)) + UnitVectorToOctahedron(GBuffer.WorldNormal);
        BentNormal = OctahedronToUnitVector(oct1);            
    }
    
    // 處理AO.
    float DiffOcclusion = MaterialAO;
    float SpecOcclusion = MaterialAO;
    // 應用環境法線.
    ApplyBentNormal( MaterialParameters, GBuffer.Roughness, BentNormal, DiffOcclusion, SpecOcclusion );
    // FIXME: ALLOW_STATIC_LIGHTING == 0 expects this to be AO
    GBuffer.GBufferAO = AOMultiBounce( Luminance( GBuffer.SpecularColor ), SpecOcclusion ).g;

    half3 DiffuseColor = 0;
    half3 Color = 0;
    float IndirectIrradiance = 0;

    half3 ColorSeparateSpecular = 0;
    half3 ColorSeparateEmissive = 0;

    // 非Unlit光照模型, 則疊加次表面散射顏色到漫反射中.
    #if !MATERIAL_SHADINGMODEL_UNLIT
        float3 DiffuseDir = BentNormal;
        float3 DiffuseColorForIndirect = GBuffer.DiffuseColor;

        // 次表面散射和預計算皮膚著色模型.
        #if MATERIAL_SHADINGMODEL_SUBSURFACE || MATERIAL_SHADINGMODEL_PREINTEGRATED_SKIN
        if (GBuffer.ShadingModelID == SHADINGMODELID_SUBSURFACE || GBuffer.ShadingModelID == SHADINGMODELID_PREINTEGRATED_SKIN)
        {
            // Add subsurface energy to diffuse
            //@todo - better subsurface handling for these shading models with skylight and precomputed GI
            DiffuseColorForIndirect += SubsurfaceColor;
        }
        #endif

        // 布料著色模型.
        #if MATERIAL_SHADINGMODEL_CLOTH
        if (GBuffer.ShadingModelID == SHADINGMODELID_CLOTH)
        {
            DiffuseColorForIndirect += SubsurfaceColor * saturate(GetMaterialCustomData0(MaterialParameters));
        }
        #endif
        
        // 頭發著色模型.
        #if MATERIAL_SHADINGMODEL_HAIR
        if (GBuffer.ShadingModelID == SHADINGMODELID_HAIR)
        {
            FHairTransmittanceData TransmittanceData = InitHairTransmittanceData(true);
            float3 N = MaterialParameters.WorldNormal;
            float3 V = MaterialParameters.CameraVector;
            float3 L = normalize( V - N * dot(V,N) );
            DiffuseDir = L;
            DiffuseColorForIndirect = 2*PI * HairShading( GBuffer, L, V, N, 1, TransmittanceData, 0, 0.2, uint2(0,0));

            #if USE_HAIR_COMPLEX_TRANSMITTANCE
            GBuffer.CustomData.a = 1.f / 255.f;
            #endif
        }
        #endif

        // 計算預計算非直接光照和天空光.
        float3 DiffuseIndirectLighting;
        float3 SubsurfaceIndirectLighting;
        GetPrecomputedIndirectLightingAndSkyLight(MaterialParameters, Interpolants, BasePassInterpolants, LightmapVTPageTableResult, GBuffer, DiffuseDir, VolumetricLightmapBrickTextureUVs, DiffuseIndirectLighting, SubsurfaceIndirectLighting, IndirectIrradiance);
        
        // 非直接光照應用AO.
        float IndirectOcclusion = 1.0f;
        float2 NearestResolvedDepthScreenUV = 0;
        float DirectionalLightShadow = 1.0f;
    
        #if FORWARD_SHADING && (MATERIALBLENDING_SOLID || MATERIALBLENDING_MASKED)
            float2 NDC = MaterialParameters.ScreenPosition.xy / MaterialParameters.ScreenPosition.w;
            float2 ScreenUV = NDC * ResolvedView.ScreenPositionScaleBias.xy + ResolvedView.ScreenPositionScaleBias.wz;
            NearestResolvedDepthScreenUV = CalculateNearestResolvedDepthScreenUV(ScreenUV, MaterialParameters.ScreenPosition.w);

            IndirectOcclusion = GetIndirectOcclusion(NearestResolvedDepthScreenUV, GBuffer);
            DiffuseIndirectLighting *= IndirectOcclusion;
            SubsurfaceIndirectLighting *= IndirectOcclusion;
            IndirectIrradiance *= IndirectOcclusion;
        #endif

        // 最終漫反射.
        DiffuseColor += (DiffuseIndirectLighting * DiffuseColorForIndirect + SubsurfaceIndirectLighting * SubsurfaceColor) * AOMultiBounce( GBuffer.BaseColor, DiffOcclusion );

        // 前向渲染或透明物體光照.
        #if TRANSLUCENCY_PERVERTEX_FORWARD_SHADING
            Color += BasePassInterpolants.VertexDiffuseLighting * GBuffer.DiffuseColor;
        #elif FORWARD_SHADING || TRANSLUCENCY_LIGHTING_SURFACE_FORWARDSHADING || TRANSLUCENCY_LIGHTING_SURFACE_LIGHTINGVOLUME || MATERIAL_SHADINGMODEL_SINGLELAYERWATER
            uint GridIndex = 0;

            #if FEATURE_LEVEL >= FEATURE_LEVEL_SM5
                GridIndex = ComputeLightGridCellIndex((uint2)(MaterialParameters.SvPosition.xy * View.LightProbeSizeRatioAndInvSizeRatio.zw - ResolvedView.ViewRectMin.xy), MaterialParameters.SvPosition.w, EyeIndex);

                #if FORWARD_SHADING || TRANSLUCENCY_LIGHTING_SURFACE_FORWARDSHADING || MATERIAL_SHADINGMODEL_SINGLELAYERWATER
                    const float Dither = InterleavedGradientNoise(MaterialParameters.SvPosition.xy, View.StateFrameIndexMod8);
                    FDeferredLightingSplit ForwardDirectLighting = GetForwardDirectLightingSplit(GridIndex, MaterialParameters.AbsoluteWorldPosition, MaterialParameters.CameraVector, GBuffer, NearestResolvedDepthScreenUV, MaterialParameters.PrimitiveId, EyeIndex, Dither, DirectionalLightShadow);
                    
                    #if MATERIAL_SHADINGMODEL_THIN_TRANSLUCENT
                        DiffuseColor += ForwardDirectLighting.DiffuseLighting.rgb;
                        ColorSeparateSpecular += ForwardDirectLighting.SpecularLighting.rgb;
                    #else
                        Color += ForwardDirectLighting.DiffuseLighting.rgb;
                        Color += ForwardDirectLighting.SpecularLighting.rgb;
                    #endif
                #endif
            #endif
            (......)
        #endif

        // 簡單前向*行光光照.
        #if SIMPLE_FORWARD_DIRECTIONAL_LIGHT && !MATERIAL_SHADINGMODEL_SINGLELAYERWATER && !MATERIAL_SHADINGMODEL_THIN_TRANSLUCENT
            float3 DirectionalLighting = GetSimpleForwardLightingDirectionalLight(
                GBuffer,
                DiffuseColorForIndirect,
                GBuffer.SpecularColor,
                GBuffer.Roughness,
                MaterialParameters.WorldNormal, 
                MaterialParameters.CameraVector);
            
            #if STATICLIGHTING_SIGNEDDISTANCEFIELD
                DirectionalLighting *= GBuffer.PrecomputedShadowFactors.x;
            #elif PRECOMPUTED_IRRADIANCE_VOLUME_LIGHTING
                DirectionalLighting *= GetVolumetricLightmapDirectionalLightShadowing(VolumetricLightmapBrickTextureUVs);
            #elif CACHED_POINT_INDIRECT_LIGHTING
                DirectionalLighting *= IndirectLightingCache.DirectionalLightShadowing;
            #endif
            
            Color += DirectionalLighting;

        #endif
    #endif

    // 霧效果.
    #if NEEDS_BASEPASS_VERTEX_FOGGING
        float4 HeightFogging = BasePassInterpolants.VertexFog;
    #elif NEEDS_BASEPASS_PIXEL_FOGGING
        float4 HeightFogging = CalculateHeightFog(MaterialParameters.WorldPosition_CamRelative);
    #else
        float4 HeightFogging = float4(0,0,0,1);
    #endif

    float4 Fogging = HeightFogging;

    // 體積霧.
#if NEEDS_BASEPASS_PIXEL_VOLUMETRIC_FOGGING && COMPILE_BASEPASS_PIXEL_VOLUMETRIC_FOGGING
    if (FogStruct.ApplyVolumetricFog > 0) 
    {
        float3 VolumeUV = ComputeVolumeUV(MaterialParameters.AbsoluteWorldPosition, ResolvedView.WorldToClip);
        Fogging = CombineVolumetricFog(HeightFogging, VolumeUV, EyeIndex);
    }
#endif

    // 逐像素霧.
#if NEEDS_BASEPASS_PIXEL_FOGGING
    const float OneOverPreExposure = USE_PREEXPOSURE ? ResolvedView.OneOverPreExposure : 1.0f;
    float4 NDCPosition = mul(float4(MaterialParameters.AbsoluteWorldPosition.xyz, 1), ResolvedView.WorldToClip);
#endif

    // 天空大氣效果.
#if NEEDS_BASEPASS_PIXEL_FOGGING && PROJECT_SUPPORT_SKY_ATMOSPHERE && MATERIAL_IS_SKY==0
    if (ResolvedView.SkyAtmosphereApplyCameraAerialPerspectiveVolume > 0.0f)
    {
        Fogging = GetAerialPerspectiveLuminanceTransmittanceWithFogOver(
            ResolvedView.RealTimeReflectionCapture, ResolvedView.SkyAtmosphereCameraAerialPerspectiveVolumeSizeAndInvSize,
            NDCPosition, MaterialParameters.AbsoluteWorldPosition.xyz*CM_TO_SKY_UNIT, ResolvedView.WorldCameraOrigin.xyz*CM_TO_SKY_UNIT,
            View.CameraAerialPerspectiveVolume, View.CameraAerialPerspectiveVolumeSampler,
            ResolvedView.SkyAtmosphereCameraAerialPerspectiveVolumeDepthResolutionInv,
            ResolvedView.SkyAtmosphereCameraAerialPerspectiveVolumeDepthResolution,
            ResolvedView.SkyAtmosphereAerialPerspectiveStartDepthKm,
            ResolvedView.SkyAtmosphereCameraAerialPerspectiveVolumeDepthSliceLengthKm,
            ResolvedView.SkyAtmosphereCameraAerialPerspectiveVolumeDepthSliceLengthKmInv,
            OneOverPreExposure, Fogging);
    }
#endif

    // 云體霧.
#if NEEDS_BASEPASS_PIXEL_FOGGING && MATERIAL_ENABLE_TRANSLUCENCY_CLOUD_FOGGING
    if (TranslucentBasePass.ApplyVolumetricCloudOnTransparent > 0.0f)
    {
        Fogging = GetCloudLuminanceTransmittanceOverFog(
            NDCPosition, MaterialParameters.AbsoluteWorldPosition.xyz, ResolvedView.WorldCameraOrigin.xyz,
            TranslucentBasePass.VolumetricCloudColor, TranslucentBasePass.VolumetricCloudColorSampler,
            TranslucentBasePass.VolumetricCloudDepth, TranslucentBasePass.VolumetricCloudDepthSampler,
            OneOverPreExposure, Fogging);
    }
#endif

    // 透明物體的體積光照.
#if (MATERIAL_SHADINGMODEL_DEFAULT_LIT || MATERIAL_SHADINGMODEL_SUBSURFACE) && (MATERIALBLENDING_TRANSLUCENT || MATERIALBLENDING_ADDITIVE) && !SIMPLE_FORWARD_SHADING && !FORWARD_SHADING
    if (GBuffer.ShadingModelID == SHADINGMODELID_DEFAULT_LIT || GBuffer.ShadingModelID == SHADINGMODELID_SUBSURFACE)
    {
        Color += GetTranslucencyVolumeLighting(MaterialParameters, PixelMaterialInputs, BasePassInterpolants, GBuffer, IndirectIrradiance);
    }
#endif

    (......)
#endif

    // 如果是次表面散射或薄透明著色模型, 需要保持DiffuseColor分離狀態, 而不應該直接將漫反射直接疊加到最終顏色中.
#if !POST_PROCESS_SUBSURFACE && !MATERIAL_SHADINGMODEL_THIN_TRANSLUCENT
    Color += DiffuseColor;
#endif

    // 自發光.
#if !MATERIAL_SHADINGMODEL_THIN_TRANSLUCENT
    Color += Emissive;
#endif

(......)

    // THIN_TRANSLUCENT光照模型.
#if MATERIAL_SHADINGMODEL_THIN_TRANSLUCENT
    float3 DualBlendColorAdd = 0.0f;
    float3 DualBlendColorMul = 1.0f;

    {
        AccumulateThinTranslucentModel(DualBlendColorAdd, DualBlendColorMul, MaterialParameters, GBuffer, ...);

        Color = 0;
        Opacity = 1.0f;
    }
#endif

    // 保存GBuffer到MRT中, 保存的RT位置根據不同著色模型各有不同.
    #if MATERIAL_DOMAIN_POSTPROCESS
        #if MATERIAL_OUTPUT_OPACITY_AS_ALPHA
            Out.MRT[0] = half4(Color, Opacity);
        #else
            Out.MRT[0] = half4(Color, 0);
        #endif
        Out.MRT[0] = RETURN_COLOR(Out.MRT[0]);
    // MATERIAL_SHADINGMODEL_THIN_TRANSLUCENT must come first because it also has MATERIALBLENDING_TRANSLUCENT defined
    #elif MATERIAL_SHADINGMODEL_THIN_TRANSLUCENT
        float3 AdjustedDualBlendAdd = Fogging.rgb + Fogging.a * DualBlendColorAdd;
        float3 AdjustedDualBlendMul =               Fogging.a * DualBlendColorMul;

        #if THIN_TRANSLUCENT_USE_DUAL_BLEND
            // no RETURN_COLOR because these values are explicit multiplies and adds
            Out.MRT[0] = half4(AdjustedDualBlendAdd,0.0);
            Out.MRT[1] = half4(AdjustedDualBlendMul,1.0);
        #else
            // In the fallback case, we are blending with the mode 
            float AdjustedAlpha = saturate(1-dot(AdjustedDualBlendMul,float3(1.0f,1.0f,1.0f)/3.0f));
            Out.MRT[0] = half4(AdjustedDualBlendAdd,AdjustedAlpha);
            Out.MRT[0] = RETURN_COLOR(Out.MRT[0]);
        #endif
    #elif MATERIALBLENDING_ALPHAHOLDOUT
        // not implemented for holdout
        Out.MRT[0] = half4(Color * Fogging.a + Fogging.rgb * Opacity, Opacity);
        Out.MRT[0] = RETURN_COLOR(Out.MRT[0]);
    #elif MATERIALBLENDING_ALPHACOMPOSITE
        Out.MRT[0] = half4(Color * Fogging.a + Fogging.rgb * Opacity, Opacity);
        Out.MRT[0] = RETURN_COLOR(Out.MRT[0]);
    #elif MATERIALBLENDING_TRANSLUCENT
        Out.MRT[0] = half4(Color * Fogging.a + Fogging.rgb, Opacity);
        Out.MRT[0] = RETURN_COLOR(Out.MRT[0]);
    #elif MATERIALBLENDING_ADDITIVE
        Out.MRT[0] = half4(Color * Fogging.a * Opacity, 0.0f);
        Out.MRT[0] = RETURN_COLOR(Out.MRT[0]);
    #elif MATERIALBLENDING_MODULATE
        // RETURN_COLOR not needed with modulative blending
        half3 FoggedColor = lerp(float3(1, 1, 1), Color, Fogging.aaa * Fogging.aaa);
        Out.MRT[0] = half4(FoggedColor, Opacity);
    #else // 其它著色模型.
        {
            FLightAccumulator LightAccumulator = (FLightAccumulator)0;
            Color = Color * Fogging.a + Fogging.rgb;

#if POST_PROCESS_SUBSURFACE
            DiffuseColor = DiffuseColor * Fogging.a + Fogging.rgb;

            if (UseSubsurfaceProfile(GBuffer.ShadingModelID) && 
                View.bSubsurfacePostprocessEnabled > 0 && View.bCheckerboardSubsurfaceProfileRendering > 0 )
            {
                Color *= !bChecker;
            }
            LightAccumulator_Add(LightAccumulator, Color + DiffuseColor, DiffuseColor, 1.0f, UseSubsurfaceProfile(GBuffer.ShadingModelID));
#else
            LightAccumulator_Add(LightAccumulator, Color, 0, 1.0f, false);
#endif
            Out.MRT[0] = RETURN_COLOR(LightAccumulator_GetResult(LightAccumulator));

            #if !USES_GBUFFER
                Out.MRT[0].a = 0;
            #endif
        }
    #endif

    // 將GBuffer資料編碼到MRT中.
    #if USES_GBUFFER
        GBuffer.IndirectIrradiance = IndirectIrradiance;

        // -0.5 .. 0.5, could be optimzed as lower quality noise would be sufficient
        float QuantizationBias = PseudoRandom( MaterialParameters.SvPosition.xy ) - 0.5f;
        EncodeGBuffer(GBuffer, Out.MRT[1], Out.MRT[2], Out.MRT[3], OutGBufferD, OutGBufferE, OutVelocity, QuantizationBias);
    #endif 

    (......)

    // 速度快取及之后的GBuffer.
#if USES_GBUFFER
    #if GBUFFER_HAS_VELOCITY
        Out.MRT[4] = OutVelocity;
    #endif

    Out.MRT[GBUFFER_HAS_VELOCITY ? 5 : 4] = OutGBufferD;

    #if GBUFFER_HAS_PRECSHADOWFACTOR
        Out.MRT[GBUFFER_HAS_VELOCITY ? 6 : 5] = OutGBufferE;
    #endif
#else // 沒有GBuffer, 說明是前向渲染, 但依然可以將速度資料寫入到MRT[1]中.
    #if GBUFFER_HAS_VELOCITY
        Out.MRT[1] = OutVelocity;
    #endif
#endif

    // 處理預曝光.
#if !MATERIALBLENDING_MODULATE && USE_PREEXPOSURE
    #if MATERIAL_IS_SKY
        // Dynamic capture exposure is 1 as of today.
        const float ViewPreExposure = View.RealTimeReflectionCapture>0.0f ? View.RealTimeReflectionCapturePreExposure : View.PreExposure;
    #else
        const float ViewPreExposure = View.PreExposure;
    #endif

    #if MATERIAL_DOMAIN_POSTPROCESS || MATERIAL_SHADINGMODEL_THIN_TRANSLUCENT || MATERIALBLENDING_ALPHAHOLDOUT || MATERIALBLENDING_ALPHACOMPOSITE || MATERIALBLENDING_TRANSLUCENT || MATERIALBLENDING_ADDITIVE
        Out.MRT[0].rgb  *= ViewPreExposure;
    #else
        Out.MRT[0].rgba *= ViewPreExposure;
    #endif
#endif
    
    (......)
}

#if NUM_VIRTUALTEXTURE_SAMPLES || LIGHTMAP_VT_ENABLED
    #if COMPILER_SUPPORTS_DEPTHSTENCIL_EARLYTEST_LATEWRITE
        #define PIXELSHADER_EARLYDEPTHSTENCIL DEPTHSTENCIL_EARLYTEST_LATEWRITE
    #elif !OUTPUT_PIXEL_DEPTH_OFFSET
        #define PIXELSHADER_EARLYDEPTHSTENCIL EARLYDEPTHSTENCIL
    #endif
#endif

// MRT序號宏定義, 須與FSceneRenderTargets::GetGBufferRenderTargets()保持匹配.
#define PIXELSHADEROUTPUT_BASEPASS 1
#if USES_GBUFFER
#define PIXELSHADEROUTPUT_MRT0 (!SELECTIVE_BASEPASS_OUTPUTS || NEEDS_BASEPASS_VERTEX_FOGGING || USES_EMISSIVE_COLOR || ALLOW_STATIC_LIGHTING || MATERIAL_SHADINGMODEL_SINGLELAYERWATER)
#define PIXELSHADEROUTPUT_MRT1 ((!SELECTIVE_BASEPASS_OUTPUTS || !MATERIAL_SHADINGMODEL_UNLIT))
#define PIXELSHADEROUTPUT_MRT2 ((!SELECTIVE_BASEPASS_OUTPUTS || !MATERIAL_SHADINGMODEL_UNLIT))
#define PIXELSHADEROUTPUT_MRT3 ((!SELECTIVE_BASEPASS_OUTPUTS || !MATERIAL_SHADINGMODEL_UNLIT))
    #if GBUFFER_HAS_VELOCITY
        #define PIXELSHADEROUTPUT_MRT4 WRITES_VELOCITY_TO_GBUFFER
        #define PIXELSHADEROUTPUT_MRT5 (!SELECTIVE_BASEPASS_OUTPUTS || WRITES_CUSTOMDATA_TO_GBUFFER)
        #define PIXELSHADEROUTPUT_MRT6 (GBUFFER_HAS_PRECSHADOWFACTOR && (!SELECTIVE_BASEPASS_OUTPUTS || WRITES_PRECSHADOWFACTOR_TO_GBUFFER && !MATERIAL_SHADINGMODEL_UNLIT))
    #else //GBUFFER_HAS_VELOCITY
        #define PIXELSHADEROUTPUT_MRT4 (!SELECTIVE_BASEPASS_OUTPUTS || WRITES_CUSTOMDATA_TO_GBUFFER)
        #define PIXELSHADEROUTPUT_MRT5 (GBUFFER_HAS_PRECSHADOWFACTOR && (!SELECTIVE_BASEPASS_OUTPUTS || WRITES_PRECSHADOWFACTOR_TO_GBUFFER && !MATERIAL_SHADINGMODEL_UNLIT))
    #endif //GBUFFER_HAS_VELOCITY
#else //USES_GBUFFER
    #define PIXELSHADEROUTPUT_MRT0 1
    // we also need MRT for thin translucency due to dual blending if we are not on the fallback path
    #define PIXELSHADEROUTPUT_MRT1 (WRITES_VELOCITY_TO_GBUFFER || (MATERIAL_SHADINGMODEL_THIN_TRANSLUCENT && THIN_TRANSLUCENT_USE_DUAL_BLEND))
#endif //USES_GBUFFER
#define PIXELSHADEROUTPUT_A2C ((EDITOR_ALPHA2COVERAGE) != 0)
#define PIXELSHADEROUTPUT_COVERAGE (MATERIALBLENDING_MASKED_USING_COVERAGE && !EARLY_Z_PASS_ONLY_MATERIAL_MASKING)


// 下面檔案的代碼會呼叫上面的FPixelShaderInOut_MainPS介面和GBuffer對應MRT的輸出.
#include"PixelShaderOutputCommon.ush"

根據上面的代碼分析,總結BasePassPixelShader的主要步驟:

  • 初始化ResolvedView、GBuffer等資料,

  • GetMaterialPixelParameters:獲取材質的引數,從材質編輯器編輯的材質節點和插值資料而來,詳細資料由FMaterialPixelParameters定義:

    // Engine\Shaders\Private\MaterialTemplate.ush
    
    struct FMaterialPixelParameters
    {
    #if NUM_TEX_COORD_INTERPOLATORS
        float2 TexCoords[NUM_TEX_COORD_INTERPOLATORS];
    #endif
    
        half4 VertexColor;
        half3 WorldNormal;
        half3 WorldTangent;
        half3 ReflectionVector;
        half3 CameraVector;
        half3 LightVector;
        float4 SvPosition;
        float4 ScreenPosition;
        half UnMirrored;
        half TwoSidedSign;
        half3x3 TangentToWorld;
    
    #if USE_WORLDVERTEXNORMAL_CENTER_INTERPOLATION
        half3 WorldVertexNormal_Center;
    #endif
    
        float3 AbsoluteWorldPosition;
        float3 WorldPosition_CamRelative;
        float3 WorldPosition_NoOffsets;
        float3 WorldPosition_NoOffsets_CamRelative;
        half3 LightingPositionOffset;
    
        float AOMaterialMask;
    
    #if LIGHTMAP_UV_ACCESS
        float2    LightmapUVs;
    #endif
    
    #if USE_INSTANCING
        half4 PerInstanceParams;
    #endif
    
        uint PrimitiveId;
    
        (......)
    };
    
  • CalcMaterialParametersEx:根據FMaterialPixelParameters計算額外的材質引數,

  • 根據FMaterialPixelParameters的資料和從頂點插值而來的資料計算GBuffer資料,

  • 計算次表面散射、貼花、體積光照圖等資料,

  • SetGBufferForShadingModel:設定前面步驟收集到的引數到GBuffer,

  • 計算或處理速度緩沖、高光顏色、漫反射顏色、環境法線、AO,疊加次表面散射顏色到漫反射中,

  • 處理預計算的非直接光照和天空光、非直接AO,計算最終的漫反射項,

  • 處理前向渲染光斬訓*行光,處理透明物體光照,

  • 計算霧效果,包含高度霧、指數霧、體積霧、逐像素霧、云體霧,以及天空大氣效果,

  • 處理自發光,

  • EncodeGBuffer:將GBuffer基礎資料編碼到MRT中,

  • 將GBuffer的非基礎資料寫入到后面幾個RT,如速度緩沖、逐物體陰影等,

  • 處理預曝光,

雖然BasePass的像素著色器未計算動態光照,但不等于其沒有執行光照處理,主要是處理了靜態光、環境光、非直接光、霧效果、次表面散射和自發光等,

5.3.4 BasePass總結

經過上小節的分析,可以得出BasePass的VS和PS雖然沒有計算動態光照,但實際上也做了很多跟幾何物體相關的處理,由此產生的shader指令數相當可觀,如何提升BasePass的渲染效率是值得深入研究的課題,

下面將以ShadingModel為DefaultLit的材質作為研究物件,利用RenderDoc截幀,分析其使用的BasePassPixelShader,材質具體設定如下:

在截取出來的幀資料中選取FPixelShaderInOut_MainPS代碼片段如下:

void FPixelShaderInOut_MainPS(FVertexFactoryInterpolantsVSToPS Interpolants, FBasePassInterpolantsVSToPS BasePassInterpolants, in FPixelShaderIn In, inout FPixelShaderOut Out)
{
    const uint EyeIndex = 0;
    ResolvedView = ResolveView();

    float4 OutVelocity = 0;
    float4 OutGBufferD = 0;
    float4 OutGBufferE = 0;
    float4 OutGBufferF = 0;

    FMaterialPixelParameters MaterialParameters = GetMaterialPixelParameters(Interpolants, In.SvPosition);
    FPixelMaterialInputs PixelMaterialInputs;

    float  LightmapVTPageTableResult = ( float )0.0f;
    {
        float4 ScreenPosition = SvPositionToResolvedScreenPosition(In.SvPosition);
        float3 TranslatedWorldPosition = SvPositionToResolvedTranslatedWorld(In.SvPosition);
        CalcMaterialParametersEx(MaterialParameters, PixelMaterialInputs, In.SvPosition, ScreenPosition, In.bIsFrontFace, TranslatedWorldPosition, TranslatedWorldPosition);
    }

    const bool bEditorWeightedZBuffering = false;

    if (!bEditorWeightedZBuffering)
    {
        GetMaterialCoverageAndClipping(MaterialParameters, PixelMaterialInputs);
    }

    float3  BaseColor = GetMaterialBaseColor(PixelMaterialInputs);
    float  Metallic = GetMaterialMetallic(PixelMaterialInputs);
    float  Specular = GetMaterialSpecular(PixelMaterialInputs);
    float MaterialAO = GetMaterialAmbientOcclusion(PixelMaterialInputs);
    float Roughness = GetMaterialRoughness(PixelMaterialInputs);
    float Anisotropy = GetMaterialAnisotropy(PixelMaterialInputs);
    uint ShadingModel = GetMaterialShadingModel(PixelMaterialInputs);
    float  Opacity = GetMaterialOpacity(PixelMaterialInputs);
    float SubsurfaceProfile = 0;
    float3 SubsurfaceColor = 0;
    float DBufferOpacity = 1.0f;

    if (GetPrimitiveData(MaterialParameters.PrimitiveId).DecalReceiverMask > 0 && View_ShowDecalsMask > 0)
    {
        uint DBufferMask = 0x07;
        if (DBufferMask)
        {
            float2 NDC = MaterialParameters.ScreenPosition.xy / MaterialParameters.ScreenPosition.w;
            float2 ScreenUV = NDC * ResolvedView.ScreenPositionScaleBias.xy + ResolvedView.ScreenPositionScaleBias.wz;
            FDBufferData DBufferData = https://www.cnblogs.com/timlly/p/GetDBufferData(ScreenUV, DBufferMask);

            ApplyDBufferData(DBufferData, MaterialParameters.WorldNormal, SubsurfaceColor, Roughness, BaseColor, Metallic, Specular);
            DBufferOpacity = (DBufferData.ColorOpacity + DBufferData.NormalOpacity + DBufferData.RoughnessOpacity) * (1.0f / 3.0f);
        }
    }

    const float BaseMaterialCoverageOverWater = Opacity;
    const float WaterVisibility = 1.0 - BaseMaterialCoverageOverWater;

    float3 VolumetricLightmapBrickTextureUVs;
    VolumetricLightmapBrickTextureUVs = ComputeVolumetricLightmapBrickTextureUVs(MaterialParameters.AbsoluteWorldPosition);

    FGBufferData GBuffer = (FGBufferData)0;

    GBuffer.GBufferAO = MaterialAO;
    GBuffer.PerObjectGBufferData = GetPrimitiveData(MaterialParameters.PrimitiveId).PerObjectGBufferData;
    GBuffer.Depth = MaterialParameters.ScreenPosition.w;
    GBuffer.PrecomputedShadowFactors = GetPrecomputedShadowMasks(LightmapVTPageTableResult, Interpolants, MaterialParameters.PrimitiveId, MaterialParameters.AbsoluteWorldPosition, VolumetricLightmapBrickTextureUVs);

    const float GBufferDither = InterleavedGradientNoise(MaterialParameters.SvPosition.xy, View_StateFrameIndexMod8);

SetGBufferForShadingModel(GBuffer,MaterialParameters,Opacity,BaseColor,Metallic,Specular,Roughness,Anisotropy,SubsurfaceColor,SubsurfaceProfile,GBufferDither,ShadingModel);

    GBuffer.SelectiveOutputMask = GetSelectiveOutputMask();
    GBuffer.Velocity = 0;
    GBuffer.SpecularColor = ComputeF0(Specular, BaseColor, Metallic);
    GBuffer.DiffuseColor = BaseColor - BaseColor * Metallic;

    GBuffer.DiffuseColor = GBuffer.DiffuseColor * View_DiffuseOverrideParameter.w + View_DiffuseOverrideParameter.xyz;
    GBuffer.SpecularColor = GBuffer.SpecularColor * View_SpecularOverrideParameter.w + View_SpecularOverrideParameter.xyz;

    if (View_RenderingReflectionCaptureMask)
    {
        EnvBRDFApproxFullyRough(GBuffer.DiffuseColor, GBuffer.SpecularColor);
    }

    float3 BentNormal = MaterialParameters.WorldNormal;

    if( GBuffer.ShadingModelID ==  4  &&  0 )
    {
        const float2 oct1 = ((float2(GBuffer.CustomData.a, GBuffer.CustomData.z) * 2) - (256.0/255.0)) + UnitVectorToOctahedron(GBuffer.WorldNormal);
        BentNormal = OctahedronToUnitVector(oct1);
    }

    float DiffOcclusion = MaterialAO;
    float SpecOcclusion = MaterialAO;

    ApplyBentNormal( MaterialParameters, GBuffer.Roughness, BentNormal, DiffOcclusion, SpecOcclusion );

    GBuffer.GBufferAO = AOMultiBounce( Luminance( GBuffer.SpecularColor ), SpecOcclusion ).g;

    float3  DiffuseColor = 0;
    float3  Color = 0;
    float IndirectIrradiance = 0;

    float3  ColorSeparateSpecular = 0;
    float3  ColorSeparateEmissive = 0;

    float3 DiffuseDir = BentNormal;
    float3 DiffuseColorForIndirect = GBuffer.DiffuseColor;
    float3 DiffuseIndirectLighting;
    float3 SubsurfaceIndirectLighting;

    GetPrecomputedIndirectLightingAndSkyLight(MaterialParameters, Interpolants, BasePassInterpolants, LightmapVTPageTableResult, GBuffer, DiffuseDir, VolumetricLightmapBrickTextureUVs, DiffuseIndirectLighting, SubsurfaceIndirectLighting, IndirectIrradiance);

    float IndirectOcclusion = 1.0f;
    float2 NearestResolvedDepthScreenUV = 0;
    float DirectionalLightShadow = 1.0f;
    DiffuseColor += (DiffuseIndirectLighting * DiffuseColorForIndirect + SubsurfaceIndirectLighting * SubsurfaceColor) * AOMultiBounce( GBuffer.BaseColor, DiffOcclusion );
    float4 HeightFogging = float4(0,0,0,1);

    float4 Fogging = HeightFogging;
    float3 GBufferDiffuseColor = GBuffer.DiffuseColor;
    float3 GBufferSpecularColor = GBuffer.SpecularColor;

    EnvBRDFApproxFullyRough(GBufferDiffuseColor, GBufferSpecularColor);

    Color = lerp(Color, GBufferDiffuseColor, View_UnlitViewmodeMask);

    float3  Emissive = GetMaterialEmissive(PixelMaterialInputs);

    if (View_OutOfBoundsMask > 0)
    {
        if (any(abs(MaterialParameters.AbsoluteWorldPosition - GetPrimitiveData(MaterialParameters.PrimitiveId).ObjectWorldPositionAndRadius.xyz) > GetPrimitiveData(MaterialParameters.PrimitiveId).ObjectBounds + 1))
        {
            float Gradient = frac(dot(MaterialParameters.AbsoluteWorldPosition, float3(.577f, .577f, .577f)) / 500.0f);
            Emissive = lerp(float3(1,1,0), float3(0,1,1), Gradient.xxx > .5f);
            Opacity = 1;
        }
    }

    Color += DiffuseColor;
    Color += Emissive;

    {
        FLightAccumulator LightAccumulator = (FLightAccumulator)0;

        Color = Color * Fogging.a + Fogging.rgb;

        LightAccumulator_Add(LightAccumulator, Color, 0, 1.0f, false);

        Out.MRT[0] =  ( LightAccumulator_GetResult(LightAccumulator) ) ;
    }

    GBuffer.IndirectIrradiance = IndirectIrradiance;

    float QuantizationBias = PseudoRandom( MaterialParameters.SvPosition.xy ) - 0.5f;

    EncodeGBuffer(GBuffer, Out.MRT[1], Out.MRT[2], Out.MRT[3], OutGBufferD, OutGBufferE, OutGBufferF, OutVelocity, QuantizationBias);

    Out.MRT[ 0  ||  0  ? 5 : 4] = OutGBufferD;
    Out.MRT[ 0  ||  0  ? 6 : 5] = OutGBufferE;
    Out.MRT[0].rgba *= View_PreExposure;
}

這樣少了很多宏定義,是不是干凈清爽簡潔多了?由此可以很容易地抽取DefaultLit著色模型下最終顏色的輸出程序:

float3  Color = 0;

GetPrecomputedIndirectLightingAndSkyLight(MaterialParameters, Interpolants, BasePassInterpolants, LightmapVTPageTableResult, GBuffer, DiffuseDir, VolumetricLightmapBrickTextureUVs, DiffuseIndirectLighting, SubsurfaceIndirectLighting, IndirectIrradiance);

DiffuseColor += (DiffuseIndirectLighting * DiffuseColorForIndirect + SubsurfaceIndirectLighting * SubsurfaceColor) * AOMultiBounce( GBuffer.BaseColor, DiffOcclusion );

Color += DiffuseColor;
Color += Emissive;
Color = Color * Fogging.a + Fogging.rgb;

FLightAccumulator LightAccumulator = (FLightAccumulator)0;
LightAccumulator_Add(LightAccumulator, Color, 0, 1.0f, false);
Out.MRT[0] =  ( LightAccumulator_GetResult(LightAccumulator) ) ;

Out.MRT[0].rgba *= View_PreExposure;

若將其分解成圖例,則如下圖:

graph TD FinalOutColor(FinalColor) --> Mul1{*} Mul1 --> LightAccumulator(LightAccumulator) Mul1 --> View_PreExposure(View.PreExposure) LightAccumulator --> Color(Color) Color --> Mul2{*} Mul2 --> Fogging.a(Fogging.a) Mul2 --> Add1{+} Add1 --> Fogging.rgb(Fogging.rgb) Add1 --> DiffuseColor(DiffuseColor) Add1 --> Emissive(Emissive) DiffuseColor --> Mul3{*} Mul3 --> AOMultiBounce(AOMultiBounce) AOMultiBounce --> GBuffer.BaseColor(GBuffer.BaseColor) AOMultiBounce --> DiffOcclusion(DiffOcclusion) Mul3 --> Add2{+} Add2 --> Mul4{*} Add2 --> Mul5{*} Mul4 --> DiffuseIndirectLighting(DiffuseIndirectLighting) Mul4 --> DiffuseColorForIndirect(GBuffer.DiffuseColor) Mul5 --> SubsurfaceIndirectLighting(SubsurfaceIndirectLighting) Mul5 --> SubsurfaceColor(SubsurfaceColor)

因此,在默認著色模型(DefaultLit)下,BasePass的最終輸出顏色受GBuffer.BaseColor、GBuffer.DiffuseColor、預計算非直接光、天空光、次表面散射、AO、自發光、霧效、預曝光等因素共同影響,(啊!!原來BasePass OutColor是個海王,同時和那么多顏色小姐姐搞曖昧O_O)

 

5.4 UE的光源

5.4.1 光源概述

UE的內置光源有Directional Light(*行光)、Point Light(點光源)、Spot Light(聚光燈)、Rect Light(矩形光)以及Sky Light(天空光),(下圖)

以上光源都擁有變換(Transform)及移動性(Mobility)等基礎屬性:

移動性有3種模式:靜態(Static)、固定(Stationary)、可移動(Movable),

靜態(Static)光源只能作為預計算光源,離線生成Lightmap光照圖,不能在運行時改變它的屬性,性能消耗最低,但可調整性最差,

固定(Stationary)光源會離線烘焙陰影和非直接光到靜態幾何體中,故而運行時不能改變其陰影和間接光,也不能改變位置,其它屬性可以改變,性能消耗適中,可調整性適中,

可移動(Movable)光源是完全動態光照和陰影,在運行時可以改變其任意屬性,性能消耗最高,可調整性適也最高,

  • Directional Light(*行光)

模擬太陽、月亮、激光等用同一個方向照射場景全部物體的光源,

它有光照強度、顏色、色溫等屬性,以及級聯陰影(CSM)、光束(Light Shaft)、光照函式(Light Function)等屬性組,

  • Point Light(點光源)

模擬某個點向四面八方擴散的光源,比如燈泡,

它擁有除了基礎屬性之外,也支持光照函式的設定,

  • Spot Light(聚光燈)

聚光燈會發出椎體形狀的光照,模擬手電筒等光源,

它擁有內錐角、外錐角、衰減半徑等特殊屬性,也支持光照函式、IES等,

  • Rect Light(矩形光)

矩形光模擬帶有長方形區域的面光源,如LED、電視、顯示幕等,

它的屬性是點光源和聚光燈的混合體,擁有Source Width、Source Height、Barn Door Angle、Barn Door Length等特殊屬性,以及IES、光照函式屬性組,

  • Sky Light(天空光)

天空光正如其名,模擬場景(Level)中帶有太陽或陰天的光斬訓境,不僅可以用于戶外環境,也可以給室內環境帶來環境光效果,

模擬戶外帶有太陽和云朵的天空光,

它也有靜態、固定和可移動三種移動屬性,它們的含義和普通其它型別的光源比較相似,天空光具有Source Type(來源型別)的屬性:

Source Type支持兩種模式:

  • Capture Scene:從場景中捕捉Cubemap(立方體圖),這種模式又可以用根據移動性細分3種行為:

    • 對于靜態天空光,會在構建場景的光照時自動觸發場景步驟,

    • 對于固定或可移動天空光,會在組件被加載時捕捉場景,還有一種特殊情況,當實時捕捉()被開啟時(UE4.25尚未有此特性),捕捉場景介面可以在運行時被呼叫,

  • Specified Cubemap:可以使用已經存在的cubemap,而不是從場景捕捉,

5.4.2 光源演算法

5.4.2.1 BRDF概述

本節將結合Real Shading in Unreal Engine 4和Shader原始碼決議UE的光照理論、模型和演算法,對PBR光照理論和知識薄弱的同學建議看筆者的另一篇文章由淺入深學習PBR的原理和實作,

1982年,Cook-Torrance的Specular BRDF由Cook和Torrance兩人聯合提出,該模型考慮在同一景物中不同材料和不同光源的相對亮度,它描述反射光線在方向上的分布和當反射隨入射角而改變時顏色的變化,并能求得從具體的實際材料制成的物體反射出來的光線的光譜能量分布,并根據這種光譜能量分布精確地再現顏色,它的公式如下:

\[f_{cook-torrance} = \frac{D(h)\cdot F(l,h)\cdot G(l,v,h)}{4(n \cdot l)(n \cdot v)} \]

2012年,迪斯尼原則的BRDF由迪斯尼影片作業室的Brent Burly提出,用少量簡單易懂的引數和高度完善的美術作業流程,大大簡化了此前復雜的PBR的引數和制作流程,它是藝術導向的著色模型,而不完全是物理正確,

迪斯尼原則BRDF抽象出的各種材質引數,這些引數都被映射到0~1之間的數值,

迪斯尼原則的BRDF奠定了后續游戲行業和電影行業PBR的方向和標準,隨后各大商業引擎紛紛基于迪斯尼原則的BRDF將PBR渲染管線集成進自家引擎,下面是主流商業引擎支持迪斯尼原則的PBR時間表:

  • Unreal Engine 4:《Real Shading in Unreal Engine 4》,SIGGRAPH 2013
  • Unity 5:《Physically Based Shading in Unity》,GDC 2014
  • Frostbite(寒霜): 《Moving Frostbite to PBR》,SIGGRAPH 2014
  • Cry Engine 3.6:《Physically Based Shading in Cry Engine》,2015

2013年,反應迅速的以Brian Karis為首的UE團隊敢為人先,成為首批將迪斯尼原則的BRDF集成到了商業引擎的先驅,在集成程序中,UE做了取舍、簡化、改進、優化,優秀地將原本用于離線領域的迪斯尼BRDF集成進UE的實時渲染體系中,

UE對于Cook-Torrance的各個子項的選取上,做了多維度的對比,最終得出以下的模型選擇,

首先是D項(法線分布函式),相比Blinn-Phong模型,GGX的法線分布函式具有更長的拖尾,更加符合自然物理現象,而且相當高效,最終被UE選中:

GGX法線分布函式的公式:

\[D_{GGX}(n, h, \alpha) = \frac{\alpha^2}{\pi((n \cdot h)^2 (\alpha^2 - 1) + 1)^2} \]

UE對應的shader實作代碼:

float D_GGX( float a2, float NoH )
{
    float d = ( NoH * a2 - NoH ) * NoH + 1;    // 2 mad
    return a2 / ( PI*d*d );                    // 4 mul, 1 rcp
}

其次是F項(菲涅爾),使用的是Schlick*似法:

Schlick的菲涅爾*似法經典公式:

\[F_{Schlick}(h, v, F_0) = F_0 + (1 - F_0) ( 1 - (h \cdot v))^5 \]

UE對應的shader*似實作代碼:

float3 F_Schlick( float3 SpecularColor, float VoH )
{
    float Fc = Pow5( 1 - VoH );                    // 1 sub, 3 mul
    return saturate( 50.0 * SpecularColor.g ) * Fc + (1 - Fc) * SpecularColor;
}

不過,UE并沒有完全使用以上公式,而是為了效率采用球面高斯(Spherical Gaussian)*似法代替了Pow運算:

\[F(v, h) = F_0 + (1-F_0)2^{(-5.55473(v\cdot h)-6.98316)(v\cdot h)} \]

UE4.25對應的實作代碼并沒有完全按照上面的公式,似乎做了很多*似和優化,如下:

float3 F_Fresnel( float3 SpecularColor, float VoH )
{
    float3 SpecularColorSqrt = sqrt( clamp( float3(0, 0, 0), float3(0.99, 0.99, 0.99), SpecularColor ) );
    float3 n = ( 1 + SpecularColorSqrt ) / ( 1 - SpecularColorSqrt );
    float3 g = sqrt( n*n + VoH*VoH - 1 );
    return 0.5 * Square( (g - VoH) / (g + VoH) ) * ( 1 + Square( ((g+VoH)*VoH - 1) / ((g-VoH)*VoH + 1) ) );
}

最后是G項(幾何遮蔽),采用了Smith和Schlick聯合遮蔽函式:

它們的聯合公式如下:

\[\begin{eqnarray*} k = \cfrac{(\text{Roughness} + 1)^2}{8} \\ G_1(v) = \cfrac{n\cdot v}{(n\cdot v)(1-k) + k} \\ \\ G(l,v,h) = G_1(l)G_1(v) \end{eqnarray*} \]

對應的UE實作代碼:

float Vis_Schlick( float a2, float NoV, float NoL )
{
    float k = sqrt(a2) * 0.5;
    float Vis_SchlickV = NoV * (1 - k) + k;
    float Vis_SchlickL = NoL * (1 - k) + k;
    return 0.25 / ( Vis_SchlickV * Vis_SchlickL );
}

Cook-Torrance的各項光照函式除了上述方法,UE還提供了其它若干種*似方法,詳情見5.2.3.6 BRDF.ush小節,

UE的IBL將光斬訓分用黎曼和方法來*似模擬,光照函式結合了蒙特卡洛和重要性采樣,公式如下:

\[\int \limits_H L_i(l)f(l,v)\cos\theta_ldl \approx \frac{1}{N} \sum_{k=1}^N \cfrac{L_i(l_k)f(l_k,v)\cos\theta_{l_k}}{p(l_k,v)} \]

對應的shader實作代碼:

float4 ImportanceSampleGGX( float2 E, float a2 )
{
    float Phi = 2 * PI * E.x;
    float CosTheta = sqrt( (1 - E.y) / ( 1 + (a2 - 1) * E.y ) );
    float SinTheta = sqrt( 1 - CosTheta * CosTheta );

    float3 H;
    H.x = SinTheta * cos( Phi );
    H.y = SinTheta * sin( Phi );
    H.z = CosTheta;
    
    float d = ( CosTheta * a2 - CosTheta ) * CosTheta + 1;
    float D = a2 / ( PI*d*d );
    float PDF = D * CosTheta;

    return float4( H, PDF );
}

float3 SpecularIBL( uint2 Random, float3 SpecularColor, float Roughness, float3 N, float3 V )
{
    float3 SpecularLighting = 0;

    const uint NumSamples = 32;
    for( uint i = 0; i < NumSamples; i++ )
    {
        float2 E = Hammersley( i, NumSamples, Random );
        float3 H = TangentToWorld( ImportanceSampleGGX( E, Pow4(Roughness) ).xyz, N );
        float3 L = 2 * dot( V, H ) * H - V;

        float NoV = saturate( dot( N, V ) );
        float NoL = saturate( dot( N, L ) );
        float NoH = saturate( dot( N, H ) );
        float VoH = saturate( dot( V, H ) );
        
        if( NoL > 0 )
        {
            float3 SampleColor = AmbientCubemap.SampleLevel( AmbientCubemapSampler, L, 0 ).rgb;

            float Vis = Vis_SmithJointApprox( Pow4(Roughness), NoV, NoL );
            float Fc = pow( 1 - VoH, 5 );
            float3 F = (1 - Fc) * SpecularColor + Fc;

            // Incident light = SampleColor * NoL
            // Microfacet specular = D*G*F / (4*NoL*NoV) = D*Vis*F
            // pdf = D * NoH / (4 * VoH)
            SpecularLighting += SampleColor * F * ( NoL * Vis * (4 * VoH / NoH) );
        }
    }

    return SpecularLighting / NumSamples;
}

之后還會將漫反射項分離成兩項,這部分就不再闡述了,有興趣的童鞋可以參看由淺入深學習PBR的原理和實作,

下面將闡述UE的光照模型,對于光源的距離衰減函式,UE采用了如下的物理*似:

\[\text{Falloff} = \cfrac{\text{saturate}(1-(\text{distance}/\text{lightRadius})^4)^2}{\text{distance}^2+1} \]

對應的實作代碼:

// Engine\Shaders\Private\DeferredLightingCommon.ush

float GetLocalLightAttenuation(float3 WorldPosition, FDeferredLightData LightData, inout float3 ToLight, inout float3 L)
{
    ToLight = LightData.Position - WorldPosition;
        
    float DistanceSqr = dot( ToLight, ToLight );
    L = ToLight * rsqrt( DistanceSqr );

    float LightMask;
    if (LightData.bInverseSquared)
    {
        LightMask = Square( saturate( 1 - Square( DistanceSqr * Square(LightData.InvRadius) ) ) );
    }
    
    (......)

    return LightMask;
}

相比舊的距離衰減函式,新的更符合物理真實:擁有更合理的輻射范圍和更小對比度的亮度變化:

5.4.2.2 特殊光源

對于區域光(Area Light),UE力求達到一致的材質外觀表現,對于漫反射和高光反射的BRDF的能量計算盡量保持匹配,當立體角接*于0時,可使用點光源來*似區域光源,最后要保證足夠高效,以致可以被廣泛地使用,

UE對高光的D項進行了修改,基于立體角的更寬的高光分布,其中粗糙度的改變公式如下:

\[\alpha' = \text{saturate}\bigg(\alpha + \cfrac{\text{sourceRadius}}{2*\text{distance}} \bigg) \]

這也會導致新的問題:光滑的物體看起來不再光滑(下圖),

同時選取了區域光內的一個代表點(Representative Point)來模擬整片區域,可以直接用來光照著色,對于具有很大范圍分布的點是個很好的選擇,可以使用足夠小的角度*似反射光線,

對于球形光(Sphere Light),輻照度被當成點光源(前提是球體在反射光線的水*面上),然后用反射光線和球體求得最*點:

其中計算球體和反射光線的最小距離的公式如下:

\[\begin{eqnarray*} \text{CenterToRay} &=& (L\cdot r)r - L \\ \text{ClosetPoint} &=& L + \text{CenterToRay} \cdot \text{saturate} \bigg(\cfrac{\text{SourceRadius}}{|\text{CenterToRay}|}\bigg) \\ l &=& || \text{ClosetPoint} || \end{eqnarray*} \]

其中\(L\)是著色點指向球體光源中心的向量,\(\text{SourceRadius}\)是光源球體半徑,\(r\)\(L\)在著色點的反射向量,

接著可以按照以下公式算出著色點的能量積分:

\[\begin{eqnarray*} I_{point} &=& \cfrac{p+2}{2\pi}\cos^p\phi_r \\ \\ I_{sphere} &=& \begin{cases} \cfrac{p+2}{2\pi} & \quad \text{if } \phi_r<\phi_s\\ \cfrac{p+2}{2\pi}\cos^p(\phi_r-\phi_s) & \quad \text{if } \phi_r\ge\phi_s \end{cases} \end{eqnarray*} \]

其中\(\phi_r\)\(r\)\(L\)之間的角度,\(\phi_s\)是球體對角(subtended angle)的一半,\(I_{point}\)已歸一化,意味著它在半球的積分是1,\(I_{sphere}\)不再是歸一化的且依賴能量\(p\),積分可能變得非常大:

由于GGX的歸一化因子是\(\cfrac{1}{\pi \alpha^2}\),為了獲得代表點的*似歸一化,由下面的公式承擔:

\[\text{SphereNormalization} = \bigg( \cfrac{\alpha}{\alpha'} \bigg)^2 \]

由此獲得非常不錯的球形光照效果:

下面將闡述管狀光源(Tube Light, Capsule Light)的演算法,對于從著色點指向管狀光源兩端的向量\(L_0\)\(L_1\),它們之間線段的輻照度積分的決議解為:

\[\int_{L_0}^{L_1}\cfrac{n\cdot L}{|L|^3}dL = \frac{\cfrac{n\cdot L_0}{L_0} + \cfrac{n\cdot L_1}{L_1}} {|L_0||L_1| + (L_0 \cdot L_1)} \]

UE為此做了大量的簡化和*似,其中最主要的是類似于球體光源,*似最小的角度并用最短距離代替:

\[\begin{eqnarray*} L_d &=& L_1 - L_0 \\ t &=& \cfrac{(r\cdot L_0)(r\cdot L_d)-(L_0\cdot L_d)}{|L_d|^2-(r\cdot L_d)^2} \end{eqnarray*} \]

并且根據各項異性GGX的歸一化因子是\(\cfrac{1}{\pi\alpha_x \alpha_y}\)(假設\(\alpha_x = \alpha_y = \alpha\)),由此得到線性的歸一化公式:

\[\text{LineNormalizetion} = \cfrac{\alpha}{\alpha'} \]

由于只需改變光源原點和應用能量守恒項,這些操作可以被累積,因此管狀光源的*似卷積效果非常好:

5.4.3 光源分配和裁剪

5.4.3.1 光源基礎概念

UE的光源分配由FDeferredShadingSceneRenderer::Render內的bComputeLightGrid變數決定:

void FDeferredShadingSceneRenderer::Render(FRHICommandListImmediate& RHICmdList)
{
    (......)
    
    bool bComputeLightGrid = false;
    if (!IsSimpleForwardShadingEnabled(ShaderPlatform))
    {
        if (bUseGBuffer)
        {
            bComputeLightGrid = bRenderDeferredLighting;
        }
        else
        {
            bComputeLightGrid = ViewFamily.EngineShowFlags.Lighting;
        }

        bComputeLightGrid |= (
            ShouldRenderVolumetricFog() ||
            ViewFamily.ViewMode != VMI_Lit);
    }
    
    (......)
}

是否開啟光源分配任務由幾個因素共同影響:不是簡單前向著色,并且使用了GBuffer的延遲渲染或開啟了ViewFamily的光照計算或需要渲染體積霧等,接著呼叫下面的邏輯執行光源分配:

void FDeferredShadingSceneRenderer::Render(FRHICommandListImmediate& RHICmdList)
{
    (......)
    
    // 有序的光源集合.
    FSortedLightSetSceneInfo SortedLightSet;
    {
        // 收集和排序光源.
        GatherAndSortLights(SortedLightSet);
        // 計算光源格子(分配光源).
        ComputeLightGrid(RHICmdList, bComputeLightGrid, SortedLightSet);
    }
    
    (......)
}

FSortedLightSetSceneInfo是光源非常重要的基礎概念,后續的光源處理都會涉及到它,下面看看它和其它光源相關概念的定義:

// Engine\Source\Runtime\Engine\Public\PrimitiveSceneProxy.h

// 簡單動態光源的資料.
class FSimpleLightEntry
{
public:
    FVector Color;
    float Radius;
    float Exponent;
    float VolumetricScatteringIntensity;
    bool bAffectTranslucency;
};

// 簡單動態光源的和視圖相關的資料.
class FSimpleLightPerViewEntry
{
public:
    FVector Position;
};

// 指向簡單光源實體的view相關的資料. 
// 類名應該有誤, Instace應該是Instance.
class FSimpleLightInstacePerViewIndexData
{
public:
    uint32 PerViewIndex : 31; // 占31位
    uint32 bHasPerViewData;   // 占1位
};

// 簡單動態光源串列.
class FSimpleLightArray
{
public:
    // 簡單動態光源串列, 和視圖無關.
    TArray<FSimpleLightEntry, TMemStackAllocator<>> InstanceData;
    // 簡單動態光源串列, 和視圖有關.
    TArray<FSimpleLightPerViewEntry, TMemStackAllocator<>> PerViewData;
    // 指向簡單光源實體的view相關的資料串列. 
    TArray<FSimpleLightInstacePerViewIndexData, TMemStackAllocator<>> InstancePerViewDataIndices;

public:
    // 獲取簡單光源的視圖相關的資料.
    inline const FSimpleLightPerViewEntry& GetViewDependentData(int32 LightIndex, int32 ViewIndex, int32 NumViews) const
    {
        if (InstanceData.Num() == PerViewData.Num())
        {
            return PerViewData[LightIndex];
        }
        else 
        {
            // 計算視圖索引.
            FSimpleLightInstacePerViewIndexData PerViewIndex = InstancePerViewDataIndices[LightIndex];
            const int32 PerViewDataIndex = PerViewIndex.PerViewIndex + ( PerViewIndex.bHasPerViewData ? ViewIndex : 0 );
            return PerViewData[PerViewDataIndex];
        }
    }
};


// Engine\Source\Runtime\Renderer\Private\LightSceneInfo.h

// 用于排序的光源的資訊.
struct FSortedLightSceneInfo
{
    union
    {
        // 類似于FMeshDrawCommand的排序鍵值, 下列資料成員的順序控制著光源的排序重要性.
        struct
        {
            // 光源型別.
            uint32 LightType : LightType_NumBits;
            // 是否擁有紋理配置
            uint32 bTextureProfile : 1;
            // 是否擁有光照函式.
            uint32 bLightFunction : 1;
            // 是否計算陰影.
            uint32 bShadowed : 1;
            // 是否使用燈光通道.
            uint32 bUsesLightingChannels : 1;
            // 是否非簡單光源.
            uint32 bIsNotSimpleLight : 1;
            // 是否不支持延遲分塊渲染.
            uint32 bTiledDeferredNotSupported : 1;
            // 是否不支持延遲分簇渲染.
            uint32 bClusteredDeferredNotSupported : 1;
        } Fields;
        // 打包的排序鍵值.
        int32 Packed;
    } SortKey;

    // 對應的光源場景資訊.
    const FLightSceneInfo* LightSceneInfo;
    // 簡單光源索引.
    int32 SimpleLightIndex;

    // 非簡單光源建構式.
    explicit FSortedLightSceneInfo(const FLightSceneInfo* InLightSceneInfo)
        : LightSceneInfo(InLightSceneInfo),
        SimpleLightIndex(-1)
    {
        SortKey.Packed = 0;
        SortKey.Fields.bIsNotSimpleLight = 1;
    }

    // 簡單光源建構式.
    explicit FSortedLightSceneInfo(int32 InSimpleLightIndex)
        : LightSceneInfo(nullptr),
        SimpleLightIndex(InSimpleLightIndex)
    {
        SortKey.Packed = 0;
        SortKey.Fields.bIsNotSimpleLight = 0;
    }
};

struct FSortedLightSetSceneInfo
{
    // 簡單光源結束索引.
    int SimpleLightsEnd;
    // 分塊光照結束索引.
    int TiledSupportedEnd;
    // 分簇光照結束索引.
    int ClusteredSupportedEnd;

    // 首個帶陰影圖的光源索引.
    int AttenuationLightStart;

    // 簡單光源串列.
    FSimpleLightArray SimpleLights;
    // 有序的非簡單光源串列.
    TArray<FSortedLightSceneInfo, SceneRenderingAllocator> SortedLights;
};

可以關注的是FSortedLightSceneInfo的光源排序鍵值,最高優先級的屬性跟分簇、分塊、簡單光源相關,其次是燈光通道、陰影、光照函式、光照配置,最低優先級是光源型別,

這樣排序的依據是:分簇、分塊可以通過提前光源裁剪等操作,極大地降低著色消耗和指令數量,放在最高位;簡單光源光照邏輯簡單很多,指令數也少很多,所有簡單光源都可以分簇分塊渲染,所以緊隨分簇分塊之后;剩下的是非簡單光照的特性,由于燈光通道是否開啟也直接影響很多邏輯,開啟了燈光通道會較大地影響渲染效率,故而放在非簡單光源的首位;是否開啟陰影對渲染性能的影響也非常大,如果開啟了陰影,會增加若干個Pass繪制陰影圖和衰減紋理,增加渲染紋理切換,故而排次位合情合理;后面的幾個鍵值以此類推,

5.4.3.2 GatherAndSortLights

本節決議收集和排序光源的介面GatherAndSortLights

// Engine\Source\Runtime\Renderer\Private\LightRendering.cpp

void FDeferredShadingSceneRenderer::GatherAndSortLights(FSortedLightSetSceneInfo& OutSortedLights)
{
    if (bAllowSimpleLights)
    {
        // 收集簡單光源, 詳細決議見后面.
        GatherSimpleLights(ViewFamily, Views, OutSortedLights.SimpleLights);
    }
    
    FSimpleLightArray &SimpleLights = OutSortedLights.SimpleLights;
    TArray<FSortedLightSceneInfo, SceneRenderingAllocator> &SortedLights = OutSortedLights.SortedLights;

    // SortedLights包含了簡單光源和非簡單光源, 方便后面排序.
    SortedLights.Empty(Scene->Lights.Num() + SimpleLights.InstanceData.Num());

    bool bDynamicShadows = ViewFamily.EngineShowFlags.DynamicShadows && GetShadowQuality() > 0;

    // 構建可見光源串列.
    for (TSparseArray<FLightSceneInfoCompact>::TConstIterator LightIt(Scene->Lights); LightIt; ++LightIt)
    {
        const FLightSceneInfoCompact& LightSceneInfoCompact = *LightIt;
        const FLightSceneInfo* const LightSceneInfo = LightSceneInfoCompact.LightSceneInfo;

        if (LightSceneInfo->ShouldRenderLightViewIndependent()
            && !ViewFamily.EngineShowFlags.ReflectionOverride)
        {
            // 檢查光源是否在某個vie內可見.
            for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
            {
                if (LightSceneInfo->ShouldRenderLight(Views[ViewIndex]))
                {
                    // 創建FSortedLightSceneInfo實體.
                    FSortedLightSceneInfo* SortedLightInfo = new(SortedLights) FSortedLightSceneInfo(LightSceneInfo);

                    // 檢測陰影或光照函式.
                    SortedLightInfo->SortKey.Fields.LightType = LightSceneInfoCompact.LightType;
                    SortedLightInfo->SortKey.Fields.bTextureProfile = ViewFamily.EngineShowFlags.TexturedLightProfiles && LightSceneInfo->Proxy->GetIESTextureResource();
                    SortedLightInfo->SortKey.Fields.bShadowed = bDynamicShadows && CheckForProjectedShadows(LightSceneInfo);
                    SortedLightInfo->SortKey.Fields.bLightFunction = ViewFamily.EngineShowFlags.LightFunctions && CheckForLightFunction(LightSceneInfo);
                    SortedLightInfo->SortKey.Fields.bUsesLightingChannels = Views[ViewIndex].bUsesLightingChannels && LightSceneInfo->Proxy->GetLightingChannelMask() != GetDefaultLightingChannelMask();

                    // 非簡單光源.
                    SortedLightInfo->SortKey.Fields.bIsNotSimpleLight = 1;

                    // 支持分塊或分簇的具體條件: 沒有使用光源的附加特性, 也非*行光或矩形光.
                    const bool bTiledOrClusteredDeferredSupported =
                        !SortedLightInfo->SortKey.Fields.bTextureProfile &&
                        !SortedLightInfo->SortKey.Fields.bShadowed &&
                        !SortedLightInfo->SortKey.Fields.bLightFunction &&
                        !SortedLightInfo->SortKey.Fields.bUsesLightingChannels
                        && LightSceneInfoCompact.LightType != LightType_Directional
                        && LightSceneInfoCompact.LightType != LightType_Rect;

                    // 是否不支持分塊渲染.
                    SortedLightInfo->SortKey.Fields.bTiledDeferredNotSupported = !(bTiledOrClusteredDeferredSupported && LightSceneInfo->Proxy->IsTiledDeferredLightingSupported());

                    // 是否不支持分簇渲染.
                    SortedLightInfo->SortKey.Fields.bClusteredDeferredNotSupported = !bTiledOrClusteredDeferredSupported;
                    break;
                }
            }
        }
    }
    
    // 加入簡單光源.
    for (int32 SimpleLightIndex = 0; SimpleLightIndex < SimpleLights.InstanceData.Num(); SimpleLightIndex++)
    {
        FSortedLightSceneInfo* SortedLightInfo = new(SortedLights) FSortedLightSceneInfo(SimpleLightIndex);
        SortedLightInfo->SortKey.Fields.LightType = LightType_Point;
        SortedLightInfo->SortKey.Fields.bTextureProfile = 0;
        SortedLightInfo->SortKey.Fields.bShadowed = 0;
        SortedLightInfo->SortKey.Fields.bLightFunction = 0;
        SortedLightInfo->SortKey.Fields.bUsesLightingChannels = 0;

        // 簡單光源.
        SortedLightInfo->SortKey.Fields.bIsNotSimpleLight = 0;

        // 簡單光源都可以被分塊或分簇渲染.
        SortedLightInfo->SortKey.Fields.bTiledDeferredNotSupported = 0;
        SortedLightInfo->SortKey.Fields.bClusteredDeferredNotSupported = 0;
    }

    // 光源排序, 無陰影無光照函式的光源優先, 可以避免渲染紋理切換.
    struct FCompareFSortedLightSceneInfo
    {
        FORCEINLINE bool operator()( const FSortedLightSceneInfo& A, const FSortedLightSceneInfo& B ) const
        {
            return A.SortKey.Packed < B.SortKey.Packed;
        }
    };
    SortedLights.Sort( FCompareFSortedLightSceneInfo() );

    // 初始化索引.
    OutSortedLights.SimpleLightsEnd = SortedLights.Num();
    OutSortedLights.TiledSupportedEnd = SortedLights.Num();
    OutSortedLights.ClusteredSupportedEnd = SortedLights.Num();
    OutSortedLights.AttenuationLightStart = SortedLights.Num();

    // 遍歷所有待渲染的光源, 構建分簇分塊和無陰影光源的范圍.
    for (int32 LightIndex = 0; LightIndex < SortedLights.Num(); LightIndex++)
    {
        const FSortedLightSceneInfo& SortedLightInfo = SortedLights[LightIndex];
        const bool bDrawShadows = SortedLightInfo.SortKey.Fields.bShadowed;
        const bool bDrawLightFunction = SortedLightInfo.SortKey.Fields.bLightFunction;
        const bool bTextureLightProfile = SortedLightInfo.SortKey.Fields.bTextureProfile;
        const bool bLightingChannels = SortedLightInfo.SortKey.Fields.bUsesLightingChannels;

        if (SortedLightInfo.SortKey.Fields.bIsNotSimpleLight && OutSortedLights.SimpleLightsEnd == SortedLights.Num())
        {
            // 簡單光源的結束索引.
            OutSortedLights.SimpleLightsEnd = LightIndex;
        }

        if (SortedLightInfo.SortKey.Fields.bTiledDeferredNotSupported && OutSortedLights.TiledSupportedEnd == SortedLights.Num())
        {
            // 分塊光源的結束索引.
            OutSortedLights.TiledSupportedEnd = LightIndex;
        }

        if (SortedLightInfo.SortKey.Fields.bClusteredDeferredNotSupported && OutSortedLights.ClusteredSupportedEnd == SortedLights.Num())
        {
            // 分簇光源的結束索引.
            OutSortedLights.ClusteredSupportedEnd = LightIndex;
        }

        if (bDrawShadows || bDrawLightFunction || bLightingChannels)
        {
            // 首個帶陰影的光源索引.
            OutSortedLights.AttenuationLightStart = LightIndex;
            break;
        }
    }
}

上面代碼中可以看到,簡單光源都可以被分塊或分簇渲染,但對于非簡單光源,只有滿足以下條件的光源才可被分塊或分簇渲染:

  • 沒有使用光源的附加特性(TextureProfile、LightFunction、LightingChannel),
  • 沒有開啟陰影,
  • 非*行光或矩形光,

另外,是否支持分塊渲染,還需要光源場景代理的IsTiledDeferredLightingSupported回傳true:

// Engine\Source\Runtime\Engine\Public\SceneManagement.h

bool FLightSceneProxy::IsTiledDeferredLightingSupported() const 
{ 
    return bTiledDeferredLightingSupported;  
}

整個UE工程原始碼中,只有以下一句才可能使bTiledDeferredLightingSupported為true:

// GEngine\Source\Runtime\Engine\Private\PointLightSceneProxy.h

class FPointLightSceneProxy : public FLocalLightSceneProxy
{
    FPointLightSceneProxy(const UPointLightComponent* Component)
    ,    SourceLength(Component->SourceLength)
    (......)
    {
        // 是否支持分塊渲染.
        bTiledDeferredLightingSupported = (SourceLength == 0.0f);
    }
}

也就是說,長度為0的點光源才支持分塊渲染,其它型別和情況的光源都不支持,

光源長度大于0的點光源變成了球形光照模型,不再適用于簡單光照模型,也無法分塊渲染,

GatherSimpleLights的作用是從場景可見圖元收集簡單光源:

void FSceneRenderer::GatherSimpleLights(const FSceneViewFamily& ViewFamily, const TArray<FViewInfo>& Views, FSimpleLightArray& SimpleLights)
{
    TArray<const FPrimitiveSceneInfo*, SceneRenderingAllocator> PrimitivesWithSimpleLights;

    // 從所有可能存在簡單光源的視圖中收集可見圖元.
    for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
    {
        const FViewInfo& View = Views[ViewIndex];
        for (int32 PrimitiveIndex = 0; PrimitiveIndex < View.VisibleDynamicPrimitivesWithSimpleLights.Num(); PrimitiveIndex++)
        {
            const FPrimitiveSceneInfo* PrimitiveSceneInfo = View.VisibleDynamicPrimitivesWithSimpleLights[PrimitiveIndex];

            // 注意加入元素使用的是AddUnique, 可以防止同一個光源在串列中存在多份.
            PrimitivesWithSimpleLights.AddUnique(PrimitiveSceneInfo);
        }
    }

    // 從圖元中收集簡單光源.
    for (int32 PrimitiveIndex = 0; PrimitiveIndex < PrimitivesWithSimpleLights.Num(); PrimitiveIndex++)
    {
        const FPrimitiveSceneInfo* Primitive = PrimitivesWithSimpleLights[PrimitiveIndex];
        Primitive->Proxy->GatherSimpleLights(ViewFamily, SimpleLights);
    }
}

收集簡單光源時,會呼叫每個圖元場景代表的介面GatherSimpleLights,目前,實作了此介面的圖元場景代表主要有:

  • FNiagaraSceneProxy
  • FNiagaraRendererLights
  • FDynamicSpriteEmitterData
  • FParticleSystemSceneProxy

都是級聯粒子和Niagara粒子相關的代理,因此可推斷,簡單光源只用于粒子特效模塊,

5.4.3.3 ComputeLightGrid

ComputeLightGrid作用是在錐體空間(frustum space)裁剪區域光源和反射探針到3D格子中,構建每個視圖相關的光源串列和格子,

// Engine\Source\Runtime\Renderer\Private\LightGridInjection.cpp

void FDeferredShadingSceneRenderer::ComputeLightGrid(FRHICommandListImmediate& RHICmdList, bool bNeedLightGrid, FSortedLightSetSceneInfo &SortedLightSet)
{
    // 不需要光源格子或不支持SM5及以上, 直接回傳.
    if (!bNeedLightGrid || FeatureLevel < ERHIFeatureLevel::SM5)
    {
        for (auto& View : Views)
        {
            View.ForwardLightingResources = GetMinimalDummyForwardLightingResources();
        }
        return;
    }

    {
        SCOPED_DRAW_EVENT(RHICmdList, ComputeLightGrid);

        static const auto AllowStaticLightingVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.AllowStaticLighting"));
        const bool bAllowStaticLighting = (!AllowStaticLightingVar || AllowStaticLightingVar->GetValueOnRenderThread() != 0);
        const bool bAllowFormatConversion = RHISupportsBufferLoadTypeConversion(GMaxRHIShaderPlatform);

        // 是否有view使用前向渲染.
        bool bAnyViewUsesForwardLighting = false;
        for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
        {
            const FViewInfo& View = Views[ViewIndex];
            bAnyViewUsesForwardLighting |= View.bTranslucentSurfaceLighting || ShouldRenderVolumetricFog() || View.bHasSingleLayerWaterMaterial;
        }

        // 是否裁剪到格子的光源.
        const bool bCullLightsToGrid = GLightCullingQuality 
            && (ViewFamily.EngineShowFlags.DirectLighting 
            && (IsForwardShadingEnabled(ShaderPlatform) || bAnyViewUsesForwardLighting || IsRayTracingEnabled() || ShouldUseClusteredDeferredShading()));
               
        // Store this flag if lights are injected in the grids, check with 'AreClusteredLightsInLightGrid()'
        bClusteredShadingLightsInLightGrid = bCullLightsToGrid;

        for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
        {
            FViewInfo& View = Views[ViewIndex];
            FForwardLightData& ForwardLightData = https://www.cnblogs.com/timlly/p/View.ForwardLightingResources->ForwardLightData;
            ForwardLightData = FForwardLightData();

            TArray ForwardLocalLightData;
            
            float FurthestLight = 1000;

            // Track the end markers for different types
            int32 SimpleLightsEnd = 0;
            int32 ClusteredSupportedEnd = 0;

            // 裁剪光源.
            if (bCullLightsToGrid)
            {
                SimpleLightsEnd = SortedLightSet.SimpleLightsEnd;
                // 1. 插入簡單光源.
                if (SimpleLightsEnd > 0)
                {
                    const FSimpleLightArray &SimpleLights = SortedLightSet.SimpleLights;

                    const FFloat16 SimpleLightSourceLength16f = FFloat16(0);
                    FLightingChannels SimpleLightLightingChannels;
                    // 將簡單光源應用于所有通道上.
                    SimpleLightLightingChannels.bChannel0 = SimpleLightLightingChannels.bChannel1 = SimpleLightLightingChannels.bChannel2 = true;
                    const uint32 SimpleLightLightingChannelMask = GetLightingChannelMaskForStruct(SimpleLightLightingChannels);

                    // 使用有序的光源串列, 跟蹤它們的范圍.
                    for (int SortedIndex = 0; SortedIndex < SortedLightSet.SimpleLightsEnd; ++SortedIndex)
                    {
                        int32 SimpleLightIndex = SortedLightSet.SortedLights[SortedIndex].SimpleLightIndex;

                        ForwardLocalLightData.AddUninitialized(1);
                        FForwardLocalLightData& LightData = ForwardLocalLightData.Last();

                        const FSimpleLightEntry& SimpleLight = SimpleLights.InstanceData[SimpleLightIndex];
                        const FSimpleLightPerViewEntry& SimpleLightPerViewData = SimpleLights.GetViewDependentData(SimpleLightIndex, ViewIndex, Views.Num());
                        LightData.LightPositionAndInvRadius = FVector4(SimpleLightPerViewData.Position, 1.0f / FMath::Max(SimpleLight.Radius, KINDA_SMALL_NUMBER));
                        LightData.LightColorAndFalloffExponent = FVector4(SimpleLight.Color, SimpleLight.Exponent);

                        // 簡單光源沒有陰影.
                        uint32 ShadowMapChannelMask = 0;
                        ShadowMapChannelMask |= SimpleLightLightingChannelMask << 8;

                        LightData.LightDirectionAndShadowMapChannelMask = FVector4(FVector(1, 0, 0), *((float*)&ShadowMapChannelMask));

                        const FFloat16 VolumetricScatteringIntensity16f = FFloat16(SimpleLight.VolumetricScatteringIntensity);
                        const uint32 PackedWInt = ((uint32)SimpleLightSourceLength16f.Encoded) | ((uint32)VolumetricScatteringIntensity16f.Encoded << 16);

                        LightData.SpotAnglesAndSourceRadiusPacked = FVector4(-2, 1, 0, *(float*)&PackedWInt);
                        LightData.LightTangentAndSoftSourceRadius = FVector4(1.0f, 0.0f, 0.0f, 0.0f);
                        LightData.RectBarnDoor = FVector4(0, -2, 0, 0);
                    }
                }
                
                const TArray& SortedLights = SortedLightSet.SortedLights;
                ClusteredSupportedEnd = SimpleLightsEnd;
                // 2. 增加其它型別的光源, 追蹤支持分簇渲染的末尾索引.
                for (int SortedIndex = SimpleLightsEnd; SortedIndex < SortedLights.Num(); ++SortedIndex)
                {
                    const FSortedLightSceneInfo& SortedLightInfo = SortedLights[SortedIndex];
                    const FLightSceneInfo* const LightSceneInfo = SortedLightInfo.LightSceneInfo;
                    const FLightSceneProxy* LightProxy = LightSceneInfo->Proxy;

                    if (LightSceneInfo->ShouldRenderLight(View)
                        && !ViewFamily.EngineShowFlags.ReflectionOverride)
                    {
                        FLightShaderParameters LightParameters;
                        LightProxy->GetLightShaderParameters(LightParameters);

                        if (LightProxy->IsInverseSquared())
                        {
                            LightParameters.FalloffExponent = 0;
                        }

                        if (View.bIsReflectionCapture)
                        {
                            LightParameters.Color *= LightProxy->GetIndirectLightingScale();
                        }

                        int32 ShadowMapChannel = LightProxy->GetShadowMapChannel();
                        int32 DynamicShadowMapChannel = LightSceneInfo->GetDynamicShadowMapChannel();

                        if (!bAllowStaticLighting)
                        {
                            ShadowMapChannel = INDEX_NONE;
                        }

                        // 靜態陰影使用ShadowMapChannel, 動態陰影被打包進光源衰減紋理DynamicShadowMapChannel.
                        uint32 LightTypeAndShadowMapChannelMaskPacked =
                            (ShadowMapChannel == 0 ? 1 : 0) |
                            (ShadowMapChannel == 1 ? 2 : 0) |
                            (ShadowMapChannel == 2 ? 4 : 0) |
                            (ShadowMapChannel == 3 ? 8 : 0) |
                            (DynamicShadowMapChannel == 0 ? 16 : 0) |
                            (DynamicShadowMapChannel == 1 ? 32 : 0) |
                            (DynamicShadowMapChannel == 2 ? 64 : 0) |
                            (DynamicShadowMapChannel == 3 ? 128 : 0);

                        LightTypeAndShadowMapChannelMaskPacked |= LightProxy->GetLightingChannelMask() << 8;
                        LightTypeAndShadowMapChannelMaskPacked |= SortedLightInfo.SortKey.Fields.LightType << 16;

                        // 處理區域光源.
                        if ((SortedLightInfo.SortKey.Fields.LightType == LightType_Point && ViewFamily.EngineShowFlags.PointLights) ||
                            (SortedLightInfo.SortKey.Fields.LightType == LightType_Spot && ViewFamily.EngineShowFlags.SpotLights) ||
                            (SortedLightInfo.SortKey.Fields.LightType == LightType_Rect && ViewFamily.EngineShowFlags.RectLights))
                        {
                            ForwardLocalLightData.AddUninitialized(1);
                            FForwardLocalLightData& LightData = ForwardLocalLightData.Last();

                            // Track the last one to support clustered deferred
                            if (!SortedLightInfo.SortKey.Fields.bClusteredDeferredNotSupported)
                            {
                                ClusteredSupportedEnd = FMath::Max(ClusteredSupportedEnd, ForwardLocalLightData.Num());
                            }
                            const float LightFade = GetLightFadeFactor(View, LightProxy);
                            LightParameters.Color *= LightFade;

                            LightData.LightPositionAndInvRadius = FVector4(LightParameters.Position, LightParameters.InvRadius);
                            LightData.LightColorAndFalloffExponent = FVector4(LightParameters.Color, LightParameters.FalloffExponent);
                            LightData.LightDirectionAndShadowMapChannelMask = FVector4(LightParameters.Direction, *((float*)&LightTypeAndShadowMapChannelMaskPacked));

                            LightData.SpotAnglesAndSourceRadiusPacked = FVector4(LightParameters.SpotAngles.X, LightParameters.SpotAngles.Y, LightParameters.SourceRadius, 0);

                            LightData.LightTangentAndSoftSourceRadius = FVector4(LightParameters.Tangent, LightParameters.SoftSourceRadius);

                            LightData.RectBarnDoor = FVector4(LightParameters.RectLightBarnCosAngle, LightParameters.RectLightBarnLength, 0, 0);

                            float VolumetricScatteringIntensity = LightProxy->GetVolumetricScatteringIntensity();

                            if (LightNeedsSeparateInjectionIntoVolumetricFog(LightSceneInfo, VisibleLightInfos[LightSceneInfo->Id]))
                            {
                                // Disable this lights forward shading volumetric scattering contribution
                                VolumetricScatteringIntensity = 0;
                            }

                            // Pack both values into a single float to keep float4 alignment
                            const FFloat16 SourceLength16f = FFloat16(LightParameters.SourceLength);
                            const FFloat16 VolumetricScatteringIntensity16f = FFloat16(VolumetricScatteringIntensity);
                            const uint32 PackedWInt = ((uint32)SourceLength16f.Encoded) | ((uint32)VolumetricScatteringIntensity16f.Encoded << 16);
                            LightData.SpotAnglesAndSourceRadiusPacked.W = *(float*)&PackedWInt;

                            const FSphere BoundingSphere = LightProxy->GetBoundingSphere();
                            const float Distance = View.ViewMatrices.GetViewMatrix().TransformPosition(BoundingSphere.Center).Z + BoundingSphere.W;
                            FurthestLight = FMath::Max(FurthestLight, Distance);
                        }
                        // *行光源.
                        else if (SortedLightInfo.SortKey.Fields.LightType == LightType_Directional && ViewFamily.EngineShowFlags.DirectionalLights)
                        {
                            (......)
                        }
                    }
                }
            }

            const int32 NumLocalLightsFinal = ForwardLocalLightData.Num();
            if (ForwardLocalLightData.Num() == 0)
            {
                ForwardLocalLightData.AddZeroed();
            }

            // 更新光源的資料到GPU.
            UpdateDynamicVector4BufferData(ForwardLocalLightData, View.ForwardLightingResources->ForwardLocalLightBuffer);

            const FIntPoint LightGridSizeXY = FIntPoint::DivideAndRoundUp(View.ViewRect.Size(), GLightGridPixelSize);
            ForwardLightData.ForwardLocalLightBuffer = View.ForwardLightingResources->ForwardLocalLightBuffer.SRV;
            ForwardLightData.NumLocalLights = NumLocalLightsFinal;
            ForwardLightData.NumReflectionCaptures = View.NumBoxReflectionCaptures + View.NumSphereReflectionCaptures;
            ForwardLightData.NumGridCells = LightGridSizeXY.X * LightGridSizeXY.Y * GLightGridSizeZ;
            ForwardLightData.CulledGridSize = FIntVector(LightGridSizeXY.X, LightGridSizeXY.Y, GLightGridSizeZ);
            ForwardLightData.MaxCulledLightsPerCell = GMaxCulledLightsPerCell;
            ForwardLightData.LightGridPixelSizeShift = FMath::FloorLog2(GLightGridPixelSize);
            ForwardLightData.SimpleLightsEndIndex = SimpleLightsEnd;
            ForwardLightData.ClusteredDeferredSupportedEndIndex = ClusteredSupportedEnd;

            // Clamp far plane to something reasonable
            float FarPlane = FMath::Min(FMath::Max(FurthestLight, View.FurthestReflectionCaptureDistance), (float)HALF_WORLD_MAX / 5.0f);
            FVector ZParams = GetLightGridZParams(View.NearClippingDistance, FarPlane + 10.f);
            ForwardLightData.LightGridZParams = ZParams;

            const uint64 NumIndexableLights = CHANGE_LIGHTINDEXTYPE_SIZE && !bAllowFormatConversion ? (1llu << (sizeof(FLightIndexType32) * 8llu)) : (1llu << (sizeof(FLightIndexType) * 8llu));

            const int32 NumCells = LightGridSizeXY.X * LightGridSizeXY.Y * GLightGridSizeZ * NumCulledGridPrimitiveTypes;

            if (View.ForwardLightingResources->NumCulledLightsGrid.NumBytes != NumCells * NumCulledLightsGridStride * sizeof(uint32))
            {
                View.ForwardLightingResources->NumCulledLightsGrid.Initialize(sizeof(uint32), NumCells * NumCulledLightsGridStride, PF_R32_UINT);
            }

            if (View.ForwardLightingResources->CulledLightDataGrid.NumBytes != NumCells * GMaxCulledLightsPerCell * LightIndexTypeSize)
            {
                View.ForwardLightingResources->CulledLightDataGrid.Initialize(LightIndexTypeSize, NumCells * GMaxCulledLightsPerCell, LightIndexTypeSize == sizeof(uint16) ? PF_R16_UINT : PF_R32_UINT);
            }

            const bool bShouldCacheTemporaryBuffers = View.ViewState != nullptr;
            FForwardLightingCullingResources LocalCullingResources;
            FForwardLightingCullingResources& ForwardLightingCullingResources = bShouldCacheTemporaryBuffers ? View.ViewState->ForwardLightingCullingResources : LocalCullingResources;

            const uint32 CulledLightLinksElements = NumCells * GMaxCulledLightsPerCell * LightLinkStride;

            ForwardLightData.DummyRectLightSourceTexture = GWhiteTexture->TextureRHI;
            ForwardLightData.NumCulledLightsGrid = View.ForwardLightingResources->NumCulledLightsGrid.SRV;
            ForwardLightData.CulledLightDataGrid = View.ForwardLightingResources->CulledLightDataGrid.SRV;

            // 創建光源資料的Uniform Buffer.
            View.ForwardLightingResources->ForwardLightDataUniformBuffer = TUniformBufferRef::CreateUniformBufferImmediate(ForwardLightData, UniformBuffer_SingleFrame);

            // 下面使用RDG和Compute Shader去裁剪區域光源和反射探針.
            
            // 執行緒組數.
            const FIntVector NumGroups = FIntVector::DivideAndRoundUp(FIntVector(LightGridSizeXY.X, LightGridSizeXY.Y, GLightGridSizeZ), LightGridInjectionGroupSize);

            TArray> OutUAVs({
                View.ForwardLightingResources->NumCulledLightsGrid.UAV,
                View.ForwardLightingResources->CulledLightDataGrid.UAV });

            RHICmdList.TransitionResources(EResourceTransitionAccess::EWritable, EResourceTransitionPipeline::EGfxToCompute, OutUAVs.GetData(), OutUAVs.Num());
            {
                FRDGBuilder GraphBuilder(RHICmdList);
                {
                    RDG_EVENT_SCOPE(GraphBuilder,"CullLights %ux%ux%u NumLights %u NumCaptures %u",
                        ForwardLightData.CulledGridSize.X,
                        ForwardLightData.CulledGridSize.Y,
                        ForwardLightData.CulledGridSize.Z,
                        ForwardLightData.NumLocalLights,
                        ForwardLightData.NumReflectionCaptures);

                    FRDGBufferRef CulledLightLinksBuffer = GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateBufferDesc(sizeof(uint32), CulledLightLinksElements), TEXT("CulledLightLinks"));
                    FRDGBufferRef StartOffsetGridBuffer = GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateBufferDesc(sizeof(uint32), NumCells), TEXT("StartOffsetGrid"));
                    FRDGBufferRef NextCulledLightLinkBuffer = GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateBufferDesc(sizeof(uint32), 1), TEXT("NextCulledLightLink"));
                    FRDGBufferRef NextCulledLightDataBuffer = GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateBufferDesc(sizeof(uint32), 1), TEXT("NextCulledLightData"));

                    // 創建PassParameters實體.
                    FLightGridInjectionCS::FParameters *PassParameters = GraphBuilder.AllocParameters<FLightGridInjectionCS::FParameters>();

                    PassParameters->View = View.ViewUniformBuffer;
                    PassParameters->ReflectionCapture = View.ReflectionCaptureUniformBuffer;
                    PassParameters->Forward = View.ForwardLightingResources->ForwardLightDataUniformBuffer;
                    PassParameters->RWNumCulledLightsGrid = View.ForwardLightingResources->NumCulledLightsGrid.UAV;
                    PassParameters->RWCulledLightDataGrid = View.ForwardLightingResources->CulledLightDataGrid.UAV;
                    PassParameters->RWNextCulledLightLink = GraphBuilder.CreateUAV(NextCulledLightLinkBuffer, PF_R32_UINT);
                    PassParameters->RWStartOffsetGrid = GraphBuilder.CreateUAV(StartOffsetGridBuffer, PF_R32_UINT);
                    PassParameters->RWCulledLightLinks = GraphBuilder.CreateUAV(CulledLightLinksBuffer, PF_R32_UINT);

                    FLightGridInjectionCS::FPermutationDomain PermutationVector;
                    PermutationVector.Set<FLightGridInjectionCS::FUseLinkedListDim>(GLightLinkedListCulling != 0);
                    // 光源格子注入shader.
                    TShaderMapRef<FLightGridInjectionCS> ComputeShader(View.ShaderMap, PermutationVector);

                    // 處理光源格子.
                    if (GLightLinkedListCulling != 0)
                    {
                        // 清理UAV.
                        AddClearUAVPass(GraphBuilder, PassParameters->RWStartOffsetGrid, 0xFFFFFFFF);
                        AddClearUAVPass(GraphBuilder, PassParameters->RWNextCulledLightLink, 0);
                        AddClearUAVPass(GraphBuilder, GraphBuilder.CreateUAV(NextCulledLightDataBuffer, PF_R32_UINT), 0);
                        // 增加光源格子注入Pass.
                        FComputeShaderUtils::AddPass(GraphBuilder, RDG_EVENT_NAME("LightGridInject:LinkedList"), ComputeShader, PassParameters, NumGroups);

                        {
                            // 光源格子壓縮shader.
                            TShaderMapRef<FLightGridCompactCS> ComputeShaderCompact(View.ShaderMap);
                            FLightGridCompactCS::FParameters *PassParametersCompact = GraphBuilder.AllocParameters<FLightGridCompactCS::FParameters>();
                            PassParametersCompact->View = View.ViewUniformBuffer;
                            PassParametersCompact->Forward = View.ForwardLightingResources->ForwardLightDataUniformBuffer;

                            PassParametersCompact->CulledLightLinks = GraphBuilder.CreateSRV(CulledLightLinksBuffer, PF_R32_UINT);
                            PassParametersCompact->RWNumCulledLightsGrid = View.ForwardLightingResources->NumCulledLightsGrid.UAV;
                            PassParametersCompact->RWCulledLightDataGrid = View.ForwardLightingResources->CulledLightDataGrid.UAV;
                            PassParametersCompact->RWNextCulledLightData = https://www.cnblogs.com/timlly/p/GraphBuilder.CreateUAV(NextCulledLightDataBuffer, PF_R32_UINT);
                            PassParametersCompact->StartOffsetGrid = GraphBuilder.CreateSRV(StartOffsetGridBuffer, PF_R32_UINT);

                            // 增加光源格子壓縮Pass.
                            FComputeShaderUtils::AddPass(GraphBuilder, RDG_EVENT_NAME("CompactLinks"), ComputeShaderCompact, PassParametersCompact, NumGroups);
                        }
                    }
                    else
                    {
                        RHICmdList.ClearUAVUint(View.ForwardLightingResources->NumCulledLightsGrid.UAV, FUintVector4(0, 0, 0, 0));
                        FComputeShaderUtils::AddPass(GraphBuilder, RDG_EVENT_NAME("LightGridInject:NotLinkedList"), ComputeShader, PassParameters, NumGroups);
                    }
                }
                GraphBuilder.Execute();

                RHICmdList.TransitionResources(EResourceTransitionAccess::EReadable, EResourceTransitionPipeline::EComputeToGfx, OutUAVs.GetData(), OutUAVs.Num());
            }
        }
    }
}

需要注意的是,只有前向渲染、使用表面著色的透明通道和分簇延遲渲染才有效,處理光源格子使用了RDG和Compute Shader,使用的Shader是FLightGridInjectionCS和FLightGridCompactCS:

// Engine\Source\Runtime\Renderer\Private\LightGridInjection.cpp

class FLightGridInjectionCS : public FGlobalShader
{
    DECLARE_GLOBAL_SHADER(FLightGridInjectionCS);
    SHADER_USE_PARAMETER_STRUCT(FLightGridInjectionCS, FGlobalShader)
public:
    class FUseLinkedListDim : SHADER_PERMUTATION_BOOL("USE_LINKED_CULL_LIST");
    using FPermutationDomain = TShaderPermutationDomain<FUseLinkedListDim>;

    BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
        //SHADER_PARAMETER_STRUCT_INCLUDE(FLightGridInjectionCommonParameters, CommonParameters)
        SHADER_PARAMETER_STRUCT_REF(FReflectionCaptureShaderData, ReflectionCapture)
        SHADER_PARAMETER_STRUCT_REF(FForwardLightData, Forward)
        SHADER_PARAMETER_STRUCT_REF(FViewUniformShaderParameters, View)
        SHADER_PARAMETER_UAV(RWBuffer<uint>, RWNumCulledLightsGrid)
        SHADER_PARAMETER_UAV(RWBuffer<uint>, RWCulledLightDataGrid)
        SHADER_PARAMETER_RDG_BUFFER_UAV(RWBuffer<uint>, RWNextCulledLightLink)
        SHADER_PARAMETER_RDG_BUFFER_UAV(RWBuffer<uint>, RWStartOffsetGrid)
        SHADER_PARAMETER_RDG_BUFFER_UAV(RWBuffer<uint>, RWCulledLightLinks)
        SHADER_PARAMETER_SRV(StrongTypedBuffer<float4>, LightViewSpacePositionAndRadius)
        SHADER_PARAMETER_SRV(StrongTypedBuffer<float4>, LightViewSpaceDirAndPreprocAngle)
    END_SHADER_PARAMETER_STRUCT()

    static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
    {
        return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::SM5);
    }

    static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
    {
        FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment);
        OutEnvironment.SetDefine(TEXT("THREADGROUP_SIZE"), LightGridInjectionGroupSize);
        FForwardLightingParameters::ModifyCompilationEnvironment(Parameters.Platform, OutEnvironment);
        OutEnvironment.SetDefine(TEXT("LIGHT_LINK_STRIDE"), LightLinkStride);
        OutEnvironment.SetDefine(TEXT("ENABLE_LIGHT_CULLING_VIEW_SPACE_BUILD_DATA"), ENABLE_LIGHT_CULLING_VIEW_SPACE_BUILD_DATA);
    }
};

class FLightGridCompactCS : public FGlobalShader
{
    DECLARE_GLOBAL_SHADER(FLightGridCompactCS)
    SHADER_USE_PARAMETER_STRUCT(FLightGridCompactCS, FGlobalShader)
public:
    BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
        SHADER_PARAMETER_STRUCT_REF(FForwardLightData, Forward)
        SHADER_PARAMETER_STRUCT_REF(FViewUniformShaderParameters, View)
        SHADER_PARAMETER_UAV(RWBuffer<uint>, RWNumCulledLightsGrid)
        SHADER_PARAMETER_UAV(RWBuffer<uint>, RWCulledLightDataGrid)
        SHADER_PARAMETER_RDG_BUFFER_UAV(RWBuffer<uint>, RWNextCulledLightData)
        SHADER_PARAMETER_RDG_BUFFER_SRV(Buffer<uint>, StartOffsetGrid)
        SHADER_PARAMETER_RDG_BUFFER_SRV(Buffer<uint>, CulledLightLinks)

    END_SHADER_PARAMETER_STRUCT()

    static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
    {
        return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::SM5);
    }

    static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
    {
        FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment);
        OutEnvironment.SetDefine(TEXT("THREADGROUP_SIZE"), LightGridInjectionGroupSize);
        FForwardLightingParameters::ModifyCompilationEnvironment(Parameters.Platform, OutEnvironment);
        OutEnvironment.SetDefine(TEXT("LIGHT_LINK_STRIDE"), LightLinkStride);
        OutEnvironment.SetDefine(TEXT("MAX_CAPTURES"), GMaxNumReflectionCaptures);
        OutEnvironment.SetDefine(TEXT("ENABLE_LIGHT_CULLING_VIEW_SPACE_BUILD_DATA"), ENABLE_LIGHT_CULLING_VIEW_SPACE_BUILD_DATA);
    }
};

對應的shader代碼:

// Engine\Shaders\Private\LightGridInjection.usf

RWBuffer<uint> RWNumCulledLightsGrid;
RWBuffer<uint> RWCulledLightDataGrid;

RWBuffer<uint> RWNextCulledLightLink;
RWBuffer<uint> RWStartOffsetGrid;
RWBuffer<uint> RWCulledLightLinks;

#define REFINE_SPOTLIGHT_BOUNDS 1

// 從Z切片序號轉到就*的視圖空間深度值.
float ComputeCellNearViewDepthFromZSlice(uint ZSlice)
{
    // 注意切片的深度不是執行緒的, 而是指數型, 離相機越遠的, 深度范圍越大.
    float SliceDepth = (exp2(ZSlice / ForwardLightData.LightGridZParams.z) - ForwardLightData.LightGridZParams.y) / ForwardLightData.LightGridZParams.x;

    if (ZSlice == (uint)ForwardLightData.CulledGridSize.z)
    {
        // Extend the last slice depth max out to world max
        // This allows clamping the depth range to reasonable values, 
        // But has the downside that any lights falling into the last depth slice will have very poor culling,
        // Since the view space AABB will be bloated in x and y
        SliceDepth = 2000000.0f;
    }

    if (ZSlice == 0)
    {
        // The exponential distribution of z slices contains an offset, but some screen pixels
        // may be nearer to the camera than this offset. To avoid false light rejection, we set the
        // first depth slice to zero to ensure that the AABB includes the [0, offset] depth range.
        SliceDepth = 0.0f;
    }

    return SliceDepth;
}

// 根據格子計算AABB
void ComputeCellViewAABB(uint3 GridCoordinate, out float3 ViewTileMin, out float3 ViewTileMax)
{
    // Compute extent of tiles in clip-space. Note that the last tile may extend a bit outside of view if view size is not evenly divisible tile size.
    const float2 InvCulledGridSizeF = (1 << ForwardLightData.LightGridPixelSizeShift) * View.ViewSizeAndInvSize.zw;
    const float2 TileSize = float2(2.0f, -2.0f) * InvCulledGridSizeF.xy;
    const float2 UnitPlaneMin = float2(-1.0f, 1.0f);

    float2 UnitPlaneTileMin = GridCoordinate.xy * TileSize + UnitPlaneMin;
    float2 UnitPlaneTileMax = (GridCoordinate.xy + 1) * TileSize + UnitPlaneMin;

    float MinTileZ = ComputeCellNearViewDepthFromZSlice(GridCoordinate.z);
    float MaxTileZ = ComputeCellNearViewDepthFromZSlice(GridCoordinate.z + 1);

    float MinTileDeviceZ = ConvertToDeviceZ(MinTileZ);
    float4 MinDepthCorner0 = mul(float4(UnitPlaneTileMin.x, UnitPlaneTileMin.y, MinTileDeviceZ, 1), View.ClipToView);
    float4 MinDepthCorner1 = mul(float4(UnitPlaneTileMax.x, UnitPlaneTileMax.y, MinTileDeviceZ, 1), View.ClipToView);
    float4 MinDepthCorner2 = mul(float4(UnitPlaneTileMin.x, UnitPlaneTileMax.y, MinTileDeviceZ, 1), View.ClipToView);
    float4 MinDepthCorner3 = mul(float4(UnitPlaneTileMax.x, UnitPlaneTileMin.y, MinTileDeviceZ, 1), View.ClipToView);

    float MaxTileDeviceZ = ConvertToDeviceZ(MaxTileZ);
    float4 MaxDepthCorner0 = mul(float4(UnitPlaneTileMin.x, UnitPlaneTileMin.y, MaxTileDeviceZ, 1), View.ClipToView);
    float4 MaxDepthCorner1 = mul(float4(UnitPlaneTileMax.x, UnitPlaneTileMax.y, MaxTileDeviceZ, 1), View.ClipToView);
    float4 MaxDepthCorner2 = mul(float4(UnitPlaneTileMin.x, UnitPlaneTileMax.y, MaxTileDeviceZ, 1), View.ClipToView);
    float4 MaxDepthCorner3 = mul(float4(UnitPlaneTileMax.x, UnitPlaneTileMin.y, MaxTileDeviceZ, 1), View.ClipToView);

    float2 ViewMinDepthCorner0 = MinDepthCorner0.xy / MinDepthCorner0.w;
    float2 ViewMinDepthCorner1 = MinDepthCorner1.xy / MinDepthCorner1.w;
    float2 ViewMinDepthCorner2 = MinDepthCorner2.xy / MinDepthCorner2.w;
    float2 ViewMinDepthCorner3 = MinDepthCorner3.xy / MinDepthCorner3.w;
    float2 ViewMaxDepthCorner0 = MaxDepthCorner0.xy / MaxDepthCorner0.w;
    float2 ViewMaxDepthCorner1 = MaxDepthCorner1.xy / MaxDepthCorner1.w;
    float2 ViewMaxDepthCorner2 = MaxDepthCorner2.xy / MaxDepthCorner2.w;
    float2 ViewMaxDepthCorner3 = MaxDepthCorner3.xy / MaxDepthCorner3.w;

    ViewTileMin.xy = min(ViewMinDepthCorner0, ViewMinDepthCorner1);
    ViewTileMin.xy = min(ViewTileMin.xy, ViewMinDepthCorner2);
    ViewTileMin.xy = min(ViewTileMin.xy, ViewMinDepthCorner3);
    ViewTileMin.xy = min(ViewTileMin.xy, ViewMaxDepthCorner0);
    ViewTileMin.xy = min(ViewTileMin.xy, ViewMaxDepthCorner1);
    ViewTileMin.xy = min(ViewTileMin.xy, ViewMaxDepthCorner2);
    ViewTileMin.xy = min(ViewTileMin.xy, ViewMaxDepthCorner3);

    ViewTileMax.xy = max(ViewMinDepthCorner0, ViewMinDepthCorner1);
    ViewTileMax.xy = max(ViewTileMax.xy, ViewMinDepthCorner2);
    ViewTileMax.xy = max(ViewTileMax.xy, ViewMinDepthCorner3);
    ViewTileMax.xy = max(ViewTileMax.xy, ViewMaxDepthCorner0);
    ViewTileMax.xy = max(ViewTileMax.xy, ViewMaxDepthCorner1);
    ViewTileMax.xy = max(ViewTileMax.xy, ViewMaxDepthCorner2);
    ViewTileMax.xy = max(ViewTileMax.xy, ViewMaxDepthCorner3);

    ViewTileMin.z = MinTileZ;
    ViewTileMax.z = MaxTileZ; 
}

// 圓錐體和球體相交測驗.
bool IntersectConeWithSphere(float3 ConeVertex, float3 ConeAxis, float ConeRadius, float2 CosSinAngle, float4 SphereToTest)
{
    float3 ConeVertexToSphereCenter = SphereToTest.xyz - ConeVertex;
    float ConeVertexToSphereCenterLengthSq = dot(ConeVertexToSphereCenter, ConeVertexToSphereCenter);
    float SphereProjectedOntoConeAxis = dot(ConeVertexToSphereCenter, -ConeAxis);
    float DistanceToClosestPoint = CosSinAngle.x * sqrt(ConeVertexToSphereCenterLengthSq - SphereProjectedOntoConeAxis * SphereProjectedOntoConeAxis) - SphereProjectedOntoConeAxis * CosSinAngle.y;
 
    bool bSphereTooFarFromCone = DistanceToClosestPoint > SphereToTest.w;
    bool bSpherePastConeEnd = SphereProjectedOntoConeAxis > SphereToTest.w + ConeRadius;
    bool bSphereBehindVertex = SphereProjectedOntoConeAxis < -SphereToTest.w;
    return !(bSphereTooFarFromCone || bSpherePastConeEnd || bSphereBehindVertex);
}

bool AabbOutsidePlane(float3 center, float3 extents, float4 plane)
{
    float dist = dot(float4(center, 1.0), plane);
    float radius = dot(extents, abs(plane.xyz));

    return dist > radius;
}

// *似錐體和AABB的測驗. 創建一個在錐體上的面向AABB中心的專用*面.
bool IsAabbOutsideInfiniteAcuteConeApprox(float3 ConeVertex, float3 ConeAxis, float TanConeAngle, float3 AabbCentre, float3 AabbExt)
{
    // 1. find plane (well, base) in which normal lies, and which is perpendicular to axis and centre of aabb.
    float3 D = AabbCentre - ConeVertex;

    // perpendicular to cone axis in plane of cone axis and aabb centre.
    float3 M = -normalize(cross(cross(D, ConeAxis), ConeAxis));
    float3 N = -TanConeAngle * ConeAxis + M;
    float4 Plane = float4(N, 0.0);

    return AabbOutsidePlane(D, AabbExt, Plane);
}

// 光源格子注入主入口.
[numthreads(THREADGROUP_SIZE, THREADGROUP_SIZE, THREADGROUP_SIZE)]
void LightGridInjectionCS(
    uint3 GroupId : SV_GroupID,
    uint3 DispatchThreadId : SV_DispatchThreadID,
    uint3 GroupThreadId : SV_GroupThreadID)
{
    uint3 GridCoordinate = DispatchThreadId;

    if (all(GridCoordinate < (uint3)ForwardLightData.CulledGridSize))
    {
        // 格子索引.
        uint GridIndex = (GridCoordinate.z * ForwardLightData.CulledGridSize.y + GridCoordinate.y) * ForwardLightData.CulledGridSize.x + GridCoordinate.x;

#define CULL_LIGHTS 1
    #if CULL_LIGHTS
        float3 ViewTileMin;
        float3 ViewTileMax;
        // 計算格子包圍盒.
        ComputeCellViewAABB(GridCoordinate, ViewTileMin, ViewTileMax);

        float3 ViewTileCenter = .5f * (ViewTileMin + ViewTileMax);
        float3 ViewTileExtent = ViewTileMax - ViewTileCenter;
        float3 WorldTileCenter = mul(float4(ViewTileCenter, 1), View.ViewToTranslatedWorld).xyz - View.PreViewTranslation;
        float4 WorldTileBoundingSphere = float4(WorldTileCenter, length(ViewTileExtent));

        uint NumAvailableLinks = ForwardLightData.NumGridCells * ForwardLightData.MaxCulledLightsPerCell * NUM_CULLED_GRID_PRIMITIVE_TYPES;

        // 遍歷所有光源, 將和格子相交的光源寫入到串列中.
        LOOP
        for (uint LocalLightIndex = 0; LocalLightIndex < ForwardLightData.NumLocalLights; LocalLightIndex++)
        {
            uint LocalLightBaseIndex = LocalLightIndex * LOCAL_LIGHT_DATA_STRIDE;

#if ENABLE_LIGHT_CULLING_VIEW_SPACE_BUILD_DATA
            float4 LightPositionAndRadius = LightViewSpacePositionAndRadius[LocalLightIndex];
            float3 ViewSpaceLightPosition = LightPositionAndRadius.xyz;
            float LightRadius = LightPositionAndRadius.w;
            float4 LightPositionAndInvRadius = ForwardLightData.ForwardLocalLightBuffer[LocalLightBaseIndex + 0];
#else // !ENABLE_LIGHT_CULLING_VIEW_SPACE_BUILD_DATA
            float4 LightPositionAndInvRadius = ForwardLightData.ForwardLocalLightBuffer[LocalLightBaseIndex + 0];
            float LightRadius = 1.0f / LightPositionAndInvRadius.w;

            float3 ViewSpaceLightPosition = mul(float4(LightPositionAndInvRadius.xyz + View.PreViewTranslation.xyz, 1), View.TranslatedWorldToView).xyz;
#endif // ENABLE_LIGHT_CULLING_VIEW_SPACE_BUILD_DATA

            float BoxDistanceSq = ComputeSquaredDistanceFromBoxToPoint(ViewTileCenter, ViewTileExtent, ViewSpaceLightPosition);

            if (BoxDistanceSq < LightRadius * LightRadius)
            {
#if REFINE_SPOTLIGHT_BOUNDS
                bool bPassSpotlightTest = true;
                {
                    float4 ViewSpaceDirAndPreprocAngle = LightViewSpaceDirAndPreprocAngle[LocalLightIndex];
                    float TanConeAngle = ViewSpaceDirAndPreprocAngle.w;

                    // Set to 0 for non-acute cones, or non-spot lights.
                    if (TanConeAngle > 0.0f)
                    {
                        float3 ViewSpaceLightDirection = -ViewSpaceDirAndPreprocAngle.xyz;
                        // 光源圓錐體和格子的AABB相交測驗.
                        bPassSpotlightTest = !IsAabbOutsideInfiniteAcuteConeApprox(ViewSpaceLightPosition, ViewSpaceLightDirection, TanConeAngle, ViewTileCenter, ViewTileExtent);
                    }
                }
                // 如果相交, 寫入光源資訊.
                if (bPassSpotlightTest)
#endif // REFINE_SPOTLIGHT_BOUNDS
                {
#if USE_LINKED_CULL_LIST
                    uint NextLink;
                    InterlockedAdd(RWNextCulledLightLink[0], 1U, NextLink);

                    // 保存光源鏈接.
                    if (NextLink < NumAvailableLinks)
                    {
                        uint PreviousLink;
                        InterlockedExchange(RWStartOffsetGrid[GridIndex], NextLink, PreviousLink);
                        RWCulledLightLinks[NextLink * LIGHT_LINK_STRIDE + 0] = LocalLightIndex;
                        RWCulledLightLinks[NextLink * LIGHT_LINK_STRIDE + 1] = PreviousLink;
                    }

#else
                    uint CulledLightIndex;
                    InterlockedAdd(RWNumCulledLightsGrid[GridIndex * NUM_CULLED_LIGHTS_GRID_STRIDE + 0], 1U, CulledLightIndex);
                    RWNumCulledLightsGrid[GridIndex * NUM_CULLED_LIGHTS_GRID_STRIDE + 1] = GridIndex * ForwardLightData.MaxCulledLightsPerCell;

                    // 保存光源索引.
                    if (CulledLightIndex < ForwardLightData.MaxCulledLightsPerCell)
                    {
                        RWCulledLightDataGrid[GridIndex * ForwardLightData.MaxCulledLightsPerCell + CulledLightIndex] = LocalLightIndex;
                    }
#endif
                }
            }
        }
        
        // 處理反射捕捉器.
        LOOP
        for (uint ReflectionCaptureIndex = 0; ReflectionCaptureIndex < ForwardLightData.NumReflectionCaptures; ReflectionCaptureIndex++)
        {
            float4 CapturePositionAndRadius = ReflectionCapture.PositionAndRadius[ReflectionCaptureIndex];
            float3 ViewSpaceCapturePosition = mul(float4(CapturePositionAndRadius.xyz + View.PreViewTranslation.xyz, 1), View.TranslatedWorldToView).xyz;

            float BoxDistanceSq = ComputeSquaredDistanceFromBoxToPoint(ViewTileCenter, ViewTileExtent, ViewSpaceCapturePosition);

            if (BoxDistanceSq < CapturePositionAndRadius.w * CapturePositionAndRadius.w)
            {
                #if USE_LINKED_CULL_LIST
                    uint NextLink;
                    InterlockedAdd(RWNextCulledLightLink[0], 1U, NextLink);

                    if (NextLink < NumAvailableLinks)
                    {
                        uint PreviousLink;
                        InterlockedExchange(RWStartOffsetGrid[ForwardLightData.NumGridCells + GridIndex], NextLink, PreviousLink);
                        RWCulledLightLinks[NextLink * LIGHT_LINK_STRIDE + 0] = ReflectionCaptureIndex;
                        RWCulledLightLinks[NextLink * LIGHT_LINK_STRIDE + 1] = PreviousLink;
                    }

                #else
                    uint CulledCaptureIndex;
                    InterlockedAdd(RWNumCulledLightsGrid[(ForwardLightData.NumGridCells + GridIndex) * NUM_CULLED_LIGHTS_GRID_STRIDE + 0], 1U, CulledCaptureIndex);
                    RWNumCulledLightsGrid[(ForwardLightData.NumGridCells + GridIndex) * NUM_CULLED_LIGHTS_GRID_STRIDE + 1] = (ForwardLightData.NumGridCells + GridIndex) * ForwardLightData.MaxCulledLightsPerCell;

                    if (CulledCaptureIndex < ForwardLightData.MaxCulledLightsPerCell)
                    {
                        RWCulledLightDataGrid[(ForwardLightData.NumGridCells + GridIndex) * ForwardLightData.MaxCulledLightsPerCell + CulledCaptureIndex] = ReflectionCaptureIndex;
                    }
                #endif
            }
        }
#else

        LOOP
        for (uint LocalLightIndex = 0; LocalLightIndex < ForwardLightData.NumLocalLights; LocalLightIndex++)
        {
            if (LocalLightIndex < ForwardLightData.MaxCulledLightsPerCell)
            {
                RWCulledLightDataGrid[GridIndex * ForwardLightData.MaxCulledLightsPerCell + LocalLightIndex] = LocalLightIndex;
            }
        }

        RWNumCulledLightsGrid[GridIndex * NUM_CULLED_LIGHTS_GRID_STRIDE + 0] = ForwardLightData.NumLocalLights;
        RWNumCulledLightsGrid[GridIndex * NUM_CULLED_LIGHTS_GRID_STRIDE + 1] = GridIndex * ForwardLightData.MaxCulledLightsPerCell;
        
        LOOP
        for (uint ReflectionCaptureIndex = 0; ReflectionCaptureIndex < ForwardLightData.NumReflectionCaptures; ReflectionCaptureIndex++)
        {
            if (ReflectionCaptureIndex < ForwardLightData.MaxCulledLightsPerCell)
            {
                RWCulledLightDataGrid[(ForwardLightData.NumGridCells + GridIndex) * ForwardLightData.MaxCulledLightsPerCell + ReflectionCaptureIndex] = ReflectionCaptureIndex;
            }
        }

        RWNumCulledLightsGrid[(ForwardLightData.NumGridCells + GridIndex) * NUM_CULLED_LIGHTS_GRID_STRIDE + 0] = ForwardLightData.NumReflectionCaptures;
        RWNumCulledLightsGrid[(ForwardLightData.NumGridCells + GridIndex) * NUM_CULLED_LIGHTS_GRID_STRIDE + 1] = (ForwardLightData.NumGridCells + GridIndex) * ForwardLightData.MaxCulledLightsPerCell;
#endif
    }
}

RWBuffer<uint> RWNextCulledLightData;
Buffer<uint> StartOffsetGrid;
Buffer<uint> CulledLightLinks;

// 壓縮指定的反向鏈接串列.
void CompactReverseLinkedList(uint GridIndex, uint SceneMax)
{
    uint NumCulledLights = 0;
    uint StartLinkOffset = StartOffsetGrid[GridIndex];
    uint LinkOffset = StartLinkOffset;

    // Traverse the linked list to count how many culled indices we have
    while (LinkOffset != 0xFFFFFFFF && NumCulledLights < SceneMax)
    {
        NumCulledLights++;
        LinkOffset = CulledLightLinks[LinkOffset * LIGHT_LINK_STRIDE + 1];
    }

    uint CulledLightDataStart;
    InterlockedAdd(RWNextCulledLightData[0], NumCulledLights, CulledLightDataStart);
    RWNumCulledLightsGrid[GridIndex * NUM_CULLED_LIGHTS_GRID_STRIDE + 0] = NumCulledLights;
    RWNumCulledLightsGrid[GridIndex * NUM_CULLED_LIGHTS_GRID_STRIDE + 1] = CulledLightDataStart;

    LinkOffset = StartLinkOffset;
    uint CulledLightIndex = 0;

    while (LinkOffset != 0xFFFFFFFF && CulledLightIndex < NumCulledLights)
    {
        // Reverse the order as we write them out, which restores the original order before the reverse linked list was built
        // Reflection captures are order dependent
        RWCulledLightDataGrid[CulledLightDataStart + NumCulledLights - CulledLightIndex - 1] = CulledLightLinks[LinkOffset * LIGHT_LINK_STRIDE + 0];
        CulledLightIndex++;
        LinkOffset = CulledLightLinks[LinkOffset * LIGHT_LINK_STRIDE + 1];
    }
}

// 光源格子壓縮主入口.
[numthreads(THREADGROUP_SIZE, THREADGROUP_SIZE, THREADGROUP_SIZE)]
void LightGridCompactCS(
    uint3 GroupId : SV_GroupID,
    uint3 DispatchThreadId : SV_DispatchThreadID,
    uint3 GroupThreadId : SV_GroupThreadID)
{
    uint3 GridCoordinate = DispatchThreadId;

    if (all(GridCoordinate < ForwardLightData.CulledGridSize))
    {
        uint GridIndex = (GridCoordinate.z * ForwardLightData.CulledGridSize.y + GridCoordinate.y) * ForwardLightData.CulledGridSize.x + GridCoordinate.x;

        // 壓縮光源
        CompactReverseLinkedList(GridIndex, ForwardLightData.NumLocalLights);

        // 壓縮反射捕捉器.
        CompactReverseLinkedList(ForwardLightData.NumGridCells + GridIndex, ForwardLightData.NumReflectionCaptures);
    }
}

在格子中存盤光源有點類似上一篇中提及的Volume Tiled Forward Rendering:

Volume Light Grid資料結構示意圖,

 

5.5 LightingPass

本節主要詳細闡述LightingPass的渲染流程、渲染狀態、Shader邏輯等,

5.5.1 LightingPass渲染流程

上一篇也涉及到LightingPass的渲染流程和部分核心邏輯,本小節簡單回顧一下,LightingPass在FDeferredShadingSceneRenderer::Render是在BasePass之后:

void FDeferredShadingSceneRenderer::Render(FRHICommandListImmediate& RHICmdList)
{
    (......)
    
    // 渲染Base Pass.
    RenderBasePass(RHICmdList, ...);

    (......)
    
    // 渲染光源.
    RenderLights(RHICmdList, ...);
    
    (......)
}

下面再次簡單回顧RenderLightsRenderLight的渲染邏輯:

// Engine\Source\Runtime\Renderer\Private\LightRendering.cpp

// 渲染所有光源.
void FDeferredShadingSceneRenderer::RenderLights(FRHICommandListImmediate& RHICmdList, FSortedLightSetSceneInfo &SortedLightSet, const FHairStrandsDatas* HairDatas)
{
    (......)
    
    bool bStencilBufferDirty = false; 

    const FSimpleLightArray &SimpleLights = SortedLightSet.SimpleLights;
    const TArray<FSortedLightSceneInfo, SceneRenderingAllocator> &SortedLights = SortedLightSet.SortedLights;
    // 帶陰影的光源起始索引.
    const int32 AttenuationLightStart = SortedLightSet.AttenuationLightStart;
    const int32 SimpleLightsEnd = SortedLightSet.SimpleLightsEnd;

    // 直接光照
    {
        SCOPED_DRAW_EVENT(RHICmdList, DirectLighting);

        FSceneRenderTargets& SceneContext = FSceneRenderTargets::Get(RHICmdList);

        (......)

        // 無陰影光照
        if(ViewFamily.EngineShowFlags.DirectLighting)
        {
            SCOPED_DRAW_EVENT(RHICmdList, NonShadowedLights);

            // 無陰影的光源起始索引.
            int32 StandardDeferredStart = SortedLightSet.SimpleLightsEnd;
            bool bRenderSimpleLightsStandardDeferred = SortedLightSet.SimpleLights.InstanceData.Num() > 0;
            
            // 分簇延遲光照.
            if (ShouldUseClusteredDeferredShading() && AreClusteredLightsInLightGrid())
            {
                StandardDeferredStart = SortedLightSet.ClusteredSupportedEnd;
                bRenderSimpleLightsStandardDeferred = false;
                // 增加分簇延遲渲染Pass.
                AddClusteredDeferredShadingPass(RHICmdList, SortedLightSet);
            }
            // 分塊延遲光照.
            else if (CanUseTiledDeferred())
            {
                (......)

                if (ShouldUseTiledDeferred(SortedLightSet.TiledSupportedEnd) && !bAnyViewIsStereo)
                {
                    StandardDeferredStart = SortedLightSet.TiledSupportedEnd;
                    bRenderSimpleLightsStandardDeferred = false;
                    // 渲染分塊延遲光照.
                    RenderTiledDeferredLighting(RHICmdList, SortedLights, SortedLightSet.SimpleLightsEnd, SortedLightSet.TiledSupportedEnd, SimpleLights);
                }
            }

            // 簡單光照.
            if (bRenderSimpleLightsStandardDeferred)
            {
                SceneContext.BeginRenderingSceneColor(RHICmdList, ESimpleRenderTargetMode::EExistingColorAndDepth, FExclusiveDepthStencil::DepthRead_StencilWrite);
                // 渲染簡單光照.
                RenderSimpleLightsStandardDeferred(RHICmdList, SortedLightSet.SimpleLights);
                SceneContext.FinishRenderingSceneColor(RHICmdList);
            }

            // 標準延遲光照.
            if (!bUseHairLighting)
            {
                SCOPED_DRAW_EVENT(RHICmdList, StandardDeferredLighting);

                SceneContext.BeginRenderingSceneColor(RHICmdList, ESimpleRenderTargetMode::EExistingColorAndDepth, FExclusiveDepthStencil::DepthRead_StencilWrite, true);

                for (int32 LightIndex = StandardDeferredStart; LightIndex < AttenuationLightStart; LightIndex++)
                {
                    const FSortedLightSceneInfo& SortedLightInfo = SortedLights[LightIndex];
                    const FLightSceneInfo* const LightSceneInfo = SortedLightInfo.LightSceneInfo;

                    // 渲染無陰影光照.
                    RenderLight(RHICmdList, LightSceneInfo, nullptr, nullptr, false, false);
                }

                SceneContext.FinishRenderingSceneColor(RHICmdList);
            }
            
            (......)
        }

        (......)

        // 帶陰影的光照
        {
            SCOPED_DRAW_EVENT(RHICmdList, ShadowedLights);

            const int32 DenoiserMode = CVarShadowUseDenoiser.GetValueOnRenderThread();

            const IScreenSpaceDenoiser* DefaultDenoiser = IScreenSpaceDenoiser::GetDefaultDenoiser();
            const IScreenSpaceDenoiser* DenoiserToUse = DenoiserMode == 1 ? DefaultDenoiser : GScreenSpaceDenoiser;

            TArray<TRefCountPtr<IPooledRenderTarget>> PreprocessedShadowMaskTextures;
            TArray<TRefCountPtr<IPooledRenderTarget>> PreprocessedShadowMaskSubPixelTextures;

            const int32 MaxDenoisingBatchSize = FMath::Clamp(CVarMaxShadowDenoisingBatchSize.GetValueOnRenderThread(), 1, IScreenSpaceDenoiser::kMaxBatchSize);
            const int32 MaxRTShadowBatchSize = CVarMaxShadowRayTracingBatchSize.GetValueOnRenderThread();
            const bool bDoShadowDenoisingBatching = DenoiserMode != 0 && MaxDenoisingBatchSize > 1;

            (......)

            bool bDirectLighting = ViewFamily.EngineShowFlags.DirectLighting;
            bool bShadowMaskReadable = false;
            TRefCountPtr<IPooledRenderTarget> ScreenShadowMaskTexture;
            TRefCountPtr<IPooledRenderTarget> ScreenShadowMaskSubPixelTexture;

            // 渲染帶陰影的光源和光照函式光源.
            for (int32 LightIndex = AttenuationLightStart; LightIndex < SortedLights.Num(); LightIndex++)
            {
                const FSortedLightSceneInfo& SortedLightInfo = SortedLights[LightIndex];
                const FLightSceneInfo& LightSceneInfo = *SortedLightInfo.LightSceneInfo;

                const bool bDrawShadows = SortedLightInfo.SortKey.Fields.bShadowed && !ShouldRenderRayTracingStochasticRectLight(LightSceneInfo);
                bool bDrawLightFunction = SortedLightInfo.SortKey.Fields.bLightFunction;
                bool bDrawPreviewIndicator = ViewFamily.EngineShowFlags.PreviewShadowsIndicator && !LightSceneInfo.IsPrecomputedLightingValid() && LightSceneInfo.Proxy->HasStaticShadowing();
                bool bInjectedTranslucentVolume = false;
                bool bUsedShadowMaskTexture = false;
                const bool bDrawHairShadow = bDrawShadows && bUseHairLighting;
                const bool bUseHairDeepShadow = bDrawShadows && bUseHairLighting && LightSceneInfo.Proxy->CastsHairStrandsDeepShadow();

                FScopeCycleCounter Context(LightSceneInfo.Proxy->GetStatId());

                if ((bDrawShadows || bDrawLightFunction || bDrawPreviewIndicator) && !ScreenShadowMaskTexture.IsValid())
                {
                    SceneContext.AllocateScreenShadowMask(RHICmdList, ScreenShadowMaskTexture);
                    bShadowMaskReadable = false;
                    if (bUseHairLighting)
                    {
                        SceneContext.AllocateScreenShadowMask(RHICmdList, ScreenShadowMaskSubPixelTexture, true);
                    }
                }

                FString LightNameWithLevel;
                GetLightNameForDrawEvent(LightSceneInfo.Proxy, LightNameWithLevel);
                SCOPED_DRAW_EVENTF(RHICmdList, EventLightPass, *LightNameWithLevel);

                if (bDrawShadows)
                {
                    INC_DWORD_STAT(STAT_NumShadowedLights);

                    const FLightOcclusionType OcclusionType = GetLightOcclusionType(*LightSceneInfo.Proxy);

                    (......)
                    
                    // 處理陰影遮蔽圖.
                    else // (OcclusionType == FOcclusionType::Shadowmap)
                    {
                        for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
                        {
                            const FViewInfo& View = Views[ViewIndex];
                            View.HeightfieldLightingViewInfo.ClearShadowing(View, RHICmdList, LightSceneInfo);
                        }
                    
                        // 清理陰影遮蔽圖.
                        auto ClearShadowMask = [&](TRefCountPtr<IPooledRenderTarget>& InScreenShadowMaskTexture)
                        {
                            const bool bClearLightScreenExtentsOnly = CVarAllowClearLightSceneExtentsOnly.GetValueOnRenderThread() && SortedLightInfo.SortKey.Fields.LightType != LightType_Directional;
                            bool bClearToWhite = !bClearLightScreenExtentsOnly;

                            FRHIRenderPassInfo RPInfo(InScreenShadowMaskTexture->GetRenderTargetItem().TargetableTexture, ERenderTargetActions::Load_Store);
                            RPInfo.DepthStencilRenderTarget.Action = MakeDepthStencilTargetActions(ERenderTargetActions::Load_DontStore, ERenderTargetActions::Load_Store);
                            RPInfo.DepthStencilRenderTarget.DepthStencilTarget = SceneContext.GetSceneDepthSurface();
                            RPInfo.DepthStencilRenderTarget.ExclusiveDepthStencil = FExclusiveDepthStencil::DepthRead_StencilWrite;
                            if (bClearToWhite)
                            {
                                RPInfo.ColorRenderTargets[0].Action = ERenderTargetActions::Clear_Store;
                            }

                            TransitionRenderPassTargets(RHICmdList, RPInfo);
                            RHICmdList.BeginRenderPass(RPInfo, TEXT("ClearScreenShadowMask"));
                            if (bClearLightScreenExtentsOnly)
                            {
                                SCOPED_DRAW_EVENT(RHICmdList, ClearQuad);
    
                                for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
                                {
                                    const FViewInfo& View = Views[ViewIndex];
                                    FIntRect ScissorRect;

                                    if (!LightSceneInfo.Proxy->GetScissorRect(ScissorRect, View, View.ViewRect))
                                    {
                                        ScissorRect = View.ViewRect;
                                    }

                                    if (ScissorRect.Min.X < ScissorRect.Max.X && ScissorRect.Min.Y < ScissorRect.Max.Y)
                                    {
                                        RHICmdList.SetViewport(ScissorRect.Min.X, ScissorRect.Min.Y, 0.0f, ScissorRect.Max.X, ScissorRect.Max.Y, 1.0f);
                                        DrawClearQuad(RHICmdList, true, FLinearColor(1, 1, 1, 1), false, 0, false, 0);
                                    }
                                    else
                                    {
                                        LightSceneInfo.Proxy->GetScissorRect(ScissorRect, View, View.ViewRect);
                                    }
                                }
                            }
                            RHICmdList.EndRenderPass();
                        };

                        ClearShadowMask(ScreenShadowMaskTexture);
                        if (ScreenShadowMaskSubPixelTexture)
                        {
                            ClearShadowMask(ScreenShadowMaskSubPixelTexture);
                        }

                        RenderShadowProjections(RHICmdList, &LightSceneInfo, ScreenShadowMaskTexture, ScreenShadowMaskSubPixelTexture, HairDatas, bInjectedTranslucentVolume);
                    }

                    bUsedShadowMaskTexture = true;
                }

                for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
                {
                    const FViewInfo& View = Views[ViewIndex];
                    View.HeightfieldLightingViewInfo.ComputeLighting(View, RHICmdList, LightSceneInfo);
                }

                // 處理光照函式(light function).
                if (bDirectLighting)
                {
                    if (bDrawLightFunction)
                    {
                        const bool bLightFunctionRendered = RenderLightFunction(RHICmdList, &LightSceneInfo, ScreenShadowMaskTexture, bDrawShadows, false);
                        bUsedShadowMaskTexture |= bLightFunctionRendered;
                    }
                    
                    (......)
                }

                if (bUsedShadowMaskTexture)
                {
                    check(ScreenShadowMaskTexture);
                    RHICmdList.CopyToResolveTarget(ScreenShadowMaskTexture->GetRenderTargetItem().TargetableTexture, ScreenShadowMaskTexture->GetRenderTargetItem().ShaderResourceTexture, FResolveParams(FResolveRect()));
                    if (ScreenShadowMaskSubPixelTexture)
                    {
                        RHICmdList.CopyToResolveTarget(ScreenShadowMaskSubPixelTexture->GetRenderTargetItem().TargetableTexture, ScreenShadowMaskSubPixelTexture->GetRenderTargetItem().ShaderResourceTexture, FResolveParams(FResolveRect()));
                    }

                    if (!bShadowMaskReadable)
                    {
                        RHICmdList.TransitionResource(EResourceTransitionAccess::EReadable, ScreenShadowMaskTexture->GetRenderTargetItem().ShaderResourceTexture);
                        if (ScreenShadowMaskSubPixelTexture)
                        {
                            RHICmdList.TransitionResource(EResourceTransitionAccess::EReadable, ScreenShadowMaskSubPixelTexture->GetRenderTargetItem().ShaderResourceTexture);
                        }
                        bShadowMaskReadable = true;
                    }

                    GVisualizeTexture.SetCheckPoint(RHICmdList, ScreenShadowMaskTexture);
                    if (ScreenShadowMaskSubPixelTexture)
                    {
                        GVisualizeTexture.SetCheckPoint(RHICmdList, ScreenShadowMaskSubPixelTexture);
                    }
                }

                (......)
                
                // 渲染標準延遲光照.
                {
                    SCOPED_DRAW_EVENT(RHICmdList, StandardDeferredLighting);
                    SceneContext.BeginRenderingSceneColor(RHICmdList, ESimpleRenderTargetMode::EExistingColorAndDepth, FExclusiveDepthStencil::DepthRead_StencilWrite, true);

                    IPooledRenderTarget* LightShadowMaskTexture = nullptr;
                    IPooledRenderTarget* LightShadowMaskSubPixelTexture = nullptr;
                    if (bUsedShadowMaskTexture)
                    {
                        LightShadowMaskTexture = ScreenShadowMaskTexture;
                        LightShadowMaskSubPixelTexture = ScreenShadowMaskSubPixelTexture;
                    }

                    if (bDirectLighting)
                    {
                        // 渲染帶陰影的光源.
                        RenderLight(RHICmdList, &LightSceneInfo, LightShadowMaskTexture, InHairVisibilityViews, false, true);
                    }

                    SceneContext.FinishRenderingSceneColor(RHICmdList);

                    (......)
                }
            }
        }
    }
}

// 渲染單個光源.
void FDeferredShadingSceneRenderer::RenderLight(FRHICommandList& RHICmdList, const FLightSceneInfo* LightSceneInfo, IPooledRenderTarget* ScreenShadowMaskTexture, const FHairStrandsVisibilityViews* InHairVisibilityViews,  bool bRenderOverlap, bool bIssueDrawEvent)
{
    FGraphicsPipelineStateInitializer GraphicsPSOInit;
    RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit);

    // 設定混合狀態為疊加, 以便將所有光源的光照強度疊加到同一張紋理中.
    GraphicsPSOInit.BlendState = TStaticBlendState<CW_RGBA, BO_Add, BF_One, BF_One, BO_Add, BF_One, BF_One>::GetRHI();

    GraphicsPSOInit.PrimitiveType = PT_TriangleList;

    const FSphere LightBounds = LightSceneInfo->Proxy->GetBoundingSphere();
    const bool bTransmission = LightSceneInfo->Proxy->Transmission();

    // 遍歷所有view, 將此光源給每個view都繪制一次.
    for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
    {
        FViewInfo& View = Views[ViewIndex];

        // 確保光源在此視圖中有效.
        if (!LightSceneInfo->ShouldRenderLight(View))
        {
            continue;
        }

        bool bUseIESTexture = false;

        if(View.Family->EngineShowFlags.TexturedLightProfiles)
        {
            bUseIESTexture = (LightSceneInfo->Proxy->GetIESTextureResource() != 0);
        }

        RHICmdList.SetViewport(View.ViewRect.Min.X, View.ViewRect.Min.Y, 0.0f, View.ViewRect.Max.X, View.ViewRect.Max.Y, 1.0f);

        (......)
        
        // 繪制*行方向光.
        if (LightSceneInfo->Proxy->GetLightType() == LightType_Directional)
        {
            GraphicsPSOInit.bDepthBounds = false;
            TShaderMapRef<TDeferredLightVS<false> > VertexShader(View.ShaderMap);

            GraphicsPSOInit.RasterizerState = TStaticRasterizerState<FM_Solid, CM_None>::GetRHI();
            GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState<false, CF_Always>::GetRHI();

            // 設定延遲光源的shader和pso引數.
            if (bRenderOverlap)
            {
                TShaderMapRef<TDeferredLightOverlapPS<false> > PixelShader(View.ShaderMap);
                GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GFilterVertexDeclaration.VertexDeclarationRHI;
                GraphicsPSOInit.BoundShaderState.VertexShaderRHI = VertexShader.GetVertexShader();
                GraphicsPSOInit.BoundShaderState.PixelShaderRHI = PixelShader.GetPixelShader();
                SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit);
                PixelShader->SetParameters(RHICmdList, View, LightSceneInfo);
            }
            else
            {
                const bool bAtmospherePerPixelTransmittance = LightSceneInfo->Proxy->IsUsedAsAtmosphereSunLight() && ShouldApplyAtmosphereLightPerPixelTransmittance(Scene, View.Family->EngineShowFlags);

                FDeferredLightPS::FPermutationDomain PermutationVector;
                PermutationVector.Set< FDeferredLightPS::FSourceShapeDim >( ELightSourceShape::Directional );
                PermutationVector.Set< FDeferredLightPS::FIESProfileDim >( false );
                PermutationVector.Set< FDeferredLightPS::FInverseSquaredDim >( false );
                PermutationVector.Set< FDeferredLightPS::FVisualizeCullingDim >( View.Family->EngineShowFlags.VisualizeLightCulling );
                PermutationVector.Set< FDeferredLightPS::FLightingChannelsDim >( View.bUsesLightingChannels );
                PermutationVector.Set< FDeferredLightPS::FTransmissionDim >( bTransmission );
                PermutationVector.Set< FDeferredLightPS::FHairLighting>(bHairLighting ? 1 : 0);
                PermutationVector.Set< FDeferredLightPS::FAtmosphereTransmittance >(bAtmospherePerPixelTransmittance);

                TShaderMapRef< FDeferredLightPS > PixelShader( View.ShaderMap, PermutationVector );
                GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GFilterVertexDeclaration.VertexDeclarationRHI;
                GraphicsPSOInit.BoundShaderState.VertexShaderRHI = VertexShader.GetVertexShader();
                GraphicsPSOInit.BoundShaderState.PixelShaderRHI = PixelShader.GetPixelShader();

                SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit);
                PixelShader->SetParameters(RHICmdList, View, LightSceneInfo, ScreenShadowMaskTexture, (bHairLighting) ? &RenderLightParams : nullptr);
            }

            VertexShader->SetParameters(RHICmdList, View, LightSceneInfo);

            // 由于是*行光, 將會覆寫螢屏空間的所有區域, 故而使用全屏范圍的矩形來繪制.
            DrawRectangle(
                RHICmdList,
                0, 0,
                View.ViewRect.Width(), View.ViewRect.Height(),
                View.ViewRect.Min.X, View.ViewRect.Min.Y,
                View.ViewRect.Width(), View.ViewRect.Height(),
                View.ViewRect.Size(),
                FSceneRenderTargets::Get(RHICmdList).GetBufferSizeXY(),
                VertexShader,
                EDRF_UseTriangleOptimization);
        }
        else // 區域光源
        {
            // 是否開啟深度包圍盒測驗(DBT).
            GraphicsPSOInit.bDepthBounds = GSupportsDepthBoundsTest && GAllowDepthBoundsTest != 0;

            TShaderMapRef<TDeferredLightVS<true> > VertexShader(View.ShaderMap);

            SetBoundingGeometryRasterizerAndDepthState(GraphicsPSOInit, View, LightBounds);

            // 設定延遲光源的shader和pso引數.
            if (bRenderOverlap)
            {
                TShaderMapRef<TDeferredLightOverlapPS<true> > PixelShader(View.ShaderMap);
                GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GetVertexDeclarationFVector4();
                GraphicsPSOInit.BoundShaderState.VertexShaderRHI = VertexShader.GetVertexShader();
                GraphicsPSOInit.BoundShaderState.PixelShaderRHI = PixelShader.GetPixelShader();

                SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit);
                PixelShader->SetParameters(RHICmdList, View, LightSceneInfo);
            }
            else
            {
                FDeferredLightPS::FPermutationDomain PermutationVector;
                PermutationVector.Set< FDeferredLightPS::FSourceShapeDim >( LightSceneInfo->Proxy->IsRectLight() ? ELightSourceShape::Rect : ELightSourceShape::Capsule );
                PermutationVector.Set< FDeferredLightPS::FSourceTextureDim >( LightSceneInfo->Proxy->IsRectLight() && LightSceneInfo->Proxy->HasSourceTexture() );
                PermutationVector.Set< FDeferredLightPS::FIESProfileDim >( bUseIESTexture );
                PermutationVector.Set< FDeferredLightPS::FInverseSquaredDim >( LightSceneInfo->Proxy->IsInverseSquared() );
                PermutationVector.Set< FDeferredLightPS::FVisualizeCullingDim >( View.Family->EngineShowFlags.VisualizeLightCulling );
                PermutationVector.Set< FDeferredLightPS::FLightingChannelsDim >( View.bUsesLightingChannels );
                PermutationVector.Set< FDeferredLightPS::FTransmissionDim >( bTransmission );
                PermutationVector.Set< FDeferredLightPS::FHairLighting>(bHairLighting ? 1 : 0);
                PermutationVector.Set < FDeferredLightPS::FAtmosphereTransmittance >(false);

                TShaderMapRef< FDeferredLightPS > PixelShader( View.ShaderMap, PermutationVector );
                GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GetVertexDeclarationFVector4();
                GraphicsPSOInit.BoundShaderState.VertexShaderRHI = VertexShader.GetVertexShader();
                GraphicsPSOInit.BoundShaderState.PixelShaderRHI = PixelShader.GetPixelShader();

                SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit);
                PixelShader->SetParameters(RHICmdList, View, LightSceneInfo, ScreenShadowMaskTexture, (bHairLighting) ? &RenderLightParams : nullptr);
            }

            VertexShader->SetParameters(RHICmdList, View, LightSceneInfo);

            // 深度包圍盒測驗(DBT)只在帶陰影的光源中有效, 可以有效剔除在深度范圍之外的像素.
            if (GraphicsPSOInit.bDepthBounds)
            {
                // UE4使用了逆反的深度(reversed depth), 所以far<near.
                float NearDepth = 1.f;
                float FarDepth = 0.f;
                CalculateLightNearFarDepthFromBounds(View,LightBounds,NearDepth,FarDepth);

                if (NearDepth <= FarDepth)
                {
                    NearDepth = 1.0f;
                    FarDepth = 0.0f;
                }

                // 設定深度包圍盒引數.
                RHICmdList.SetDepthBounds(FarDepth, NearDepth);
            }

            // 點光源或區域光使用球體繪制.
            if( LightSceneInfo->Proxy->GetLightType() == LightType_Point ||
                LightSceneInfo->Proxy->GetLightType() == LightType_Rect )
            {
                StencilingGeometry::DrawSphere(RHICmdList);
            }
            // 聚光燈使用圓錐體繪制.
            else if (LightSceneInfo->Proxy->GetLightType() == LightType_Spot)
            {
                StencilingGeometry::DrawCone(RHICmdList);
            }
        }
    }
}

5.5.2 LightingPass渲染狀態

本節將闡述LightingPass渲染時使用的各類渲染狀態、Shader系結及繪制引數,

首先看看渲染單個光源時的混合狀態:

GraphicsPSOInit.BlendState = TStaticBlendState<CW_RGBA, BO_Add, BF_One, BF_One, BO_Add, BF_One, BF_One>::GetRHI();

意味著光源的顏色和Alpha都是疊加狀態,公式如下:

\[\begin{eqnarray*} \text{FinalColor} &=& 1\cdot \text{SourceColor} &+& 1\cdot \text{DestColor} &=& \text{SourceColor} &+& \text{DestColor} \\ \text{FinalAlpha} &=& 1\cdot \text{SourceAlpha} &+& 1\cdot \text{DestAlpha} &=& \text{SourceAlpha} &+& \text{DestAlpha} \end{eqnarray*} \]

這也符合物理常識,單個表面物體受到多個光源照射時,無論是顏色還是強度,都應該是疊加狀態,

其它渲染狀態如下表(*行光為例):

渲染狀態 描述
PrimitiveType PT_TriangleList 圖元型別是三角形,
bDepthBounds false 不同型別光源的深度邊界取值不一樣,
RasterizerState StaticRasterizerState<FM_Solid, CM_None> 物體渲染,關閉背面或正面裁剪,
DepthStencilState TStaticDepthStencilState<false, CF_Always> 關閉深度寫入,深度比較函式為總是通過測驗,
VertexShader TDeferredLightVS 非輻射光的頂點著色器,
PixelShader FDeferredLightPS 像素著色器,

下面剖析渲染光源時使用的VS和PS的C++代碼:

// Engine\Source\Runtime\Renderer\Private\LightRendering.h

// 頂點著色器.
template<bool bRadialLight>
class TDeferredLightVS : public FGlobalShader
{
    DECLARE_SHADER_TYPE(TDeferredLightVS,Global);
public:

    static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
    {
        return bRadialLight ? IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::SM5) : true;
    }

    static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
    {
        FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment);
        OutEnvironment.SetDefine(TEXT("SHADER_RADIAL_LIGHT"), bRadialLight ? 1 : 0);
    }

    TDeferredLightVS()    {}
    TDeferredLightVS(const ShaderMetaType::CompiledShaderInitializerType& Initializer):
        FGlobalShader(Initializer)
    {
        StencilingGeometryParameters.Bind(Initializer.ParameterMap);
    }

    // 設定引數.
    void SetParameters(FRHICommandList& RHICmdList, const FViewInfo& View, const FLightSceneInfo* LightSceneInfo)
    {
        // 設定視圖Uniform Buffer.
        FGlobalShader::SetParameters<FViewUniformShaderParameters>(RHICmdList, RHICmdList.GetBoundVertexShader(), View.ViewUniformBuffer);
        StencilingGeometryParameters.Set(RHICmdList, this, View, LightSceneInfo);
    }

    // 設定簡單光源引數.
    void SetSimpleLightParameters(FRHICommandList& RHICmdList, const FViewInfo& View, const FSphere& LightBounds)
    {
        FGlobalShader::SetParameters<FViewUniformShaderParameters>(RHICmdList, RHICmdList.GetBoundVertexShader(), View.ViewUniformBuffer);

        FVector4 StencilingSpherePosAndScale;
        StencilingGeometry::GStencilSphereVertexBuffer.CalcTransform(StencilingSpherePosAndScale, LightBounds, View.ViewMatrices.GetPreViewTranslation());
        StencilingGeometryParameters.Set(RHICmdList, this, StencilingSpherePosAndScale);
    }

private:

    LAYOUT_FIELD(FStencilingGeometryShaderParameters, StencilingGeometryParameters);
};

// Engine\Source\Runtime\Renderer\Private\LightRendering.cpp

// 光源的像素著色器.
class FDeferredLightPS : public FGlobalShader
{
    DECLARE_SHADER_TYPE(FDeferredLightPS, Global)

    class FSourceShapeDim        : SHADER_PERMUTATION_ENUM_CLASS("LIGHT_SOURCE_SHAPE", ELightSourceShape);
    class FSourceTextureDim        : SHADER_PERMUTATION_BOOL("USE_SOURCE_TEXTURE");
    class FIESProfileDim        : SHADER_PERMUTATION_BOOL("USE_IES_PROFILE");
    class FInverseSquaredDim    : SHADER_PERMUTATION_BOOL("INVERSE_SQUARED_FALLOFF");
    class FVisualizeCullingDim    : SHADER_PERMUTATION_BOOL("VISUALIZE_LIGHT_CULLING");
    class FLightingChannelsDim    : SHADER_PERMUTATION_BOOL("USE_LIGHTING_CHANNELS");
    class FTransmissionDim        : SHADER_PERMUTATION_BOOL("USE_TRANSMISSION");
    class FHairLighting            : SHADER_PERMUTATION_INT("USE_HAIR_LIGHTING", 3);
    class FAtmosphereTransmittance: SHADER_PERMUTATION_BOOL("USE_ATMOSPHERE_TRANSMITTANCE");

    using FPermutationDomain = TShaderPermutationDomain<
        FSourceShapeDim,
        FSourceTextureDim,
        FIESProfileDim,
        FInverseSquaredDim,
        FVisualizeCullingDim,
        FLightingChannelsDim,
        FTransmissionDim,
        FHairLighting,
        FAtmosphereTransmittance>;

    // 減少shader的組合.
    static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
    {
        FPermutationDomain PermutationVector(Parameters.PermutationId);

        if( PermutationVector.Get< FSourceShapeDim >() == ELightSourceShape::Directional && (
            PermutationVector.Get< FIESProfileDim >() ||
            PermutationVector.Get< FInverseSquaredDim >() ) )
        {
            return false;
        }

        if (PermutationVector.Get< FSourceShapeDim >() != ELightSourceShape::Directional && PermutationVector.Get<FAtmosphereTransmittance>())
        {
            return false;
        }

        if( PermutationVector.Get< FSourceShapeDim >() == ELightSourceShape::Rect )
        {
            if(    !PermutationVector.Get< FInverseSquaredDim >() )
            {
                return false;
            }
        }
        else
        {
            if( PermutationVector.Get< FSourceTextureDim >() )
            {
                return false;
            }
        }

        if (PermutationVector.Get<FHairLighting>() && !IsHairStrandsSupported(Parameters.Platform))
        {
            return false;
        }

        if (PermutationVector.Get< FHairLighting >() == 2 && (
            PermutationVector.Get< FVisualizeCullingDim >() ||
            PermutationVector.Get< FTransmissionDim >()))
        {
            return false;
        }

        return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::SM5);
    }

    FDeferredLightPS(const ShaderMetaType::CompiledShaderInitializerType& Initializer)
    :    FGlobalShader(Initializer)
    {
        // 系結shader引數.
        SceneTextureParameters.Bind(Initializer);
        LightAttenuationTexture.Bind(Initializer.ParameterMap, TEXT("LightAttenuationTexture"));
        LightAttenuationTextureSampler.Bind(Initializer.ParameterMap, TEXT("LightAttenuationTextureSampler"));
        LTCMatTexture.Bind(Initializer.ParameterMap, TEXT("LTCMatTexture"));
        LTCMatSampler.Bind(Initializer.ParameterMap, TEXT("LTCMatSampler"));
        LTCAmpTexture.Bind(Initializer.ParameterMap, TEXT("LTCAmpTexture"));
        LTCAmpSampler.Bind(Initializer.ParameterMap, TEXT("LTCAmpSampler"));
        IESTexture.Bind(Initializer.ParameterMap, TEXT("IESTexture"));
        IESTextureSampler.Bind(Initializer.ParameterMap, TEXT("IESTextureSampler"));
        LightingChannelsTexture.Bind(Initializer.ParameterMap, TEXT("LightingChannelsTexture"));
        LightingChannelsSampler.Bind(Initializer.ParameterMap, TEXT("LightingChannelsSampler"));
        TransmissionProfilesTexture.Bind(Initializer.ParameterMap, TEXT("SSProfilesTexture"));
        TransmissionProfilesLinearSampler.Bind(Initializer.ParameterMap, TEXT("TransmissionProfilesLinearSampler"));

        HairTransmittanceBuffer.Bind(Initializer.ParameterMap, TEXT("HairTransmittanceBuffer"));
        HairTransmittanceBufferMaxCount.Bind(Initializer.ParameterMap, TEXT("HairTransmittanceBufferMaxCount"));
        ScreenShadowMaskSubPixelTexture.Bind(Initializer.ParameterMap, TEXT("ScreenShadowMaskSubPixelTexture")); // TODO hook the shader itself

        HairLUTTexture.Bind(Initializer.ParameterMap, TEXT("HairLUTTexture"));
        HairLUTSampler.Bind(Initializer.ParameterMap, TEXT("HairLUTSampler"));
        HairComponents.Bind(Initializer.ParameterMap, TEXT("HairComponents"));
        HairShadowMaskValid.Bind(Initializer.ParameterMap, TEXT("HairShadowMaskValid"));
        HairDualScatteringRoughnessOverride.Bind(Initializer.ParameterMap, TEXT("HairDualScatteringRoughnessOverride"));

        HairCategorizationTexture.Bind(Initializer.ParameterMap, TEXT("HairCategorizationTexture"));
        HairVisibilityNodeOffsetAndCount.Bind(Initializer.ParameterMap, TEXT("HairVisibilityNodeOffsetAndCount"));
        HairVisibilityNodeCoords.Bind(Initializer.ParameterMap, TEXT("HairVisibilityNodeCoords"));
        HairVisibilityNodeData.Bind(Initializer.ParameterMap, TEXT("HairVisibilityNodeData"));
    }

    FDeferredLightPS()
    {}

public:
    // 設定光源引數.
    void SetParameters(
        FRHICommandList& RHICmdList, 
        const FSceneView& View, 
        const FLightSceneInfo* LightSceneInfo, 
        IPooledRenderTarget* ScreenShadowMaskTexture, 
        FRenderLightParams* RenderLightParams)
    {
        FRHIPixelShader* ShaderRHI = RHICmdList.GetBoundPixelShader();
        // 設定光源基礎引數.
        SetParametersBase(RHICmdList, ShaderRHI, View, ScreenShadowMaskTexture, LightSceneInfo->Proxy->GetIESTextureResource(), RenderLightParams);
        // 設定延遲光源的引數.
        SetDeferredLightParameters(RHICmdList, ShaderRHI, GetUniformBufferParameter<FDeferredLightUniformStruct>(), LightSceneInfo, View);
    }

    // 設定簡單光源引數.
    void SetParametersSimpleLight(FRHICommandList& RHICmdList, const FSceneView& View, const FSimpleLightEntry& SimpleLight, const FSimpleLightPerViewEntry& SimpleLightPerViewData)
    {
        FRHIPixelShader* ShaderRHI = RHICmdList.GetBoundPixelShader();
        SetParametersBase(RHICmdList, ShaderRHI, View, nullptr, nullptr, nullptr);
        SetSimpleDeferredLightParameters(RHICmdList, ShaderRHI, GetUniformBufferParameter<FDeferredLightUniformStruct>(), SimpleLight, SimpleLightPerViewData, View);
    }

private:
    void SetParametersBase(
        FRHICommandList& RHICmdList, 
        FRHIPixelShader* ShaderRHI, 
        const FSceneView& View, 
        IPooledRenderTarget* ScreenShadowMaskTexture, 
        FTexture* IESTextureResource, 
        FRenderLightParams* RenderLightParams)
    {
        FGlobalShader::SetParameters<FViewUniformShaderParameters>(RHICmdList, ShaderRHI,View.ViewUniformBuffer);
        SceneTextureParameters.Set(RHICmdList, ShaderRHI, View.FeatureLevel, ESceneTextureSetupMode::All);

        FSceneRenderTargets& SceneRenderTargets = FSceneRenderTargets::Get(RHICmdList);

        // 光源衰減圖.
        if(LightAttenuationTexture.IsBound())
        {
            SetTextureParameter(
                RHICmdList,
                ShaderRHI,
                LightAttenuationTexture,
                LightAttenuationTextureSampler,
                TStaticSamplerState<SF_Point,AM_Wrap,AM_Wrap,AM_Wrap>::GetRHI(),
                ScreenShadowMaskTexture ? ScreenShadowMaskTexture->GetRenderTargetItem().ShaderResourceTexture : GWhiteTexture->TextureRHI
                );
        }

        // 區域光LCT紋理.
        SetTextureParameter(
            RHICmdList,
            ShaderRHI,
            LTCMatTexture,
            LTCMatSampler,
            TStaticSamplerState<SF_Bilinear,AM_Clamp,AM_Clamp,AM_Clamp>::GetRHI(),
            GSystemTextures.LTCMat->GetRenderTargetItem().ShaderResourceTexture
            );

        SetTextureParameter(
            RHICmdList,
            ShaderRHI,
            LTCAmpTexture,
            LTCAmpSampler,
            TStaticSamplerState<SF_Bilinear,AM_Clamp,AM_Clamp,AM_Clamp>::GetRHI(),
            GSystemTextures.LTCAmp->GetRenderTargetItem().ShaderResourceTexture
            );

        {
            FRHITexture* TextureRHI = IESTextureResource ? IESTextureResource->TextureRHI : GSystemTextures.WhiteDummy->GetRenderTargetItem().TargetableTexture;

            SetTextureParameter(
                RHICmdList,
                ShaderRHI,
                IESTexture,
                IESTextureSampler,
                TStaticSamplerState<SF_Bilinear,AM_Clamp,AM_Clamp,AM_Clamp>::GetRHI(),
                TextureRHI
                );
        }

        // 燈光通道紋理.
        if( LightingChannelsTexture.IsBound() )
        {
            FRHITexture* LightingChannelsTextureRHI = SceneRenderTargets.LightingChannels ? SceneRenderTargets.LightingChannels->GetRenderTargetItem().ShaderResourceTexture : GSystemTextures.WhiteDummy->GetRenderTargetItem().TargetableTexture;

            SetTextureParameter(
                RHICmdList,
                ShaderRHI,
                LightingChannelsTexture,
                LightingChannelsSampler,
                TStaticSamplerState<SF_Point,AM_Clamp,AM_Clamp,AM_Clamp>::GetRHI(),
                LightingChannelsTextureRHI
                );
        }

        if( TransmissionProfilesTexture.IsBound() )
        {
            FSceneRenderTargets& SceneContext = FSceneRenderTargets::Get(RHICmdList);
            const IPooledRenderTarget* PooledRT = GetSubsufaceProfileTexture_RT((FRHICommandListImmediate&)RHICmdList);

            if (!PooledRT)
            {
                // no subsurface profile was used yet
                PooledRT = GSystemTextures.BlackDummy;
            }

            const FSceneRenderTargetItem& Item = PooledRT->GetRenderTargetItem();

            SetTextureParameter(RHICmdList,
                ShaderRHI,
                TransmissionProfilesTexture,
                TransmissionProfilesLinearSampler,
                TStaticSamplerState<SF_Bilinear, AM_Clamp, AM_Clamp, AM_Clamp>::GetRHI(),
                Item.ShaderResourceTexture);
        }

        if (HairTransmittanceBuffer.IsBound())
        {
            const uint32 TransmittanceBufferMaxCount = RenderLightParams ? RenderLightParams->DeepShadow_TransmittanceMaskBufferMaxCount : 0;
            SetShaderValue(
                RHICmdList,
                ShaderRHI,
                HairTransmittanceBufferMaxCount,
                TransmittanceBufferMaxCount);
            if (RenderLightParams && RenderLightParams->DeepShadow_TransmittanceMaskBuffer)
            {
                SetSRVParameter(RHICmdList, ShaderRHI, HairTransmittanceBuffer, RenderLightParams->DeepShadow_TransmittanceMaskBuffer);
            }
        }

        if (ScreenShadowMaskSubPixelTexture.IsBound())
        {
            if (RenderLightParams)
            {
                SetTextureParameter(
                    RHICmdList,
                    ShaderRHI,
                    ScreenShadowMaskSubPixelTexture,
                    LightAttenuationTextureSampler,
                    TStaticSamplerState<SF_Point, AM_Clamp, AM_Clamp, AM_Clamp>::GetRHI(),
                    (RenderLightParams && RenderLightParams->ScreenShadowMaskSubPixelTexture) ? RenderLightParams->ScreenShadowMaskSubPixelTexture->GetRenderTargetItem().ShaderResourceTexture : GWhiteTexture->TextureRHI);

                uint32 InHairShadowMaskValid = RenderLightParams->ScreenShadowMaskSubPixelTexture ? 1 : 0;
                SetShaderValue(
                    RHICmdList,
                    ShaderRHI,
                    HairShadowMaskValid,
                    InHairShadowMaskValid);
            }
        }

        if (HairCategorizationTexture.IsBound())
        {
            if (RenderLightParams && RenderLightParams->HairCategorizationTexture)
            {
                SetTextureParameter(
                    RHICmdList,
                    ShaderRHI,
                    HairCategorizationTexture,
                    LightAttenuationTextureSampler,
                    TStaticSamplerState<SF_Point, AM_Clamp, AM_Clamp, AM_Clamp>::GetRHI(),
                    RenderLightParams->HairCategorizationTexture->GetRenderTargetItem().TargetableTexture);
            }
        }

        if (HairVisibilityNodeOffsetAndCount.IsBound())
        {
            if (RenderLightParams && RenderLightParams->HairVisibilityNodeOffsetAndCount)
            {
                SetTextureParameter(
                    RHICmdList,
                    ShaderRHI,
                    HairVisibilityNodeOffsetAndCount,
                    LightAttenuationTextureSampler,
                    TStaticSamplerState<SF_Point, AM_Clamp, AM_Clamp, AM_Clamp>::GetRHI(),
                    RenderLightParams->HairVisibilityNodeOffsetAndCount->GetRenderTargetItem().TargetableTexture);
            }
        }
        
        if (HairVisibilityNodeCoords.IsBound())
        {
            if (RenderLightParams && RenderLightParams->HairVisibilityNodeCoordsSRV)
            {
                FShaderResourceViewRHIRef SRV = RenderLightParams->HairVisibilityNodeCoordsSRV;
                SetSRVParameter(
                    RHICmdList, 
                    ShaderRHI, 
                    HairVisibilityNodeCoords,
                    SRV);
            }
        }

        if (HairVisibilityNodeData.IsBound())
        {
            if (RenderLightParams && RenderLightParams->HairVisibilityNodeDataSRV)
            {
                FShaderResourceViewRHIRef SRV = RenderLightParams->HairVisibilityNodeDataSRV;
                SetSRVParameter(
                    RHICmdList, 
                    ShaderRHI, 
                    HairVisibilityNodeData, 
                    SRV);
            }
        }

        if (HairLUTTexture.IsBound())
        {
            IPooledRenderTarget* HairLUTTextureResource = GSystemTextures.HairLUT0;
            SetTextureParameter(
                RHICmdList,
                ShaderRHI,
                HairLUTTexture,
                HairLUTSampler,
                TStaticSamplerState<SF_Bilinear, AM_Clamp, AM_Clamp, AM_Clamp>::GetRHI(),
                HairLUTTextureResource ? HairLUTTextureResource->GetRenderTargetItem().ShaderResourceTexture : GBlackVolumeTexture->TextureRHI);
        }

        if (HairComponents.IsBound())
        {
            uint32 InHairComponents = ToBitfield(GetHairComponents());
            SetShaderValue(
                RHICmdList,
                ShaderRHI,
                HairComponents,
                InHairComponents);
        }

        if (HairDualScatteringRoughnessOverride.IsBound())
        {
            const float DualScatteringRoughness = GetHairDualScatteringRoughnessOverride();
            SetShaderValue(
                RHICmdList,
                ShaderRHI,
                HairDualScatteringRoughnessOverride,
                DualScatteringRoughness);
        }
        
    }

    LAYOUT_FIELD(FSceneTextureShaderParameters, SceneTextureParameters);
    LAYOUT_FIELD(FShaderResourceParameter, LightAttenuationTexture);
    LAYOUT_FIELD(FShaderResourceParameter, LightAttenuationTextureSampler);
    LAYOUT_FIELD(FShaderResourceParameter, LTCMatTexture);
    LAYOUT_FIELD(FShaderResourceParameter, LTCMatSampler);
    LAYOUT_FIELD(FShaderResourceParameter, LTCAmpTexture);
    LAYOUT_FIELD(FShaderResourceParameter, LTCAmpSampler);
    LAYOUT_FIELD(FShaderResourceParameter, IESTexture);
    LAYOUT_FIELD(FShaderResourceParameter, IESTextureSampler);
    LAYOUT_FIELD(FShaderResourceParameter, LightingChannelsTexture);
    LAYOUT_FIELD(FShaderResourceParameter, LightingChannelsSampler);
    LAYOUT_FIELD(FShaderResourceParameter, TransmissionProfilesTexture);
    LAYOUT_FIELD(FShaderResourceParameter, TransmissionProfilesLinearSampler);

    LAYOUT_FIELD(FShaderParameter, HairTransmittanceBufferMaxCount);
    LAYOUT_FIELD(FShaderResourceParameter, HairTransmittanceBuffer);
    LAYOUT_FIELD(FShaderResourceParameter, HairCategorizationTexture);
    LAYOUT_FIELD(FShaderResourceParameter, HairVisibilityNodeOffsetAndCount);
    LAYOUT_FIELD(FShaderResourceParameter, HairVisibilityNodeCoords);
    LAYOUT_FIELD(FShaderResourceParameter, HairVisibilityNodeData);
    LAYOUT_FIELD(FShaderResourceParameter, ScreenShadowMaskSubPixelTexture);

    LAYOUT_FIELD(FShaderResourceParameter, HairLUTTexture);
    LAYOUT_FIELD(FShaderResourceParameter, HairLUTSampler);
    LAYOUT_FIELD(FShaderParameter, HairComponents);
    LAYOUT_FIELD(FShaderParameter, HairShadowMaskValid);
    LAYOUT_FIELD(FShaderParameter, HairDualScatteringRoughnessOverride);
};

5.5.3 LightingPass Shader

上一篇文章中已經指出場景、光源、視圖和shader呼叫的嵌套關系:

foreach(scene in scenes)
{
    foreach(light in lights)
    {
        foreach(view in views)
        {
            RenderLight(); // 每次呼叫渲染光源就執行一遍DeferredLightVertexShaders和DeferredLightPixelShaders的代碼.
        }
    }
}

意味著DeferredLightVertexShader和DeferredLightPixelShader被執行的次數是:

\[N_{scene} \cdot N_{light} \cdot N_{view} \]

這也側面說明了對光源和陰影進行排序的必要性,可以減少CPU和GPU的交換資料,減少渲染狀態切換,提升Cache命中率,提升實體化概率,降低Draw Call,不過,對于實時游戲而言,多數情況下,場景和視圖數量都是1,也就是VS和PS的執行次數只與光源數量有關,

后面兩個小節將進入LightingPass的VS和PS的Shader邏輯進行剖析,

5.5.3.1 DeferredLightVertexShader

DeferredLightVertexShader的入口在DeferredLightVertexShaders.usf:

#include "Common.ush"

#if defined(SHADER_RADIAL_LIGHT)
float4 StencilingGeometryPosAndScale;
float4 StencilingConeParameters;    // .x NumSides (0 if not cone), .y NumSlices, .z ConeAngle, .w ConeSphereRadius
float4x4 StencilingConeTransform;
float3 StencilingPreViewTranslation;
#endif

#if defined(SHADER_RADIAL_LIGHT) && SHADER_RADIAL_LIGHT == 0

// 使用全屏方塊渲染*行光的VS.
void DirectionalVertexMain(
    in float2 InPosition : ATTRIBUTE0,
    in float2 InUV       : ATTRIBUTE1,
    out float2 OutTexCoord : TEXCOORD0,
    out float3 OutScreenVector : TEXCOORD1,
    out float4 OutPosition : SV_POSITION
    )
{
    // 繪制矩形.
    DrawRectangle(float4(InPosition.xy, 0, 1), InUV, OutPosition, OutTexCoord);
    // 將輸出位置轉換成螢屏向量.
    OutScreenVector = mul(float4(OutPosition.xy, 1, 0), View.ScreenToTranslatedWorld).xyz;
}
#endif

#if FEATURE_LEVEL >= FEATURE_LEVEL_SM4 && SHADER_RADIAL_LIGHT == 1
// 使用*似邊界幾何體繪制點光源或聚光燈的VS.
void RadialVertexMain(
    in uint InVertexId : SV_VertexID,
    in float3 InPosition : ATTRIBUTE0,
    out float4 OutScreenPosition : TEXCOORD0,
    out float4 OutPosition : SV_POSITION
    )
{
    float3 WorldPosition;
    uint NumSides = StencilingConeParameters.x;
    // 圓錐體形狀
    if (NumSides != 0)
    {
        float SphereRadius = StencilingConeParameters.w;
        float ConeAngle = StencilingConeParameters.z;

        // 錐體頂點著色.
        const float InvCosRadiansPerSide = 1.0f / cos(PI / (float)NumSides);
        // 使用Cos(Theta)=鄰邊(Adjacent)/斜邊(Hypotenuse)的公式來求圓錐末端沿圓錐Z軸的距離.
        const float ZRadius = SphereRadius * cos(ConeAngle);
        const float TanConeAngle = tan(ConeAngle);

        uint NumSlices = StencilingConeParameters.y;
        uint CapIndexStart = NumSides * NumSlices;
        // 生成圓錐形的頂點
        if (InVertexId < CapIndexStart)
        {
            uint SliceIndex = InVertexId / NumSides;
            uint SideIndex = InVertexId % NumSides;

            const float CurrentAngle = SideIndex * 2 * PI / (float)NumSides;
            const float DistanceDownConeDirection = ZRadius * SliceIndex / (float)(NumSlices - 1);
            // 使用Tan(Theta)=對邊(Opposite)/鄰邊(Adjacent)的公式來求解這個切片的半徑.
            // 提高有效半徑,使圓的邊緣位于圓錐體上, 從而代替頂點.
            const float SliceRadius = DistanceDownConeDirection * TanConeAngle * InvCosRadiansPerSide;
            // 在圓錐的區域空間創建一個位置,在XY*面上形成一個圓,并沿Z軸偏移.
            const float3 LocalPosition = float3(ZRadius * SliceIndex / (float)(NumSlices - 1), SliceRadius * sin(CurrentAngle), SliceRadius * cos(CurrentAngle));
            // 轉換到世界空間并應用pre-view translation,因為這些頂點將與一個已洗掉pre-view translation的著色器一起使用.
            WorldPosition = mul(float4(LocalPosition, 1), StencilingConeTransform).xyz + StencilingPreViewTranslation;
        }
        else
        {
            // 為球帽生成頂點.
            const float CapRadius = ZRadius * tan(ConeAngle);

            uint VertexId = InVertexId - CapIndexStart;
            uint SliceIndex = VertexId / NumSides;
            uint SideIndex = VertexId % NumSides;

            const float UnadjustedSliceRadius = CapRadius * SliceIndex / (float)(NumSlices - 1);
            // 提高有效半徑,使圓的邊緣位于圓錐體上, 從而代替頂點.
            const float SliceRadius = UnadjustedSliceRadius * InvCosRadiansPerSide;
            // 用勾股定理(Pythagorean theorem)求出這個切片的Z軸距離.
            const float ZDistance = sqrt(SphereRadius * SphereRadius - UnadjustedSliceRadius * UnadjustedSliceRadius);

            const float CurrentAngle = SideIndex * 2 * PI / (float)NumSides;
            const float3 LocalPosition = float3(ZDistance, SliceRadius * sin(CurrentAngle), SliceRadius * cos(CurrentAngle));
            WorldPosition = mul(float4(LocalPosition, 1), StencilingConeTransform).xyz + StencilingPreViewTranslation;
        }
    }
    else // 球形.
    {
        WorldPosition = InPosition * StencilingGeometryPosAndScale.w + StencilingGeometryPosAndScale.xyz;
    }

    OutScreenPosition = OutPosition = mul(float4(WorldPosition, 1), View.TranslatedWorldToClip);
}
#endif

(......)

上面用到了三角形的構圖定理以及各種三角函式定義,如下圖:

對于*行光,由于影響全場景的物體表面,所以直接全螢屏方塊繪制,

對于點光源和聚光燈,則需要特殊處理,分別使用球體和圓錐體繪制,以便剔除在光源影響范圍之外的像素,所以它們的頂點處理會比*行光復雜一些,

場景中的聚光燈在延遲光照的VS階段使用了圓錐體繪制光照,

需要注意的是,對于點光源或聚光燈,VS的頂點資料只有頂點ID而沒有頂點資料,輸出的頂點資料由頂點著色器中生成:

注意左上角VS Input的ATTRIBUTE的值都是0,右上角VS Output才真正有了正常的值,

5.5.3.2 DeferredLightPixelShader

DeferredLightPixelShader的入口在DeferredLightPixelShaders.usf:

#define SUPPORT_CONTACT_SHADOWS 1

#include "Common.ush"
#include "DeferredShadingCommon.ush"
#include "DeferredLightingCommon.ush"

(......)

#if USE_ATMOSPHERE_TRANSMITTANCE
#include "/Engine/Private/SkyAtmosphereCommon.ush"
#endif

// 輸入引數.
struct FInputParams
{
    float2 PixelPos;
    float4 ScreenPosition;
    float2 ScreenUV;
    float3 ScreenVector;
};

// 派生引數.
struct FDerivedParams
{
    float3 CameraVector;
    float3 WorldPosition;
};

// 獲取派生引數.
FDerivedParams GetDerivedParams(in FInputParams Input, in float SceneDepth)
{
    FDerivedParams Out;
#if LIGHT_SOURCE_SHAPE > 0
    // With a perspective projection, the clip space position is NDC * Clip.w
    // With an orthographic projection, clip space is the same as NDC
    float2 ClipPosition = Input.ScreenPosition.xy / Input.ScreenPosition.w * (View.ViewToClip[3][3] < 1.0f ? SceneDepth : 1.0f);
    Out.WorldPosition = mul(float4(ClipPosition, SceneDepth, 1), View.ScreenToWorld).xyz;
    Out.CameraVector = normalize(Out.WorldPosition - View.WorldCameraOrigin);
#else
    Out.WorldPosition = Input.ScreenVector * SceneDepth + View.WorldCameraOrigin;
    Out.CameraVector = normalize(Input.ScreenVector);
#endif
    return Out;
}

// 創建并設定延遲光照資料結構FDeferredLightData.
FDeferredLightData SetupLightDataForStandardDeferred()
{
    FDeferredLightData LightData;
    
    LightData.Position = DeferredLightUniforms.Position;
    LightData.InvRadius = DeferredLightUniforms.InvRadius;
    LightData.Color = DeferredLightUniforms.Color;
    LightData.FalloffExponent = DeferredLightUniforms.FalloffExponent;
    LightData.Direction = DeferredLightUniforms.Direction;
    LightData.Tangent = DeferredLightUniforms.Tangent;
    LightData.SpotAngles = DeferredLightUniforms.SpotAngles;
    LightData.SourceRadius = DeferredLightUniforms.SourceRadius;
    LightData.SourceLength = DeferredLightUniforms.SourceLength;
    LightData.SoftSourceRadius = DeferredLightUniforms.SoftSourceRadius;
    LightData.SpecularScale = DeferredLightUniforms.SpecularScale;
    LightData.ContactShadowLength = abs(DeferredLightUniforms.ContactShadowLength);
    LightData.ContactShadowLengthInWS = DeferredLightUniforms.ContactShadowLength < 0.0f;
    LightData.DistanceFadeMAD = DeferredLightUniforms.DistanceFadeMAD;
    LightData.ShadowMapChannelMask = DeferredLightUniforms.ShadowMapChannelMask;
    LightData.ShadowedBits = DeferredLightUniforms.ShadowedBits;

    LightData.bInverseSquared = INVERSE_SQUARED_FALLOFF;
    LightData.bRadialLight = LIGHT_SOURCE_SHAPE > 0;
    LightData.bSpotLight = LIGHT_SOURCE_SHAPE > 0;
    LightData.bRectLight = LIGHT_SOURCE_SHAPE == 2;
    
    LightData.RectLightBarnCosAngle = DeferredLightUniforms.RectLightBarnCosAngle;
    LightData.RectLightBarnLength = DeferredLightUniforms.RectLightBarnLength;

    LightData.HairTransmittance = InitHairTransmittanceData();
    return LightData;
}

// 燈光通道紋理.
Texture2D<uint> LightingChannelsTexture;
// 獲取燈光通道掩碼.
uint GetLightingChannelMask(float2 UV)
{
    uint2 IntegerUV = UV * View.BufferSizeAndInvSize.xy;
    return LightingChannelsTexture.Load(uint3(IntegerUV, 0)).x;
}

float GetExposure()
{
#if USE_PREEXPOSURE
    return View.PreExposure;
#else
    return 1;
#endif
}

(......)


#if USE_HAIR_LIGHTING == 0 || USE_HAIR_LIGHTING == 1

// 延遲燈光像素著色器主入口.
void DeferredLightPixelMain(
#if LIGHT_SOURCE_SHAPE > 0
    float4 InScreenPosition : TEXCOORD0,
#else
    float2 ScreenUV            : TEXCOORD0,
    float3 ScreenVector        : TEXCOORD1,
#endif
    float4 SVPos            : SV_POSITION,
    out float4 OutColor        : SV_Target0
    )
{
    const float2 PixelPos = SVPos.xy;
    OutColor = 0;

    // Convert input data (directional/local light)
    FInputParams InputParams = (FInputParams)0;
    InputParams.PixelPos        = SVPos.xy;
#if LIGHT_SOURCE_SHAPE > 0
    InputParams.ScreenPosition    = InScreenPosition;
    // 計算特殊光源的螢屏UV.
    InputParams.ScreenUV        = InScreenPosition.xy / InScreenPosition.w * View.ScreenPositionScaleBias.xy + View.ScreenPositionScaleBias.wz;
    InputParams.ScreenVector    = 0;
#else
    InputParams.ScreenPosition    = 0;
    InputParams.ScreenUV        = ScreenUV;
    InputParams.ScreenVector    = ScreenVector;
#endif

    // 獲取螢屏空間的資料, 包含GBuffer和AO.
    FScreenSpaceData ScreenSpaceData = https://www.cnblogs.com/timlly/p/GetScreenSpaceData(InputParams.ScreenUV);

    // 只有ShadingModelID不為0的像素才是延遲著色.
    BRANCH if( ScreenSpaceData.GBuffer.ShadingModelID > 0 
        // 檢測燈光通道是否重合.
        #if USE_LIGHTING_CHANNELS
        && (GetLightingChannelMask(InputParams.ScreenUV) & DeferredLightUniforms.LightingChannelMask)
        #endif
        )
    {
        const float OpaqueVisibility = 1.0f;

        // 計算場景深度值.
        const float SceneDepth = CalcSceneDepth(InputParams.ScreenUV);
        // 獲取派生資料.
        const FDerivedParams DerivedParams = GetDerivedParams(InputParams, SceneDepth);

        // 設定延遲光源資料.
        FDeferredLightData LightData = SetupLightDataForStandardDeferred();

        // 獲取抖動.
        float Dither = InterleavedGradientNoise(InputParams.PixelPos, View.StateFrameIndexMod8 );

        // 從光源的源紋理獲取矩形紋理.
        FRectTexture RectTexture = InitRectTexture(DeferredLightUniforms.SourceTexture);
        float SurfaceShadow = 1.0f;
        // 計算動態光照.
        const float4 Radiance = GetDynamicLighting(DerivedParams.WorldPosition, DerivedParams.CameraVector, ScreenSpaceData.GBuffer, ScreenSpaceData.AmbientOcclusion, ScreenSpaceData.GBuffer.ShadingModelID, LightData, GetPerPixelLightAttenuation(InputParams.ScreenUV), Dither, uint2(InputParams.PixelPos), RectTexture, SurfaceShadow);
        // 計算光源配置的額外衰減系數.
        const float  Attenuation = ComputeLightProfileMultiplier(DerivedParams.WorldPosition, DeferredLightUniforms.Position, -DeferredLightUniforms.Direction, DeferredLightUniforms.Tangent);

        // 計算最終顏色.
        OutColor += (Radiance * Attenuation) * OpaqueVisibility;

        // 大氣透射.
    #if USE_ATMOSPHERE_TRANSMITTANCE
        OutColor.rgb *= GetAtmosphericLightTransmittance(SVPos, InputParams.ScreenUV, DeferredLightUniforms.Direction.xyz);
    #endif
    }

    // RGB:SceneColor Specular and Diffuse
    // A:Non Specular SceneColor Luminance
    // So we need PreExposure for both color and alpha
    OutColor.rgba *= GetExposure();
}
#endif

(......)

上面就是延遲光源的像素著色程序,看起來 很簡單?非也,里邊有兩個重要的介面蘊含了大量的邏輯,

第一個是相對簡單的GetScreenSpaceData,追蹤它的邏輯堆疊:

// Engine\Shaders\Private\DeferredShadingCommon.ush

// 獲取螢屏空間的資料.
FScreenSpaceData GetScreenSpaceData(float2 UV, bool bGetNormalizedNormal = true)
{
    FScreenSpaceData Out;

    // 獲取GBuffer資料.
    Out.GBuffer = GetGBufferData(UV, bGetNormalizedNormal);
    // 獲取螢屏空間AO.
    float4 ScreenSpaceAO = Texture2DSampleLevel(SceneTexturesStruct.ScreenSpaceAOTexture, SceneTexturesStruct.ScreenSpaceAOTextureSampler, UV, 0);

    // 注意AO只取r通道.
    Out.AmbientOcclusion = ScreenSpaceAO.r;

    return Out;
}

// 獲取指定螢屏空間UV的GBuffer資料.
FGBufferData GetGBufferData(float2 UV, bool bGetNormalizedNormal = true)
{
    (......)
    
    // 從GBuffer紋理中采樣資料.
    float4 GBufferA = Texture2DSampleLevel(SceneTexturesStruct.GBufferATexture, SceneTexturesStruct.GBufferATextureSampler, UV, 0);
    float4 GBufferB = Texture2DSampleLevel(SceneTexturesStruct.GBufferBTexture, SceneTexturesStruct.GBufferBTextureSampler, UV, 0);
    float4 GBufferC = Texture2DSampleLevel(SceneTexturesStruct.GBufferCTexture, SceneTexturesStruct.GBufferCTextureSampler, UV, 0);
    float4 GBufferD = Texture2DSampleLevel(SceneTexturesStruct.GBufferDTexture, SceneTexturesStruct.GBufferDTextureSampler, UV, 0);
    float CustomNativeDepth = Texture2DSampleLevel(SceneTexturesStruct.CustomDepthTexture, SceneTexturesStruct.CustomDepthTextureSampler, UV, 0).r;

    int2 IntUV = (int2)trunc(UV * View.BufferSizeAndInvSize.xy);
    // 自定義模板值.
    uint CustomStencil = SceneTexturesStruct.CustomStencilTexture.Load(int3(IntUV, 0)) STENCIL_COMPONENT_SWIZZLE;
    
    (......)

    // 靜態光.
    #if ALLOW_STATIC_LIGHTING
        float4 GBufferE = Texture2DSampleLevel(SceneTexturesStruct.GBufferETexture, SceneTexturesStruct.GBufferETextureSampler, UV, 0);
    #else
        float4 GBufferE = 1;
    #endif

    // 切線.
    #if GBUFFER_HAS_TANGENT
        float4 GBufferF = Texture2DSampleLevel(SceneTexturesStruct.GBufferFTexture, SceneTexturesStruct.GBufferFTextureSampler, UV, 0);
    #else
        float4 GBufferF = 0.5f;
    #endif 

    // 速度.
    #if WRITES_VELOCITY_TO_GBUFFER
        float4 GBufferVelocity = Texture2DSampleLevel(SceneTexturesStruct.GBufferVelocityTexture, SceneTexturesStruct.GBufferVelocityTextureSampler, UV, 0);
    #else
        float4 GBufferVelocity = 0;
    #endif
#endif

    // 深度.
    float SceneDepth = CalcSceneDepth(UV);
    
    // 解碼紋理值到對應的結構體.
    return DecodeGBufferData(GBufferA, GBufferB, GBufferC, GBufferD, GBufferE, GBufferF, GBufferVelocity, CustomNativeDepth, CustomStencil, SceneDepth, bGetNormalizedNormal, CheckerFromSceneColorUV(UV));
}

// 解碼紋理值到對應的結構體.
FGBufferData DecodeGBufferData(float4 InGBufferA, float4 InGBufferB, float4 InGBufferC, float4 InGBufferD, float4 InGBufferE, float4 InGBufferF, float4 InGBufferVelocity, float CustomNativeDepth, uint CustomStencil, float SceneDepth, bool bGetNormalizedNormal, bool bChecker)
{
    FGBufferData GBuffer;

    // 法線.
    GBuffer.WorldNormal = DecodeNormal( InGBufferA.xyz );
    if(bGetNormalizedNormal)
    {
        GBuffer.WorldNormal = normalize(GBuffer.WorldNormal);
    }

    // 逐物體, 金屬度, 高光度, 粗糙度.
    GBuffer.PerObjectGBufferData = https://www.cnblogs.com/timlly/p/InGBufferA.a;  
    GBuffer.Metallic    = InGBufferB.r;
    GBuffer.Specular    = InGBufferB.g;
    GBuffer.Roughness    = InGBufferB.b;
    // 著色模型ID.
    GBuffer.ShadingModelID = DecodeShadingModelId(InGBufferB.a);
    GBuffer.SelectiveOutputMask = DecodeSelectiveOutputMask(InGBufferB.a);

    // 基礎色.
    GBuffer.BaseColor = DecodeBaseColor(InGBufferC.rgb);

    // AO和非直接光.
#if ALLOW_STATIC_LIGHTING
    GBuffer.GBufferAO = 1;
    GBuffer.IndirectIrradiance = DecodeIndirectIrradiance(InGBufferC.a);
#else
    GBuffer.GBufferAO = InGBufferC.a;
    GBuffer.IndirectIrradiance = 1;
#endif

    // 自定義資料.
    GBuffer.CustomData = !(GBuffer.SelectiveOutputMask & SKIP_CUSTOMDATA_MASK) ? InGBufferD : 0;

    // 場景或自定義的深度模板.
    GBuffer.PrecomputedShadowFactors = !(GBuffer.SelectiveOutputMask & SKIP_PRECSHADOW_MASK) ? InGBufferE :  ((GBuffer.SelectiveOutputMask & ZERO_PRECSHADOW_MASK) ? 0 :  1);
    GBuffer.CustomDepth = ConvertFromDeviceZ(CustomNativeDepth);
    GBuffer.CustomStencil = CustomStencil;
    GBuffer.Depth = SceneDepth;

    // 保持初始的基本值.
    GBuffer.StoredBaseColor = GBuffer.BaseColor;
    GBuffer.StoredMetallic = GBuffer.Metallic;
    GBuffer.StoredSpecular = GBuffer.Specular;

    (......)

    // 派生自BaseColor, Metalness, Specular的資料.
    {
        GBuffer.SpecularColor = ComputeF0(GBuffer.Specular, GBuffer.BaseColor, GBuffer.Metallic);

        if (UseSubsurfaceProfile(GBuffer.ShadingModelID))
        {
            AdjustBaseColorAndSpecularColorForSubsurfaceProfileLighting(GBuffer.BaseColor, GBuffer.SpecularColor, GBuffer.Specular, bChecker);
        }

        GBuffer.DiffuseColor = GBuffer.BaseColor - GBuffer.BaseColor * GBuffer.Metallic;

        (......)
    }

    // 切線.
#if GBUFFER_HAS_TANGENT
    GBuffer.WorldTangent = DecodeNormal(InGBufferF.rgb);
    GBuffer.Anisotropy = InGBufferF.a * 2.0f - 1.0f;

    if(bGetNormalizedNormal)
    {
        GBuffer.WorldTangent = normalize(GBuffer.WorldTangent);
    }
#else
    GBuffer.WorldTangent = 0;
    GBuffer.Anisotropy = 0;
#endif

    // 速度.
    GBuffer.Velocity = !(GBuffer.SelectiveOutputMask & SKIP_VELOCITY_MASK) ? InGBufferVelocity : 0;

    return GBuffer;
}

解碼GBuffer的程序大致是從GBuffer紋理中采樣資料,再進行二次加工并存盤到FGBufferData中,然后FGBufferData的實體又存盤到FScreenSpaceData中,以便后續的光照計算中直接訪問,也可以防止多次采樣紋理造成GPU和顯存之間的IO瓶頸和GPU Cache命中率降低,

第一個是非常復雜的GetDynamicLighting,進入復雜的光照計算邏輯:

// Engine\Shaders\Private\DeferredLightingCommon.ush

// 計算動態光照.
float4 GetDynamicLighting(
    float3 WorldPosition, float3 CameraVector, FGBufferData GBuffer, float AmbientOcclusion, uint ShadingModelID, 
    FDeferredLightData LightData, float4 LightAttenuation, float Dither, uint2 SVPos, FRectTexture SourceTexture,
    inout float SurfaceShadow)
{
    FDeferredLightingSplit SplitLighting = GetDynamicLightingSplit(
        WorldPosition, CameraVector, GBuffer, AmbientOcclusion, ShadingModelID, 
        LightData, LightAttenuation, Dither, SVPos, SourceTexture,
        SurfaceShadow);

    return SplitLighting.SpecularLighting + SplitLighting.DiffuseLighting;
}

// 計算動態光照, 分離了漫反射和高光項.
FDeferredLightingSplit GetDynamicLightingSplit(
    float3 WorldPosition, float3 CameraVector, FGBufferData GBuffer, float AmbientOcclusion, uint ShadingModelID, 
    FDeferredLightData LightData, float4 LightAttenuation, float Dither, uint2 SVPos, FRectTexture SourceTexture,
    inout float SurfaceShadow)
{
    FLightAccumulator LightAccumulator = (FLightAccumulator)0;

    float3 V = -CameraVector;
    float3 N = GBuffer.WorldNormal;
    BRANCH if( GBuffer.ShadingModelID == SHADINGMODELID_CLEAR_COAT && CLEAR_COAT_BOTTOM_NORMAL)
    {
        const float2 oct1 = ((float2(GBuffer.CustomData.a, GBuffer.CustomData.z) * 2) - (256.0/255.0)) + UnitVectorToOctahedron(GBuffer.WorldNormal);
        N = OctahedronToUnitVector(oct1);            
    }
    
    float3 L = LightData.Direction;    // Already normalized
    float3 ToLight = L;
    
    float LightMask = 1;
    // 獲取輻射光源的衰減.
    if (LightData.bRadialLight)
    {
        LightMask = GetLocalLightAttenuation( WorldPosition, LightData, ToLight, L );
    }

    LightAccumulator.EstimatedCost += 0.3f;        // running the PixelShader at all has a cost

    BRANCH
    // 只計算有效強度的表面.
    if( LightMask > 0 )
    {
        // 處理表面陰影.
        FShadowTerms Shadow;
        Shadow.SurfaceShadow = AmbientOcclusion;
        Shadow.TransmissionShadow = 1;
        Shadow.TransmissionThickness = 1;
        Shadow.HairTransmittance.Transmittance = 1;
        Shadow.HairTransmittance.OpaqueVisibility = 1;
        // 計算表面陰影資料.
        GetShadowTerms(GBuffer, LightData, WorldPosition, L, LightAttenuation, Dither, Shadow);
        SurfaceShadow = Shadow.SurfaceShadow;

        LightAccumulator.EstimatedCost += 0.3f;        // add the cost of getting the shadow terms

        BRANCH
        // 如果受陰影后的光照強度和透射強度之和>0才繼續計算光照.
        if( Shadow.SurfaceShadow + Shadow.TransmissionShadow > 0 )
        {
            const bool bNeedsSeparateSubsurfaceLightAccumulation = UseSubsurfaceProfile(GBuffer.ShadingModelID);
            float3 LightColor = LightData.Color;

        #if NON_DIRECTIONAL_DIRECT_LIGHTING // 非*行直接光
            float Lighting;

            if( LightData.bRectLight ) // 矩形光
            {
                FRect Rect = GetRect( ToLight, LightData );

                // 積分矩形光照.
                Lighting = IntegrateLight( Rect, SourceTexture);
            }
            else // 膠囊光
            {
                FCapsuleLight Capsule = GetCapsule( ToLight, LightData );

                // 積分膠囊光照.
                Lighting = IntegrateLight( Capsule, LightData.bInverseSquared );
            }

            float3 LightingDiffuse = Diffuse_Lambert( GBuffer.DiffuseColor ) * Lighting;
            LightAccumulator_AddSplit(LightAccumulator, LightingDiffuse, 0.0f, 0, LightColor * LightMask * Shadow.SurfaceShadow, bNeedsSeparateSubsurfaceLightAccumulation);
        #else // *行直接光
            FDirectLighting Lighting;

            if (LightData.bRectLight)
            {
                FRect Rect = GetRect( ToLight, LightData );

                #if REFERENCE_QUALITY
                    Lighting = IntegrateBxDF( GBuffer, N, V, Rect, Shadow, SourceTexture, SVPos );
                #else
                    Lighting = IntegrateBxDF( GBuffer, N, V, Rect, Shadow, SourceTexture);
                #endif
            }
            else
            {
                FCapsuleLight Capsule = GetCapsule( ToLight, LightData );

                #if REFERENCE_QUALITY
                    Lighting = IntegrateBxDF( GBuffer, N, V, Capsule, Shadow, SVPos );
                #else
                    Lighting = IntegrateBxDF( GBuffer, N, V, Capsule, Shadow, LightData.bInverseSquared );
                #endif
            }

            Lighting.Specular *= LightData.SpecularScale;
                
            LightAccumulator_AddSplit( LightAccumulator, Lighting.Diffuse, Lighting.Specular, Lighting.Diffuse, LightColor * LightMask * Shadow.SurfaceShadow, bNeedsSeparateSubsurfaceLightAccumulation );
            LightAccumulator_AddSplit( LightAccumulator, Lighting.Transmission, 0.0f, Lighting.Transmission, LightColor * LightMask * Shadow.TransmissionShadow, bNeedsSeparateSubsurfaceLightAccumulation );

            LightAccumulator.EstimatedCost += 0.4f;        // add the cost of the lighting computations (should sum up to 1 form one light)
        #endif
        }
    }

    return LightAccumulator_GetResultSplit(LightAccumulator);
}

上面的代碼中在NON_DIRECTIONAL_DIRECT_LIGHTING為0的分支下,會根據是否矩形光和質量參考(REFERENCE_QUALITY)來進入4種不同的BxDF介面,下面就以非矩形光非質量參考版本的IntegrateBxDF進行剖析:

// Engine\Shaders\Private\CapsuleLightIntegrate.ush

// 積分膠囊光照.
FDirectLighting IntegrateBxDF( FGBufferData GBuffer, half3 N, half3 V, FCapsuleLight Capsule, FShadowTerms Shadow, bool bInverseSquared )
{
    float NoL;
    float Falloff;
    float LineCosSubtended = 1;

    // Clip to horizon
    //float NoP0 = dot( N, Capsule.LightPos[0] );
    //float NoP1 = dot( N, Capsule.LightPos[1] );
    //if( NoP0 < 0 ) Capsule.LightPos[0] = (  Capsule.LightPos[0] * NoP1 - Capsule.LightPos[1] * NoP0 ) / ( NoP1 - NoP0);
    //if( NoP1 < 0 ) Capsule.LightPos[1] = ( -Capsule.LightPos[0] * NoP1 + Capsule.LightPos[1] * NoP0 ) / (-NoP1 + NoP0);

    BRANCH
    // 處理衰減, N和L的點積.
    if( Capsule.Length > 0 ) // 如果是有效膠囊體, 則只需線段積分(具體公式見5.4.2.2)
    {
        LineIrradiance( N, Capsule.LightPos[0], Capsule.LightPos[1], Capsule.DistBiasSqr, LineCosSubtended, Falloff, NoL );
    }
    else // 光源當成一個點
    {
        float DistSqr = dot( Capsule.LightPos[0], Capsule.LightPos[0] );
        Falloff = rcp( DistSqr + Capsule.DistBiasSqr );

        float3 L = Capsule.LightPos[0] * rsqrt( DistSqr );
        NoL = dot( N, L );
    }

    // 膠囊半徑>0, 當球體蓋帽來調整N和L的點積. 
    if( Capsule.Radius > 0 )
    {
        // TODO Use capsule area?
        float SinAlphaSqr = saturate( Pow2( Capsule.Radius ) * Falloff );
        NoL = SphereHorizonCosWrap( NoL, SinAlphaSqr );
    }

    NoL = saturate( NoL );
    Falloff = bInverseSquared ? Falloff : 1;

    // 調整有效長度的光源方向.
    float3 ToLight = Capsule.LightPos[0];
    if( Capsule.Length > 0 )
    {
        float3 R = reflect( -V, N );

        ToLight = ClosestPointLineToRay( Capsule.LightPos[0], Capsule.LightPos[1], Capsule.Length, R );
    }

    float DistSqr = dot( ToLight, ToLight );
    float InvDist = rsqrt( DistSqr );
    float3 L = ToLight * InvDist;
    
    GBuffer.Roughness = max( GBuffer.Roughness, View.MinRoughness );
    float a = Pow2( GBuffer.Roughness );
    
    // 根據上面的資訊構建區域光資訊.
    FAreaLight AreaLight;
    AreaLight.SphereSinAlpha = saturate( Capsule.Radius * InvDist * (1 - a) );
    AreaLight.SphereSinAlphaSoft = saturate( Capsule.SoftRadius * InvDist );
    AreaLight.LineCosSubtended = LineCosSubtended;
    AreaLight.FalloffColor = 1;
    AreaLight.Rect = (FRect)0;
    AreaLight.bIsRect = false;
    AreaLight.Texture = InitRectTexture(DummyRectLightTextureForCapsuleCompilerWarning);

    // 積磁區域光
    return IntegrateBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
}

// Engine\Shaders\Private\ShadingModels.ush

FDirectLighting IntegrateBxDF( FGBufferData GBuffer, half3 N, half3 V, half3 L, float Falloff, float NoL, FAreaLight AreaLight, FShadowTerms Shadow )
{
    // 根據不同的著色模型進入不同的光照BxDF.
    switch( GBuffer.ShadingModelID )
    {
        case SHADINGMODELID_DEFAULT_LIT:
        case SHADINGMODELID_SINGLELAYERWATER:
        case SHADINGMODELID_THIN_TRANSLUCENT:
            // 默認光照
            return DefaultLitBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
        case SHADINGMODELID_SUBSURFACE:
            // 次表面散射
            return SubsurfaceBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
        case SHADINGMODELID_PREINTEGRATED_SKIN:
            // 預積分皮膚
            return PreintegratedSkinBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
        case SHADINGMODELID_CLEAR_COAT:
            // 清漆
            return ClearCoatBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
        case SHADINGMODELID_SUBSURFACE_PROFILE:
            // 次表面散射配置
            return SubsurfaceProfileBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
        case SHADINGMODELID_TWOSIDED_FOLIAGE:
            // 雙面
            return TwoSidedBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
        case SHADINGMODELID_HAIR:
            // 頭發
            return HairBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
        case SHADINGMODELID_CLOTH:
            // 布料
            return ClothBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
        case SHADINGMODELID_EYE:
            // 眼睛
            return EyeBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
        default:
            return (FDirectLighting)0;
    }
}

上面的代碼可知,積磁區域光的邏輯中,會根據表面的著色模型進入不同的著色BxDF介面,下面剖析最常見的DefaultLitBxDF

// Engine\Shaders\Private\ShadingModels.ush

// 默認光照模型BxDF.
FDirectLighting DefaultLitBxDF( FGBufferData GBuffer, half3 N, half3 V, half3 L, float Falloff, float NoL, FAreaLight AreaLight, FShadowTerms Shadow )
{
#if GBUFFER_HAS_TANGENT
    half3 X = GBuffer.WorldTangent;
    half3 Y = normalize(cross(N, X));
#else
    half3 X = 0;
    half3 Y = 0;
#endif
    
    // 初始背景關系, 內含各種點積.
    BxDFContext Context;
    Init( Context, N, X, Y, V, L );
    SphereMaxNoH( Context, AreaLight.SphereSinAlpha, true );
    Context.NoV = saturate( abs( Context.NoV ) + 1e-5 );

    FDirectLighting Lighting;
    // 用蘭伯特計算直接光的漫反射.
    Lighting.Diffuse  = AreaLight.FalloffColor * (Falloff * NoL) * Diffuse_Lambert( GBuffer.DiffuseColor );

    // 使用矩形的GGX*似法的LTC計算直接光的鏡面反射.
    if( AreaLight.bIsRect )
        Lighting.Specular = RectGGXApproxLTC( GBuffer.Roughness, GBuffer.SpecularColor, N, V, AreaLight.Rect, AreaLight.Texture );
    else
        Lighting.Specular = AreaLight.FalloffColor * (Falloff * NoL) * SpecularGGX( GBuffer.Roughness, GBuffer.Anisotropy, GBuffer.SpecularColor, Context, NoL, AreaLight );

    Lighting.Transmission = 0;
    return Lighting;
}

SphereMaxNoH是調整模擬區域光后的各種向量之間的點積,由論文DECIMA ENGINE: ADVANCES IN LIGHTING AND AA提出,它的核心思想在于先拉長調整切線、副切線、光源方向:

直接決議求得最大化的\(N\cdot H\)是比較困難的,但是可以假設一些條件,然后提供有理多項式模擬\((N\cdot H)^2\)

先用Karis*似法估算出第一項,再用牛頓迭代法求得第二項,然后用它模擬\((N\cdot H)^2\)

SphereMaxNoH對應的實作代碼:

// Engine\Shaders\Private\BRDF.ush

void SphereMaxNoH( inout BxDFContext Context, float SinAlpha, bool bNewtonIteration )
{
    if( SinAlpha > 0 )
    {
        float CosAlpha = sqrt( 1 - Pow2( SinAlpha ) );
    
        float RoL = 2 * Context.NoL * Context.NoV - Context.VoL;
        if( RoL >= CosAlpha )
        {
            Context.NoH = 1;
            Context.XoH = 0;
            Context.YoH = 0;
            Context.VoH = abs( Context.NoV );
        }
        else
        {
            float rInvLengthT = SinAlpha * rsqrt( 1 - RoL*RoL );
            float NoTr = rInvLengthT * ( Context.NoV - RoL * Context.NoL );
            float VoTr = rInvLengthT * ( 2 * Context.NoV*Context.NoV - 1 - RoL * Context.VoL );

            if (bNewtonIteration)
            {
                // dot( cross(N,L), V )
                float NxLoV = sqrt( saturate( 1 - Pow2(Context.NoL) - Pow2(Context.NoV) - Pow2(Context.VoL) + 2 * Context.NoL * Context.NoV * Context.VoL ) );

                float NoBr = rInvLengthT * NxLoV;
                float VoBr = rInvLengthT * NxLoV * 2 * Context.NoV;

                float NoLVTr = Context.NoL * CosAlpha + Context.NoV + NoTr;
                float VoLVTr = Context.VoL * CosAlpha + 1   + VoTr;

                float p = NoBr   * VoLVTr;
                float q = NoLVTr * VoLVTr;
                float s = VoBr   * NoLVTr;

                float xNum = q * ( -0.5 * p + 0.25 * VoBr * NoLVTr );
                float xDenom = p*p + s * (s - 2*p) + NoLVTr * ( (Context.NoL * CosAlpha + Context.NoV) * Pow2(VoLVTr) + q * (-0.5 * (VoLVTr + Context.VoL * CosAlpha) - 0.5) );
                float TwoX1 = 2 * xNum / ( Pow2(xDenom) + Pow2(xNum) );
                float SinTheta = TwoX1 * xDenom;
                float CosTheta = 1.0 - TwoX1 * xNum;
                NoTr = CosTheta * NoTr + SinTheta * NoBr;
                VoTr = CosTheta * VoTr + SinTheta * VoBr;
            }

            Context.NoL = Context.NoL * CosAlpha + NoTr; // dot( N, L * CosAlpha + T * SinAlpha )
            Context.VoL = Context.VoL * CosAlpha + VoTr;

            float InvLenH = rsqrt( 2 + 2 * Context.VoL );
            Context.NoH = saturate( ( Context.NoL + Context.NoV ) * InvLenH );
            Context.VoH = saturate( InvLenH + InvLenH * Context.VoL );
        }
    }
}

調整表面各類向量的點積之后,會使用蘭伯特計算漫反射,使用矩形的GGX*似法的LTC計算直接光的鏡面反射,后者的實作代碼如下:

// Engine\Shaders\Private\RectLight.ush

float3 RectGGXApproxLTC( float Roughness, float3 SpecularColor, half3 N, float3 V, FRect Rect, FRectTexture RectTexture )
{
    // No visibile rect light due to barn door occlusion
    if (Rect.Extent.x == 0 || Rect.Extent.y == 0) return 0;

    float NoV = saturate( abs( dot(N, V) ) + 1e-5 );

    float2 UV = float2( Roughness, sqrt( 1 - NoV ) );
    UV = UV * (63.0 / 64.0) + (0.5 / 64.0);
   
    float4 LTCMat = LTCMatTexture.SampleLevel( LTCMatSampler, UV, 0 );
    float4 LTCAmp = LTCAmpTexture.SampleLevel( LTCAmpSampler, UV, 0 );

    float3x3 LTC = {
        float3( LTCMat.x, 0, LTCMat.z ),
        float3(        0, 1,        0 ),
        float3( LTCMat.y, 0, LTCMat.w )
    };

    float LTCDet = LTCMat.x * LTCMat.w - LTCMat.y * LTCMat.z;

    float4 InvLTCMat = LTCMat / LTCDet;
    float3x3 InvLTC = {
        float3( InvLTCMat.w, 0,-InvLTCMat.z ),
        float3(              0, 1,           0 ),
        float3(-InvLTCMat.y, 0, InvLTCMat.x )
    };

    // Rotate to tangent space
    float3 T1 = normalize( V - N * dot( N, V ) );
    float3 T2 = cross( N, T1 );
    float3x3 TangentBasis = float3x3( T1, T2, N );

    LTC = mul( LTC, TangentBasis );
    InvLTC = mul( transpose( TangentBasis ), InvLTC );

    float3 Poly[4];
    Poly[0] = mul( LTC, Rect.Origin - Rect.Axis[0] * Rect.Extent.x - Rect.Axis[1] * Rect.Extent.y );
    Poly[1] = mul( LTC, Rect.Origin + Rect.Axis[0] * Rect.Extent.x - Rect.Axis[1] * Rect.Extent.y );
    Poly[2] = mul( LTC, Rect.Origin + Rect.Axis[0] * Rect.Extent.x + Rect.Axis[1] * Rect.Extent.y );
    Poly[3] = mul( LTC, Rect.Origin - Rect.Axis[0] * Rect.Extent.x + Rect.Axis[1] * Rect.Extent.y );

    // Vector irradiance
    float3 L = PolygonIrradiance( Poly );

    float LengthSqr = dot( L, L );
    float InvLength = rsqrt( LengthSqr );
    float Length = LengthSqr * InvLength;

    // Mean light direction
    L *= InvLength;

    // Solid angle of sphere        = 2*PI * ( 1 - sqrt(1 - r^2 / d^2 ) )
    // Cosine weighted integration    = PI * r^2 / d^2
    // SinAlphaSqr = r^2 / d^2;
    float SinAlphaSqr = Length;

    float NoL = SphereHorizonCosWrap( L.z, SinAlphaSqr );
    float Irradiance = SinAlphaSqr * NoL;

    // Kill negative and NaN
    Irradiance = -min(-Irradiance, 0.0);
    
    SpecularColor = LTCAmp.y + ( LTCAmp.x - LTCAmp.y ) * SpecularColor;
    
    // Transform to world space
    L = mul( InvLTC, L );

    float3 LightColor = SampleSourceTexture( L, Rect, RectTexture );
    
    return LightColor * Irradiance * SpecularColor;
}

以上實作出自論文Real-Time Polygonal-Light Shading with Linearly Transformed Cosines,其核心思想在于通過線性預先變換*似任意形體的面光源的BRDF的粗糙度、各向異性以及斜切等特性:

而且效率比球體、半球體、夾緊余弦光照模型相當,比馮氏光照模型要快:

然后將最耗時的LTC矩陣和縮放預積分到紋理中,分別是LTCMatTexture和LTCAmpTexture:

不過以上兩張紋理是在引擎初始化時運行時算出來的,之后就快取住并直接使用:

// Engine\Source\Runtime\Renderer\Private\SystemTextures.cpp

void FSystemTextures::InitializeFeatureLevelDependentTextures(FRHICommandListImmediate& RHICmdList, const ERHIFeatureLevel::Type InFeatureLevel)
{
    (......)
    
    // LTCMatTexture
    {
        FPooledRenderTargetDesc Desc(FPooledRenderTargetDesc::Create2DDesc(FIntPoint(LTC_Size, LTC_Size), PF_FloatRGBA, FClearValueBinding::None, TexCreate_FastVRAM, TexCreate_ShaderResource, false));
        Desc.AutoWritable = false;

        GRenderTargetPool.FindFreeElement(RHICmdList, Desc, LTCMat, TEXT("LTCMat"));
        // Write the contents of the texture.
        uint32 DestStride;
        uint8* DestBuffer = (uint8*)RHICmdList.LockTexture2D((FTexture2DRHIRef&)LTCMat->GetRenderTargetItem().ShaderResourceTexture, 0, RLM_WriteOnly, DestStride, false);

        for (int32 y = 0; y < Desc.Extent.Y; ++y)
        {
            for (int32 x = 0; x < Desc.Extent.X; ++x)
            {
                uint16* Dest = (uint16*)(DestBuffer + x * 4 * sizeof(uint16) + y * DestStride);

                for (int k = 0; k < 4; k++)
                    Dest[k] = FFloat16(LTC_Mat[4 * (x + y * LTC_Size) + k]).Encoded;
            }
        }
        RHICmdList.UnlockTexture2D((FTexture2DRHIRef&)LTCMat->GetRenderTargetItem().ShaderResourceTexture, 0, false);
    }

    // LTCAmpTexture
    {
        FPooledRenderTargetDesc Desc(FPooledRenderTargetDesc::Create2DDesc(FIntPoint(LTC_Size, LTC_Size), PF_G16R16F, FClearValueBinding::None, TexCreate_FastVRAM, TexCreate_ShaderResource, false));
        Desc.AutoWritable = false;

        GRenderTargetPool.FindFreeElement(RHICmdList, Desc, LTCAmp, TEXT("LTCAmp"));
        // Write the contents of the texture.
        uint32 DestStride;
        uint8* DestBuffer = (uint8*)RHICmdList.LockTexture2D((FTexture2DRHIRef&)LTCAmp->GetRenderTargetItem().ShaderResourceTexture, 0, RLM_WriteOnly, DestStride, false);

        for (int32 y = 0; y < Desc.Extent.Y; ++y)
        {
            for (int32 x = 0; x < Desc.Extent.X; ++x)
            {
                uint16* Dest = (uint16*)(DestBuffer + x * 2 * sizeof(uint16) + y * DestStride);

                for (int k = 0; k < 2; k++)
                    Dest[k] = FFloat16(LTC_Amp[4 * (x + y * LTC_Size) + k]).Encoded;
            }
        }
        RHICmdList.UnlockTexture2D((FTexture2DRHIRef&)LTCAmp->GetRenderTargetItem().ShaderResourceTexture, 0, false);
    }
    
    (......)
}

5.5.4 LightingPass總結

下面總結光照計算像素著色器的主要流程和步驟:

graph TD DeferredLightPixelMain(DeferredLightPixelMain) --> SetupLightDataForStandardDeferred(SetupLightDataForStandardDeferred) SetupLightDataForStandardDeferred --> GetDynamicLighting(GetDynamicLighting) GetDynamicLighting --> GetDynamicLightingSplit(GetDynamicLightingSplit) GetDynamicLightingSplit --> GetLocalLightAttenuation(GetLocalLightAttenuation) GetLocalLightAttenuation --> GetShadowTerms(GetShadowTerms) GetShadowTerms --> IntegrateBxDF_CapsuleLight(IntegrateBxDF_CapsuleLight) IntegrateBxDF_CapsuleLight --> LineIrradiance(LineIrradiance) LineIrradiance --> SphereHorizonCosWrap(SphereHorizonCosWrap) SphereHorizonCosWrap --> ClosestPointLineToRay(ClosestPointLineToRay) ClosestPointLineToRay --> IntegrateBxDF_AreaLight(IntegrateBxDF_AreaLight) IntegrateBxDF_AreaLight --> ShadingModelID{ShadingModelID} ShadingModelID -->|SUBSURFACE| SubsurfaceBxDF(SubsurfaceBxDF) ShadingModelID -->|CLEAR_COAT| ClearCoatBxDF(ClearCoatBxDF) ShadingModelID -->|HAIR| HairBxDF(HairBxDF) ShadingModelID -->|SINGLELAYERWATER| DefaultLitBxDF(DefaultLitBxDF) ShadingModelID -->|DEFAULT_LIT| DefaultLitBxDF(DefaultLitBxDF) ShadingModelID -->|THIN_TRANSLUCENT| DefaultLitBxDF(DefaultLitBxDF) ShadingModelID -->|CLOTH| ClothBxDF(ClothBxDF) ShadingModelID -->|EYE| EyeBxDF(EyeBxDF) ShadingModelID -->|...| OtherShadingModel(...) DefaultLitBxDF --> SphereMaxNoH(SphereMaxNoH) SphereMaxNoH --> Diffuse_Lambert(Diffuse_Lambert) Diffuse_Lambert --> RectGGXApproxLTC(RectGGXApproxLTC) RectGGXApproxLTC --> SpecularGGX(SpecularGGX) SpecularGGX --> LightAccumulator_AddSplit(LightAccumulator_AddSplit) LightAccumulator_AddSplit --> LightAccumulator_GetResultSplit(LightAccumulator_GetResultSplit)

上面的主要步驟的具體邏輯就不在累述了,可以參看之前的代碼分析,

 

5.6 UE的陰影

UE的陰影種類繁多,實作復雜,涉及的優化技術也多,本章將花大篇幅介紹和闡述UE的陰影渲染機制,

5.6.1 陰影概述

UE的光源型別有多種,而UE的陰影則具有更豐富的型別,如下所述,

  • 靜態陰影(Static Shadow)

靜態陰影由靜態光源射到靜態物體在靜態接收體中產生的投影,故而只會在靜態物體中產生,動態物體通常不能產生靜態陰影,

左邊的動態角色在靜態光源下不會受到光照也不會產生投影,而右邊的靜態角色則會,

  • 級聯陰影(Cascading Shadow Map)

級聯陰影又被稱為Directional Light Cascading Shadow Maps或Whole Scene Shadows,是針對*行光影響全場景范圍內的多級陰影圖,以解決光源視圖空間遠處的深度圖精度不足出現各類視覺瑕疵的問題,

級聯陰影示意圖,在光源視圖空間中,紅色最*,深度圖解析度最小,黃色最遠,深度圖解析度最大,

*行光的Cascaded Shadow Maps屬性組可以設定級聯陰影的詳細引數,

此外,*行固定光源(Directional Stationary Lights)比較特殊,因為它們支持靜態陰影的同時又可以通過級聯陰影貼圖支持整個場景的陰影,對于擁有海量帶影片的植被的關卡非常有用,因為可以支持動態物體的陰影,而在遠處某個距離會逐漸過渡到靜態陰影,

可以通過調整*行光級聯陰影屬性組的Dynamic Shadow Distance StationaryLight的數值來調整過渡區域,

  • 固定光源陰影(Stationary Light Shadows)

動態物體必須從距離場陰影圖集成到世界的靜態陰影中,為此,需要借助逐物體陰影,每個可移動的物體都會從一個固定光源中產生兩個動態陰影:一個陰影用來處理靜態世界投影到動態物體上,另一個陰影用來處理動態物體投影到世界上,

通過這種設定,固定光源的唯一陰影成本來自它所影響的動態物體,這意味著成本可大可小,取決于有多少動態物體,如果場景中足夠多的動態物體,使用可移動光源會更加高效,

  • 逐物體陰影(Per Object Shadow)

可移動組件使用的逐物體陰影應用陰影圖到物體的包圍盒,因此包圍盒必須是精確的,對于骨骼網格,這意味著它們應該有一個物理資產,對于粒子系統,任何固定的邊界框必須足夠大,以容納所有的粒子,

在網格的Lighting屬性組中,Dynamic Inset Shadow可以開啟逐物體陰影,對需要高質量高精度的物體非常有用,

  • 動態陰影(Dynamic Shadow)

可移動光源在所有物體上投射出完全動態的陰影(和光),這種光源的任何資料不會被烘焙到光照圖中,它可以自由地在所有東西上投射動態陰影,靜態網格、骨架網格、粒子效果等等將完全從可移動光源投射和接收動態陰影,

一般來說,可移動的動態陰影投射光源是最耗性能的,

  • 預覽陰影(Preview Shadow)

當正在編輯固定或靜態光源時,而它們沒有被及時構建,UE會使用預覽陰影來代替未構建的陰影,預覽陰影將帶有Preview字樣:

  • 膠囊體陰影(Capsule Shadow)

UE支持使用物體的物理資產(Physics Asset)生成的膠囊體代替物體本身投射陰影,可以產生軟陰影,主要用于擁有物理資產的蒙皮網格(Skeletal Mesh),

上:膠囊體陰影;下:對應的物理資產代表,

  • 接觸陰影(Contact Shadow)

接觸陰影是逐光源的基于螢屏空間的補償陰影,原理在于陰影計算階段,若開啟了光源的接觸陰影,會額外投射可見性判定射線,執行場景深度緩沖射的Ray Marching,以確定該像素是否被遮擋(在陰影中),從而獲得更加精準的陰影資訊,

左:關閉接觸陰影;右:開啟接觸陰影,

  • 距離場陰影(Distance Field Shadow)

距離場陰影故名意思就是利用預先生成的距離場資訊投射的陰影,

在使用需要場景距離場的特性之前,都需要在Project Setting中開啟Generate Mesh Distance Fields,包含距離場陰影、距離場碰撞、距離場AO等,

距離場陰影支持軟陰影、非直接陰影、遠距離陰影等等,還可以結合級聯陰影實作更加細膩接*真實的陰影效果(下圖),

上:級聯陰影效果;下:級聯陰影+距離場陰影,

下表是傳統陰影和距離場陰影的性能對比(GPU是 Radeon 7870,解析度是1080p,單位是ms):

場景 級聯、立方體陰影圖消耗 距離場陰影消耗 速度提升
*行光,10k距離單位,3級CSM 3.1 2.3 25%
*行光,30k距離單位,6級CSM 4.9 2.8 43%
擁有超大半徑的點光源 1.8 1.3 30%
5個擁有小半徑的點光源 3.2 1.8 45%

由此可見,距離場陰影的渲染性能會高一籌,但會增加場景構建時間,增加磁盤、記憶體、顯存消耗,

5.6.2 陰影基礎型別

在進入陰影渲染剖析前,先詳細理解陰影相關的部分關鍵概念:

// Engine\Source\Runtime\Renderer\Private\ShadowRendering.h

// 投射陰影資訊.
class FProjectedShadowInfo : public FRefCountedObject
{
public:
    typedef TArray<const FPrimitiveSceneInfo*,SceneRenderingAllocator> PrimitiveArrayType;

    // 渲染陰影時使用的view.
    FViewInfo* ShadowDepthView;
    // 陰影必須渲染的主view, Null表示獨立于view的陰影.
    FViewInfo* DependentView;

    // 陰影Uniform Buffer.
    TUniformBufferRef<FShadowDepthPassUniformParameters> ShadowDepthPassUniformBuffer;
    TUniformBufferRef<FMobileShadowDepthPassUniformParameters> MobileShadowDepthPassUniformBuffer;

    // 陰影圖渲染紋理(深度或顏色).
    FShadowMapRenderTargets RenderTargets;
    // FVisibleLightInfo::AllProjectedShadows的索引.
    int32 ShadowId;
    
    // 快取模式.
    EShadowDepthCacheMode CacheMode;
    // 變換陰影矩陣之前須偏移的位置.
    FVector PreShadowTranslation;
    // 陰影的視圖矩陣, 用來代替DependentView的視圖矩陣.
    FMatrix ShadowViewMatrix;
    // 主體和接收者矩陣, 用來渲染陰影深度緩沖的矩陣.
    FMatrix SubjectAndReceiverMatrix;
    FMatrix ReceiverMatrix;
    FMatrix InvReceiverMatrix;
    
    float InvMaxSubjectDepth;

    // 主體深度擴展, 世界空間的單位. 可用于轉換陰影深度值到世界空間.
    float MaxSubjectZ;
    float MinSubjectZ;
    float MinPreSubjectZ;

    // 包含所有潛在的陰影投射者的錐體.
    FConvexVolume CasterFrustum;
    FConvexVolume ReceiverFrustum;
    // 陰影的球體包圍盒.
    FSphere ShadowBounds;
    // 級聯陰影設定.
    FShadowCascadeSettings CascadeSettings;
    
    // 邊界尺寸, 防止在圖集(atlas)中過濾陰影時引入錯誤的效果.
    uint32 BorderSize;
    // 陰影在深度緩沖(或atlas)中的位置(偏移), 實際的陰影圖內容在: X + BorderSize, Y + BorderSize.
    uint32 X;
    uint32 Y;
    // 陰影的解析度, 包含了邊界. 實際分配的陰影解析度是: ResolutionX + 2 * BorderSize, ResolutionY + 2 * BorderSize.
    uint32 ResolutionX;
    uint32 ResolutionY;

    // 最大螢屏百分比, 取任意一個view的寬或高的最大值.
    float MaxScreenPercent;
    // 每個view的過渡值.
    TArray<float, TInlineAllocator<2> > FadeAlphas;

    // 陰影是否在深度緩沖區分配過, 若是, 則X和Y屬性將被初始化過.
    uint32 bAllocated : 1;
    // 陰影投射是否已被渲染過.
    uint32 bRendered : 1;
    
    // 陰影是否已在preshadow快取中分配過, 若是, 則X和Y就是preshadow快取深度buffer的偏移.
    uint32 bAllocatedInPreshadowCache : 1;
    // 陰影是否在preshadow快取中且它的深度已被更新.
    uint32 bDepthsCached : 1;

    uint32 bDirectionalLight : 1;
    
    // 是否在同一個通道中渲染完cubemap的所有面的點光源陰影.
    uint32 bOnePassPointLightShadow : 1;
    // 是影響整個場景還是一組物體的陰影.
    uint32 bWholeSceneShadow : 1;
    // 是否RSM(ReflectiveShadowmap).
    uint32 bReflectiveShadowmap : 1; 
    // 是否透明物體陰影.
    uint32 bTranslucentShadow : 1;
    // 是否膠囊體陰影.
    uint32 bCapsuleShadow : 1;
    // 是否預陰影, 預陰影是處理靜態環境投射到動態接收者的逐物體陰影.
    uint32 bPreShadow : 1;
    // 是否只有自陰影, 若是, 則不會投影到自身之外的物體, 擁有高質量陰影(適用于第一人稱游戲).
    uint32 bSelfShadowOnly : 1;
    // 是否逐物體不透明陰影.
    uint32 bPerObjectOpaqueShadow : 1;
    // 是否開啟背光傳輸.
    uint32 bTransmission : 1;

    // 用于點光源渲染cubemap6個面的陰影圖使用的視圖投影矩陣.
    TArray<FMatrix> OnePassShadowViewProjectionMatrices;
    // 用于點光源渲染cubemap6個面的陰影圖使用的視圖矩陣.
    TArray<FMatrix> OnePassShadowViewMatrices;
    /** Frustums for each cubemap face, used for object culling one pass point light shadows. */
    TArray<FConvexVolume> OnePassShadowFrustums;

    (......)

    // 控制逐物體陰影之外的過渡引數, 防止遠處出現超級銳利的陰影.
    float PerObjectShadowFadeStart;
    float InvPerObjectShadowFadeLength;

public:
    // 設定逐物體陰影.
    bool SetupPerObjectProjection(FLightSceneInfo* InLightSceneInfo, ...);
    // 設定全場景(全景)陰影.
    void SetupWholeSceneProjection(FLightSceneInfo* InLightSceneInfo, ...);

    // 渲染不透明物體的陰影深度.
    void RenderDepth(FRHICommandListImmediate& RHICmdList, ...);
    // 渲染透明物體的陰影深度.
    void RenderTranslucencyDepths(FRHICommandList& RHICmdList, ...);
    // 為特殊的view渲染投射到場景的陰影.
    void RenderProjection(FRHICommandListImmediate& RHICmdList, ...) const;
    // 渲染單通道點光源陰影.
    void RenderOnePassPointLightProjection(FRHICommandListImmediate& RHICmdList, ...) const;
    void RenderFrustumWireframe(FPrimitiveDrawInterface* PDI) const;
    
    // 渲染狀態介面.
    void SetStateForView(FRHICommandList& RHICmdList) const;
    void SetStateForDepth(FMeshPassProcessorRenderState& DrawRenderState) const;
    void ClearDepth(FRHICommandList& RHICmdList, class FSceneRenderer* SceneRenderer, ...);
    static FRHIBlendState* GetBlendStateForProjection(int32 ShadowMapChannel, ...);
    FRHIBlendState* GetBlendStateForProjection(bool bProjectingForForwardShading, ...) const;

    // 增加需要投射陰影的主體圖元.
    void AddSubjectPrimitive(FPrimitiveSceneInfo* PrimitiveSceneInfo, TArray<FViewInfo>* ViewArray, ...);
    // 增加陰影接收者圖元.
    void AddReceiverPrimitive(FPrimitiveSceneInfo* PrimitiveSceneInfo);

    // 為所有須投射陰影的圖元收集動態網格元素.
    void GatherDynamicMeshElements(FSceneRenderer& Renderer, ...);
    // 將DynamicMeshElement轉換成FMeshDrawCommand.
    void SetupMeshDrawCommandsForShadowDepth(FSceneRenderer& Renderer, ...);
    void SetupMeshDrawCommandsForProjectionStenciling(FSceneRenderer& Renderer);
    
    // 從記憶體池中創建一個新的view且在ShadowDepthView中快取起來, 用以渲染陰影深度.
    void SetupShadowDepthView(FRHICommandListImmediate& RHICmdList, FSceneRenderer* SceneRenderer);
    // 設定和更新Uniform Buffer.
    void SetupShadowUniformBuffers(FRHICommandListImmediate& RHICmdList, FScene* Scene, ...);
    // 保證快取的陰影圖處于EReadable狀態.
    void TransitionCachedShadowmap(FRHICommandListImmediate& RHICmdList, FScene* Scene);
    
     // 計算和更新ShaderDepthBias和ShaderSlopeDepthBias.
    void UpdateShaderDepthBias();
    // 計算PCF比較引數.
    float ComputeTransitionSize() const;

     // 資料獲取和操作介面.
    float GetShaderDepthBias() const;
    float GetShaderSlopeDepthBias() const;
    float GetShaderMaxSlopeDepthBias() const;
    float GetShaderReceiverDepthBias() const;
    
    bool HasSubjectPrims() const;
    bool SubjectsVisible(const FViewInfo& View) const;
    void ClearTransientArrays();
    friend uint32 GetTypeHash(const FProjectedShadowInfo* ProjectedShadowInfo);
    
    FMatrix GetScreenToShadowMatrix(const FSceneView& View) const;
    FMatrix GetScreenToShadowMatrix(const FSceneView& View, ...) const;
    FMatrix GetWorldToShadowMatrix(FVector4& ShadowmapMinMax, ...) const;
    FIntPoint GetShadowBufferResolution() const
    
    bool IsWholeSceneDirectionalShadow() const;
    bool IsWholeScenePointLightShadow() const;
    const FLightSceneInfo& GetLightSceneInfo() const;
    const FLightSceneInfoCompact& GetLightSceneInfoCompact() const;
    const FPrimitiveSceneInfo* GetParentSceneInfo() const;
    FShadowDepthType GetShadowDepthType() const;

    (......)
    
private:
    const FLightSceneInfo* LightSceneInfo;
    FLightSceneInfoCompact LightSceneInfoCompact;
    const FPrimitiveSceneInfo* ParentSceneInfo;

    // 陰影投射圖元串列.
    PrimitiveArrayType DynamicSubjectPrimitives;
    // 接收者圖元, 只在preshadow有效.
    PrimitiveArrayType ReceiverPrimitives;
    // 透明陰影投射圖元串列.
    PrimitiveArrayType SubjectTranslucentPrimitives;

    // 投射陰影的圖元對應的網格元素.
    TArray<FMeshBatchAndRelevance,SceneRenderingAllocator> DynamicSubjectMeshElements;
    TArray<FMeshBatchAndRelevance,SceneRenderingAllocator> DynamicSubjectTranslucentMeshElements;

    TArray<const FStaticMeshBatch*, SceneRenderingAllocator> SubjectMeshCommandBuildRequests;

    // DynamicSubjectMeshElements數量.
    int32 NumDynamicSubjectMeshElements;
    // SubjectMeshCommandBuildRequests數量.
    int32 NumSubjectMeshCommandBuildRequestElements;

    // 繪制陰影所需的繪制命令/渲染狀態等.
    FMeshCommandOneFrameArray ShadowDepthPassVisibleCommands;
    FParallelMeshDrawCommandPass ShadowDepthPass;
    TArray<FShadowMeshDrawCommandPass, TInlineAllocator<2>> ProjectionStencilingPasses;
    FDynamicMeshDrawCommandStorage DynamicMeshDrawCommandStorage;
    FGraphicsMinimalPipelineStateSet GraphicsMinimalPipelineStateSet;
    bool NeedsShaderInitialisation;

    // 陰影渲染時的偏移值. 會被UpdateShaderDepthBias()設定, 被GetShaderDepthBias()獲取, -1表示未初始化.
    float ShaderDepthBias;
    float ShaderSlopeDepthBias;
    float ShaderMaxSlopeDepthBias;

    // 內部介面
    void CopyCachedShadowMap(FRHICommandList& RHICmdList, ...);
    void RenderDepthInner(FRHICommandListImmediate& RHICmdList, ...);
    void ModifyViewForShadow(FRHICommandList& RHICmdList, FViewInfo* FoundView) const;
    FViewInfo* FindViewForShadow(FSceneRenderer* SceneRenderer) const;
    void AddCachedMeshDrawCommandsForPass(int32 PrimitiveIndex, ...);
    bool ShouldDrawStaticMeshes(FViewInfo& InCurrentView, ...);
    void GetShadowTypeNameForDrawEvent(FString& TypeName) const;
    int32 UpdateShadowCastingObjectBuffers() const;
    void GatherDynamicMeshElementsArray(FViewInfo* FoundView, ...);
    void SetupFrustumForProjection(const FViewInfo* View, ...) const;
    void SetupProjectionStencilMask(FRHICommandListImmediate& RHICmdList, ...) const;
};

由上面的代碼可知,FProjectedShadowInfo幾乎囊括了陰影處理和渲染所需的重要資料和操作介面,當然,UE的陰影系統太過復雜,單單它一個,還不足以解決陰影的所有渲染功能,下面繼續分析其它基礎或關鍵性型別:

// Engine\Source\Runtime\Renderer\Private\SceneRendering.h

// 視圖[不]相關的可見光源資訊, 主要是陰影相關的資訊.
class FVisibleLightInfo
{
public:
    // 在場景記憶體堆疊(mem stack)分配和管理的投射陰影資訊.
    TArray<FProjectedShadowInfo*,SceneRenderingAllocator> MemStackProjectedShadows;
    // 所有可見的投射陰影資訊, 由陰影設定階段輸出, 不是所有的都會被渲染.
    TArray<FProjectedShadowInfo*,SceneRenderingAllocator> AllProjectedShadows;

    // 特殊的陰影投射資訊, 用于專用的特性, 如投射物/膠囊體陰影/RSM等.
    TArray<FProjectedShadowInfo*,SceneRenderingAllocator> ShadowsToProject;
    TArray<FProjectedShadowInfo*,SceneRenderingAllocator> CapsuleShadowsToProject;
    TArray<FProjectedShadowInfo*,SceneRenderingAllocator> RSMsToProject;

    // 所有可見的投射預陰影. 這些不在場景的記憶體堆疊中分配和管理, 所以需要用TRefCountPtr參考計數.
    TArray<TRefCountPtr<FProjectedShadowInfo>,SceneRenderingAllocator> ProjectedPreShadows;
    // 被遮擋的逐物體陰影, 為了提交遮擋剔除申請所以需要追蹤它們.
    TArray<FProjectedShadowInfo*,SceneRenderingAllocator> OccludedPerObjectShadows;
};

// 視圖相關的可見光源資訊.
class FVisibleLightViewInfo
{
public:
    // 光源能夠影響到的可見圖元.
    TArray<FPrimitiveSceneInfo*,SceneRenderingAllocator> VisibleDynamicLitPrimitives;
    // 對應FVisibleLightInfo::AllProjectedShadows的陰影可見性映射表.
    FSceneBitArray ProjectedShadowVisibilityMap;
    // 對應FVisibleLightInfo::AllProjectedShadows的陰影ViewRelevance.
    TArray<FPrimitiveViewRelevance,SceneRenderingAllocator> ProjectedShadowViewRelevanceMap;
    // 是否在視錐體內. (*行光/天空光總是true)
    uint32 bInViewFrustum : 1;

    (......)
};

// Engine\Source\Runtime\Renderer\Private\ScenePrivate.h

class FSceneViewState
{
public:
    // 投射陰影的鍵值. 主要用于比較兩個投射陰影實體是否一樣.
    class FProjectedShadowKey
    {
    public:
        // 鍵值比較介面.
        inline bool operator == (const FProjectedShadowKey &Other) const
        {
            return (PrimitiveId == Other.PrimitiveId && Light == Other.Light && ShadowSplitIndex == Other.ShadowSplitIndex && bTranslucentShadow == Other.bTranslucentShadow);
        }
        // 鍵值哈希介面.
        friend inline uint32 GetTypeHash(const FSceneViewState::FProjectedShadowKey& Key)
        {
            return PointerHash(Key.Light,GetTypeHash(Key.PrimitiveId));
        }

    private:
        // 陰影的圖元id.
        FPrimitiveComponentId PrimitiveId;
        // 陰影的光源.
        const ULightComponent* Light;
        // 陰影在陰影圖集中的索引.
        int32 ShadowSplitIndex;
        // 是否透明陰影.
        bool bTranslucentShadow;
    };
};

5.6.3 陰影初始化

本節將花大篇幅分析陰影的初始化,如果不想看冗余的代碼分析的童鞋,可以直接跳到5.6.3.12 陰影初始化總結

陰影的初始化位于InitViews階段,呼叫堆疊示意圖如下:

void FDeferredShadingSceneRenderer::Render(FRHICommandListImmediate& RHICmdList)
{
    bool FDeferredShadingSceneRenderer::InitViews(RHICmdList, ...)
    {
        void FDeferredShadingSceneRenderer::InitViewsPossiblyAfterPrepass(FRHICommandListImmediate& RHICmdList, ...)
        {
            // 初始化動態陰影.
            void FSceneRenderer::InitDynamicShadows(FRHICommandListImmediate& RHICmdList, ...)
            {
                (......)
            }
        }
    }
}

5.6.3.1 InitDynamicShadows

下面是FSceneRenderer::InitDynamicShadows的代碼分析(節選):

// Engine\Source\Runtime\Renderer\Private\ShadowSetup.cpp

void FSceneRenderer::InitDynamicShadows(FRHICommandListImmediate& RHICmdList, FGlobalDynamicIndexBuffer& DynamicIndexBuffer, FGlobalDynamicVertexBuffer& DynamicVertexBuffer, FGlobalDynamicReadBuffer& DynamicReadBuffer)
{
    // 初始化各類標記和數量.
    const bool bMobile = FeatureLevel < ERHIFeatureLevel::SM5;
    bool bStaticSceneOnly = false;
    for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
    {
        FViewInfo& View = Views[ViewIndex];
        bStaticSceneOnly = bStaticSceneOnly || View.bStaticSceneOnly;
    }

    const bool bProjectEnablePointLightShadows = Scene->ReadOnlyCVARCache.bEnablePointLightShadows;
    uint32 NumPointShadowCachesUpdatedThisFrame = 0;
    uint32 NumSpotShadowCachesUpdatedThisFrame = 0;

    // 預計算陰影.
    TArray<FProjectedShadowInfo*,SceneRenderingAllocator> PreShadows;
    // 視圖關聯的全景陰影.
    TArray<FProjectedShadowInfo*,SceneRenderingAllocator> ViewDependentWholeSceneShadows;
    // 視圖關聯的需要裁剪的全景陰影.
    TArray<FProjectedShadowInfo*,SceneRenderingAllocator> ViewDependentWholeSceneShadowsThatNeedCulling;
    {
        // 遍歷所有光源, 將不同型別的光源加入不同型別的待渲染的陰影串列中.
        for (TSparseArray<FLightSceneInfoCompact>::TConstIterator LightIt(Scene->Lights); LightIt; ++LightIt)
        {
            const FLightSceneInfoCompact& LightSceneInfoCompact = *LightIt;
            FLightSceneInfo* LightSceneInfo = LightSceneInfoCompact.LightSceneInfo;

            FScopeCycleCounter Context(LightSceneInfo->Proxy->GetStatId());

            FVisibleLightInfo& VisibleLightInfo = VisibleLightInfos[LightSceneInfo->Id];

            // LightOcclusionType有陰影圖和光追兩種, 如果非陰影圖型別, 則忽略.
            const FLightOcclusionType OcclusionType = GetLightOcclusionType(LightSceneInfoCompact);
            if (OcclusionType != FLightOcclusionType::Shadowmap)
                continue;

            // 如果光源沒有開啟陰影或陰影質量太小, 則忽略陰影圖.
            if ((LightSceneInfoCompact.bCastStaticShadow || LightSceneInfoCompact.bCastDynamicShadow) && GetShadowQuality() > 0)
            {
                // 檢測該光源是否在某個view里可見, 如果不可見, 則忽略.
                bool bIsVisibleInAnyView = false;
                for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
                {
                    bIsVisibleInAnyView = LightSceneInfo->ShouldRenderLight(Views[ViewIndex]);

                    if (bIsVisibleInAnyView) 
                    {
                        break;
                    }
                }

                // 所有裁剪條件都通過了, 處理光源的陰影.
                if (bIsVisibleInAnyView)
                {
                    // 初始化陰影的各種標記和變數.
                    static const auto AllowStaticLightingVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.AllowStaticLighting"));
                    const bool bAllowStaticLighting = (!AllowStaticLightingVar || AllowStaticLightingVar->GetValueOnRenderThread() != 0);
                    // 是否點光源陰影. 注意矩形光也當做點光源處理.
                    const bool bPointLightShadow = LightSceneInfoCompact.LightType == LightType_Point || LightSceneInfoCompact.LightType == LightType_Rect;

                    // 對不預計算陰影的移動光源只創建全景陰影(whole scene shadow).
                    const bool bShouldCreateShadowForMovableLight = 
                        LightSceneInfoCompact.bCastDynamicShadow
                        && (!LightSceneInfo->Proxy->HasStaticShadowing() || !bAllowStaticLighting);

                    const bool bCreateShadowForMovableLight = 
                        bShouldCreateShadowForMovableLight
                        && (!bPointLightShadow || bProjectEnablePointLightShadows);

                    // 對帶有預計算陰影的尚未構建的光源創建全景陰影.
                    const bool bShouldCreateShadowToPreviewStaticLight =
                        LightSceneInfo->Proxy->HasStaticShadowing()
                        && LightSceneInfoCompact.bCastStaticShadow
                        && !LightSceneInfo->IsPrecomputedLightingValid();                        

                    const bool bCreateShadowToPreviewStaticLight = 
                        bShouldCreateShadowToPreviewStaticLight                        
                        && (!bPointLightShadow || bProjectEnablePointLightShadows);

                    // 對需要靜態陰影但由于重疊導致沒有有效陰影圖的光源創建全景陰影.
                    const bool bShouldCreateShadowForOverflowStaticShadowing =
                        LightSceneInfo->Proxy->HasStaticShadowing()
                        && !LightSceneInfo->Proxy->HasStaticLighting()
                        && LightSceneInfoCompact.bCastStaticShadow
                        && LightSceneInfo->IsPrecomputedLightingValid()
                        && LightSceneInfo->Proxy->GetShadowMapChannel() == INDEX_NONE;

                    const bool bCreateShadowForOverflowStaticShadowing =
                        bShouldCreateShadowForOverflowStaticShadowing
                        && (!bPointLightShadow || bProjectEnablePointLightShadows);

                    // 添加點光源的全景陰影.
                    const bool bPointLightWholeSceneShadow = (bShouldCreateShadowForMovableLight || bShouldCreateShadowForOverflowStaticShadowing || bShouldCreateShadowToPreviewStaticLight) && bPointLightShadow;
                    if (bPointLightWholeSceneShadow)
                    {                        
                        UsedWholeScenePointLightNames.Add(LightSceneInfoCompact.LightSceneInfo->Proxy->GetComponentName());
                    }

                    // 創建光源的全景陰影.
                    if (bCreateShadowForMovableLight || bCreateShadowToPreviewStaticLight || bCreateShadowForOverflowStaticShadowing)
                    {
                        CreateWholeSceneProjectedShadow(LightSceneInfo, NumPointShadowCachesUpdatedThisFrame, NumSpotShadowCachesUpdatedThisFrame);
                    }

                    // 允許移動和固定的光源創建CSM(級聯陰影), 或者是尚未構建的靜態光源.
                    if ((!LightSceneInfo->Proxy->HasStaticLighting() && LightSceneInfoCompact.bCastDynamicShadow) || bCreateShadowToPreviewStaticLight)
                    {
                        // 增加視圖關聯的全景陰影.
                        if( !bMobile ||
                            ((LightSceneInfo->Proxy->UseCSMForDynamicObjects() || LightSceneInfo->Proxy->IsMovable()) 
                                && (LightSceneInfo == Scene->MobileDirectionalLights[0] || LightSceneInfo == Scene->MobileDirectionalLights[1] || LightSceneInfo == Scene->MobileDirectionalLights[2])))
                        {
                            AddViewDependentWholeSceneShadowsForView(ViewDependentWholeSceneShadows, ViewDependentWholeSceneShadowsThatNeedCulling, VisibleLightInfo, *LightSceneInfo);
                        }

                        // 處理互動陰影, 此處的互動是指光源和圖元之間的影響. 包含PerObject陰影、透明陰影、自陰影等. 
                        if( !bMobile || (LightSceneInfo->Proxy->CastsModulatedShadows() && !LightSceneInfo->Proxy->UseCSMForDynamicObjects()))
                        {
                            Scene->FlushAsyncLightPrimitiveInteractionCreation();

                            // 處理動態圖元的互動陰影.
                            for (FLightPrimitiveInteraction* Interaction = LightSceneInfo->GetDynamicInteractionOftenMovingPrimitiveList(false); Interaction; Interaction = Interaction->GetNextPrimitive())
                            {
                                SetupInteractionShadows(RHICmdList, Interaction, VisibleLightInfo, bStaticSceneOnly, ViewDependentWholeSceneShadows, PreShadows);
                            }

                            // 處理靜態圖元的互動陰影.
                            for (FLightPrimitiveInteraction* Interaction = LightSceneInfo->GetDynamicInteractionStaticPrimitiveList(false); Interaction; Interaction = Interaction->GetNextPrimitive())
                            {
                                SetupInteractionShadows(RHICmdList, Interaction, VisibleLightInfo, bStaticSceneOnly, ViewDependentWholeSceneShadows, PreShadows);
                            }
                        }
                    }
                }
            }
        }

        // 計算投射陰影的可見性.
        InitProjectedShadowVisibility(RHICmdList);
    }

    // 清理舊的預計算陰影, 嘗試增加新的到快取中.
    UpdatePreshadowCache(FSceneRenderTargets::Get(RHICmdList));

    // 收集圖元串列, 以繪制不同型別的陰影.
    GatherShadowPrimitives(PreShadows, ViewDependentWholeSceneShadowsThatNeedCulling, bStaticSceneOnly);

    // 分配陰影深度渲染紋理.
    AllocateShadowDepthTargets(RHICmdList);

    // 收集陰影的動態網格元素, 跟之前剖析的GatherDynamicMeshElements類似.
    GatherShadowDynamicMeshElements(DynamicIndexBuffer, DynamicVertexBuffer, DynamicReadBuffer);
}

5.6.3.2 CreateWholeSceneProjectedShadow

繼續分析上面代碼中涉及的幾個重要介面,首先是CreateWholeSceneProjectedShadow

void FSceneRenderer::CreateWholeSceneProjectedShadow(FLightSceneInfo* LightSceneInfo, uint32& InOutNumPointShadowCachesUpdatedThisFrame, uint32& InOutNumSpotShadowCachesUpdatedThisFrame)
{
    SCOPE_CYCLE_COUNTER(STAT_CreateWholeSceneProjectedShadow);
    FVisibleLightInfo& VisibleLightInfo = VisibleLightInfos[LightSceneInfo->Id];

    // 嘗試為光源創建全景投射陰影的初始化器.
    TArray<FWholeSceneProjectedShadowInitializer, TInlineAllocator<6> > ProjectedShadowInitializers;
    if (LightSceneInfo->Proxy->GetWholeSceneProjectedShadowInitializer(ViewFamily, ProjectedShadowInitializers))
    {
        FSceneRenderTargets& SceneContext_ConstantsOnly = FSceneRenderTargets::Get_FrameConstantsOnly();

        // 陰影解析度常量.
        const uint32 ShadowBorder = ProjectedShadowInitializers[0].bOnePassPointLightShadow ? 0 : SHADOW_BORDER;
        const uint32 EffectiveDoubleShadowBorder = ShadowBorder * 2;
        const uint32 MinShadowResolution = FMath::Max<int32>(0, CVarMinShadowResolution.GetValueOnRenderThread());
        const int32 MaxShadowResolutionSetting = GetCachedScalabilityCVars().MaxShadowResolution;
        const FIntPoint ShadowBufferResolution = SceneContext_ConstantsOnly.GetShadowDepthTextureResolution();
        const uint32 MaxShadowResolution = FMath::Min(MaxShadowResolutionSetting, ShadowBufferResolution.X) - EffectiveDoubleShadowBorder;
        const uint32 MaxShadowResolutionY = FMath::Min(MaxShadowResolutionSetting, ShadowBufferResolution.Y) - EffectiveDoubleShadowBorder;
        const uint32 ShadowFadeResolution = FMath::Max<int32>(0, CVarShadowFadeResolution.GetValueOnRenderThread());

        // 計算視圖內的陰影需要的最大解析度, 包含用于過渡的未限制解析度.
        float MaxDesiredResolution = 0;
        TArray<float, TInlineAllocator<2> > FadeAlphas;
        float MaxFadeAlpha = 0;
        bool bStaticSceneOnly = false;
        bool bAnyViewIsSceneCapture = false;
        for(int32 ViewIndex = 0, ViewCount = Views.Num(); ViewIndex < ViewCount; ++ViewIndex)
        {
            const FViewInfo& View = Views[ViewIndex];

            const float ScreenRadius = LightSceneInfo->Proxy->GetEffectiveScreenRadius(View.ShadowViewMatrices);

            // 計算解析度縮放因子UnclampedResolution.
            float UnclampedResolution = 1.0f;

            switch (LightSceneInfo->Proxy->GetLightType())
            {
            case LightType_Point:
                UnclampedResolution = ScreenRadius * CVarShadowTexelsPerPixelPointlight.GetValueOnRenderThread();
                break;
            case LightType_Spot:
                UnclampedResolution = ScreenRadius * CVarShadowTexelsPerPixelSpotlight.GetValueOnRenderThread();
                break;
            case LightType_Rect:
                UnclampedResolution = ScreenRadius * CVarShadowTexelsPerPixelRectlight.GetValueOnRenderThread();
                break;
            default:
                // *行光并不在此處理.
                checkf(false, TEXT("Unexpected LightType %d appears in CreateWholeSceneProjectedShadow %s"),
                    (int32)LightSceneInfo->Proxy->GetLightType(),
                    *LightSceneInfo->Proxy->GetComponentName().ToString());
            }

            // 在應用ShadowResolutionScale貢獻之前計算過渡因子FadeAlpha.
            const float FadeAlpha = CalculateShadowFadeAlpha( UnclampedResolution, ShadowFadeResolution, MinShadowResolution ) * LightSceneInfo->Proxy->GetShadowAmount();
            MaxFadeAlpha = FMath::Max(MaxFadeAlpha, FadeAlpha);
            FadeAlphas.Add(FadeAlpha);

            const float ShadowResolutionScale = LightSceneInfo->Proxy->GetShadowResolutionScale();

            float ClampedResolution = UnclampedResolution;

            if (ShadowResolutionScale > 1.0f)
            {
                ClampedResolution *= ShadowResolutionScale;
            }

            ClampedResolution = FMath::Min<float>(ClampedResolution, MaxShadowResolution);

            if (ShadowResolutionScale <= 1.0f)
            {
                ClampedResolution *= ShadowResolutionScale;
            }

            MaxDesiredResolution = FMath::Max(
                MaxDesiredResolution,
                FMath::Max<float>(
                    ClampedResolution,
                    FMath::Min<float>(MinShadowResolution, ShadowBufferResolution.X - EffectiveDoubleShadowBorder)
                    )
                );

            bStaticSceneOnly = bStaticSceneOnly || View.bStaticSceneOnly;
            bAnyViewIsSceneCapture = bAnyViewIsSceneCapture || View.bIsSceneCapture;
        }

        // 過渡因子大于閾值才創建陰影.
        if (MaxFadeAlpha > 1.0f / 256.0f)
        {
            Scene->FlushAsyncLightPrimitiveInteractionCreation();

            // 遍歷該光源的所有陰影初始化器, 根據級聯數量創建和設定ProjectedShadowInfo.
            for (int32 ShadowIndex = 0, ShadowCount = ProjectedShadowInitializers.Num(); ShadowIndex < ShadowCount; ShadowIndex++)
            {
                FWholeSceneProjectedShadowInitializer& ProjectedShadowInitializer = ProjectedShadowInitializers[ShadowIndex];

                int32 RoundedDesiredResolution = FMath::Max<int32>((1 << (FMath::CeilLogTwo(MaxDesiredResolution + 1.0f) - 1)) - ShadowBorder * 2, 1);
                int32 SizeX = MaxDesiredResolution >= MaxShadowResolution ? MaxShadowResolution : RoundedDesiredResolution;
                int32 SizeY = MaxDesiredResolution >= MaxShadowResolutionY ? MaxShadowResolutionY : RoundedDesiredResolution;

                if (ProjectedShadowInitializer.bOnePassPointLightShadow)
                {
                    // Round to a resolution that is supported for one pass point light shadows
                    SizeX = SizeY = SceneContext_ConstantsOnly.GetCubeShadowDepthZResolution(SceneContext_ConstantsOnly.GetCubeShadowDepthZIndex(MaxDesiredResolution));
                }

                int32 NumShadowMaps = 1;
                EShadowDepthCacheMode CacheMode[2] = { SDCM_Uncached, SDCM_Uncached };

                if (!bAnyViewIsSceneCapture && !ProjectedShadowInitializer.bRayTracedDistanceField)
                {
                    FIntPoint ShadowMapSize(SizeX + ShadowBorder * 2, SizeY + ShadowBorder * 2);

                    // 計算全景陰影的快取模式, 包含陰影圖的尺寸和數量等資料.
                    ComputeWholeSceneShadowCacheModes(
                        LightSceneInfo,
                        ProjectedShadowInitializer.bOnePassPointLightShadow,
                        ViewFamily.CurrentRealTime,
                        MaxDesiredResolution,
                        FIntPoint(MaxShadowResolution, MaxShadowResolutionY),
                        Scene,
                        // 下面是傳入或傳出引數, 可被介面內部改變.
                        ProjectedShadowInitializer,
                        ShadowMapSize,
                        InOutNumPointShadowCachesUpdatedThisFrame,
                        InOutNumSpotShadowCachesUpdatedThisFrame,
                        NumShadowMaps,
                        CacheMode);

                    SizeX = ShadowMapSize.X - ShadowBorder * 2;
                    SizeY = ShadowMapSize.Y - ShadowBorder * 2;
                }

                // 創建NumShadowMaps個陰影圖, 每個陰影圖的資料存于FProjectedShadowInfo實體中.
                for (int32 CacheModeIndex = 0; CacheModeIndex < NumShadowMaps; CacheModeIndex++)
                {
                    // 創建FProjectedShadowInfo實體.
                    FProjectedShadowInfo* ProjectedShadowInfo = new(FMemStack::Get(), 1, 16) FProjectedShadowInfo;

                    // 設定ProjectedShadowInfo的全景投射引數(視錐體邊界, 變換矩陣, 深度, 深度偏移等).
                    ProjectedShadowInfo->SetupWholeSceneProjection(
                        LightSceneInfo,
                        NULL,
                        ProjectedShadowInitializer,
                        SizeX,
                        SizeY,
                        ShadowBorder,
                        false    // no RSM
                        );

                    ProjectedShadowInfo->CacheMode = CacheMode[CacheModeIndex];
                    ProjectedShadowInfo->FadeAlphas = FadeAlphas;

                    // 加入可見光源的投射陰影串列.
                    VisibleLightInfo.MemStackProjectedShadows.Add(ProjectedShadowInfo);

                    // 單通道點光源陰影.
                    if (ProjectedShadowInitializer.bOnePassPointLightShadow)
                    {
                        const static FVector CubeDirections[6] =
                        {
                            FVector(-1, 0, 0),
                            FVector(1, 0, 0),
                            FVector(0, -1, 0),
                            FVector(0, 1, 0),
                            FVector(0, 0, -1),
                            FVector(0, 0, 1)
                        };

                        const static FVector UpVectors[6] =
                        {
                            FVector(0, 1, 0),
                            FVector(0, 1, 0),
                            FVector(0, 0, -1),
                            FVector(0, 0, 1),
                            FVector(0, 1, 0),
                            FVector(0, 1, 0)
                        };

                        const FLightSceneProxy& LightProxy = *(ProjectedShadowInfo->GetLightSceneInfo().Proxy);

                        const FMatrix FaceProjection = FPerspectiveMatrix(PI / 4.0f, 1, 1, 1, LightProxy.GetRadius());
                        const FVector LightPosition = LightProxy.GetPosition();

                        ProjectedShadowInfo->OnePassShadowViewMatrices.Empty(6);
                        ProjectedShadowInfo->OnePassShadowViewProjectionMatrices.Empty(6);
                        ProjectedShadowInfo->OnePassShadowFrustums.Empty(6);
                        ProjectedShadowInfo->OnePassShadowFrustums.AddZeroed(6);
                        const FMatrix ScaleMatrix = FScaleMatrix(FVector(1, -1, 1));
                        
                        // 利用投射錐體和遠*面填充所有(6個)面的資料.
                        ProjectedShadowInfo->CasterFrustum.Planes.Empty();
                        for (int32 FaceIndex = 0; FaceIndex < 6; FaceIndex++)
                        {
                            // 給每個面創建視圖投影矩陣.
                            const FMatrix WorldToLightMatrix = FLookAtMatrix(LightPosition, LightPosition + CubeDirections[FaceIndex], UpVectors[FaceIndex]) * ScaleMatrix;
                            ProjectedShadowInfo->OnePassShadowViewMatrices.Add(WorldToLightMatrix);
                            const FMatrix ShadowViewProjectionMatrix = WorldToLightMatrix * FaceProjection;
                            ProjectedShadowInfo->OnePassShadowViewProjectionMatrices.Add(ShadowViewProjectionMatrix);
                            // 創建凸面體積包圍錐體, 用來物體的快速裁剪.
                            GetViewFrustumBounds(ProjectedShadowInfo->OnePassShadowFrustums[FaceIndex], ShadowViewProjectionMatrix, false);

                            // 確保有有效的錐體.
                            if (ProjectedShadowInfo->OnePassShadowFrustums[FaceIndex].Planes.Num() > 0 )
                            {
                                // 假設了最后那個面是遠*面, 須包含PreShadowTranslation
                                FPlane Src = https://www.cnblogs.com/timlly/p/ProjectedShadowInfo->OnePassShadowFrustums[FaceIndex].Planes.Last();
                                // add world space preview translation
                                Src.W += (FVector(Src) | ProjectedShadowInfo->PreShadowTranslation);
                                ProjectedShadowInfo->CasterFrustum.Planes.Add(Src);
                            }
                        }
                        // 初始化投射錐體.
                        ProjectedShadowInfo->CasterFrustum.Init();
                    }

                    // 對非光追距離場陰影, 執行CPU側裁剪.
                    if (!ProjectedShadowInfo->bRayTracedDistanceField)
                    {
                        // 構建光源視圖的凸面體, 以裁剪陰影投射.
                        FLightViewFrustumConvexHulls LightViewFrustumConvexHulls;
                        if (CacheMode[CacheModeIndex] != SDCM_StaticPrimitivesOnly)
                        {
                            FVector const& LightOrigin = LightSceneInfo->Proxy->GetOrigin();
                            BuildLightViewFrustumConvexHulls(LightOrigin, Views, LightViewFrustumConvexHulls);
                        }

                        bool bCastCachedShadowFromMovablePrimitives = GCachedShadowsCastFromMovablePrimitives || LightSceneInfo->Proxy->GetForceCachedShadowsForMovablePrimitives();
                        if (CacheMode[CacheModeIndex] != SDCM_StaticPrimitivesOnly 
                            && (CacheMode[CacheModeIndex] != SDCM_MovablePrimitivesOnly || bCastCachedShadowFromMovablePrimitives))
                        {
                            // 將所有受光源影響的圖元加入到受影響的圖元串列(subject primitive list).
                            for (FLightPrimitiveInteraction* Interaction = LightSceneInfo->GetDynamicInteractionOftenMovingPrimitiveList(false);
                                Interaction;
                                Interaction = Interaction->GetNextPrimitive())
                            {
                                // 投射陰影且非自陰影(InsetShader)且和光源相交的圖元才加入subject primitive list.
                                if (Interaction->HasShadow()
                                    && !Interaction->CastsSelfShadowOnly()
                                    && (!bStaticSceneOnly || Interaction->GetPrimitiveSceneInfo()->Proxy->HasStaticLighting()))
                                {
                                    FBoxSphereBounds const& Bounds = Interaction->GetPrimitiveSceneInfo()->Proxy->GetBounds();
                                    if (IntersectsConvexHulls(LightViewFrustumConvexHulls, Bounds))
                                    {
                                        ProjectedShadowInfo->AddSubjectPrimitive(Interaction->GetPrimitiveSceneInfo(), &Views, FeatureLevel, false);
                                    }
                                }
                            }
                        }
                        
                        if (CacheMode[CacheModeIndex] != SDCM_MovablePrimitivesOnly)
                        {
                            // 將所有受陰影投射錐體影響的圖元添加到SubjectPrimitiveList.
                            for (FLightPrimitiveInteraction* Interaction = LightSceneInfo->GetDynamicInteractionStaticPrimitiveList(false);
                                Interaction;
                                Interaction = Interaction->GetNextPrimitive())
                            {
                                if (Interaction->HasShadow()
                                    && !Interaction->CastsSelfShadowOnly()
                                    && (!bStaticSceneOnly || Interaction->GetPrimitiveSceneInfo()->Proxy->HasStaticLighting()))
                                {
                                    FBoxSphereBounds const& Bounds = Interaction->GetPrimitiveSceneInfo()->Proxy->GetBounds();
                                    if (IntersectsConvexHulls(LightViewFrustumConvexHulls, Bounds))
                                    {
                                        ProjectedShadowInfo->AddSubjectPrimitive(Interaction->GetPrimitiveSceneInfo(), &Views, FeatureLevel, false);
                                    }
                                }
                            }
                        }
                    }

                    bool bRenderShadow = true;
                    
                    // 快取模式是否只有靜態圖元.
                    if (CacheMode[CacheModeIndex] == SDCM_StaticPrimitivesOnly)
                    {
                        const bool bHasStaticPrimitives = ProjectedShadowInfo->HasSubjectPrims();
                        // 靜態陰影不需要渲染.
                        bRenderShadow = bHasStaticPrimitives;
                        FCachedShadowMapData& CachedShadowMapData = Scene->CachedShadowMaps.FindChecked(ProjectedShadowInfo->GetLightSceneInfo().Id);
                        CachedShadowMapData.bCachedShadowMapHasPrimitives = bHasStaticPrimitives;
                    }

                    // 需要渲染陰影則添加到VisibleLightInfo.AllProjectedShadows串列中.
                    if (bRenderShadow)
                    {
                        VisibleLightInfo.AllProjectedShadows.Add(ProjectedShadowInfo);
                    }
                }
            }
        }
    }
}

5.6.3.3 AddViewDependentWholeSceneShadowsForView

下面將分析AddViewDependentWholeSceneShadowsForView

void FSceneRenderer::AddViewDependentWholeSceneShadowsForView(
    TArray<FProjectedShadowInfo*, SceneRenderingAllocator>& ShadowInfos, 
    TArray<FProjectedShadowInfo*, SceneRenderingAllocator>& ShadowInfosThatNeedCulling,
    FVisibleLightInfo& VisibleLightInfo, 
    FLightSceneInfo& LightSceneInfo)
{
    // 遍歷所有view, 給每個view創建view關聯的全景陰影.
    for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
    {
        FViewInfo& View = Views[ViewIndex];

        const float LightShadowAmount = LightSceneInfo.Proxy->GetShadowAmount();
        TArray<float, TInlineAllocator<2> > FadeAlphas;
        FadeAlphas.Init(0.0f, Views.Num());
        FadeAlphas[ViewIndex] = LightShadowAmount;    
        
        (......)

        // 如果是主視圖, 處理投射陰影.
        if (IStereoRendering::IsAPrimaryView(View))
        {
            const bool bExtraDistanceFieldCascade = LightSceneInfo.Proxy->ShouldCreateRayTracedCascade(View.GetFeatureLevel(), LightSceneInfo.IsPrecomputedLightingValid(), View.MaxShadowCascades);

            // 獲取視圖相關的投影數量.
            const int32 ProjectionCount = LightSceneInfo.Proxy->GetNumViewDependentWholeSceneShadows(View, LightSceneInfo.IsPrecomputedLightingValid()) + (bExtraDistanceFieldCascade?1:0);

            FSceneRenderTargets& SceneContext_ConstantsOnly = FSceneRenderTargets::Get_FrameConstantsOnly();

            // 根據投影數量, 創建投射陰影初始化器和對應的FProjectedShadowInfo.
            for (int32 Index = 0; Index < ProjectionCount; Index++)
            {
                FWholeSceneProjectedShadowInitializer ProjectedShadowInitializer;

                int32 LocalIndex = Index;

                // Indexing like this puts the ray traced shadow cascade last (might not be needed)
                if(bExtraDistanceFieldCascade && LocalIndex + 1 == ProjectionCount)
                {
                    LocalIndex = INDEX_NONE;
                }

                if (LightSceneInfo.Proxy->GetViewDependentWholeSceneProjectedShadowInitializer(View, LocalIndex, LightSceneInfo.IsPrecomputedLightingValid(), ProjectedShadowInitializer))
                {
                    const FIntPoint ShadowBufferResolution(
                    FMath::Clamp(GetCachedScalabilityCVars().MaxCSMShadowResolution, 1, (int32)GMaxShadowDepthBufferSizeX),
                    FMath::Clamp(GetCachedScalabilityCVars().MaxCSMShadowResolution, 1, (int32)GMaxShadowDepthBufferSizeY));

                    // 創建FProjectedShadowInfo實體.
                    FProjectedShadowInfo* ProjectedShadowInfo = new(FMemStack::Get(), 1, 16) FProjectedShadowInfo;
                    // 陰影邊界.
                    uint32 ShadowBorder = NeedsUnatlasedCSMDepthsWorkaround(FeatureLevel) ? 0 : SHADOW_BORDER;
                    // 設定全景投影.
                    ProjectedShadowInfo->SetupWholeSceneProjection(
                        &LightSceneInfo,
                        &View,
                        ProjectedShadowInitializer,
                        ShadowBufferResolution.X - ShadowBorder * 2,
                        ShadowBufferResolution.Y - ShadowBorder * 2,
                        ShadowBorder,
                        false    // no RSM
                        );

                    ProjectedShadowInfo->FadeAlphas = FadeAlphas;

                    // 將ProjectedShadowInfo添加到可見光源資訊的相關串列中.
                    FVisibleLightInfo& LightViewInfo = VisibleLightInfos[LightSceneInfo.Id];
                    VisibleLightInfo.MemStackProjectedShadows.Add(ProjectedShadowInfo);
                    VisibleLightInfo.AllProjectedShadows.Add(ProjectedShadowInfo);
                    ShadowInfos.Add(ProjectedShadowInfo);

                    // 添加到待裁剪陰影串列.
                    if (!ProjectedShadowInfo->bRayTracedDistanceField)
                    {
                        ShadowInfosThatNeedCulling.Add(ProjectedShadowInfo);
                    }
                }
            }

            // 處理RSM(Refletive Shadow Map), 用于LPV光照.
            FSceneViewState* ViewState = (FSceneViewState*)View.State;
            if (ViewState)
            {
                (......)
            }
        }
    }
}

5.6.3.4 SetupInteractionShadows

接著繼續分析SetupInteractionShadows

void FSceneRenderer::SetupInteractionShadows(
    FRHICommandListImmediate& RHICmdList,
    FLightPrimitiveInteraction* Interaction, 
    FVisibleLightInfo& VisibleLightInfo, 
    bool bStaticSceneOnly,
    const TArray<FProjectedShadowInfo*,SceneRenderingAllocator>& ViewDependentWholeSceneShadows,
    TArray<FProjectedShadowInfo*,SceneRenderingAllocator>& PreShadows)
{
    FPrimitiveSceneInfo* PrimitiveSceneInfo = Interaction->GetPrimitiveSceneInfo();
    FLightSceneProxy* LightProxy = Interaction->GetLight()->Proxy;
    extern bool GUseTranslucencyShadowDepths;

    bool bShadowHandledByParent = false;

    // 處理光源附加根組件, 如果存在附加根節點, 置bShadowHandledByParent為true.
    if (PrimitiveSceneInfo->LightingAttachmentRoot.IsValid())
    {
        FAttachmentGroupSceneInfo& AttachmentGroup = Scene->AttachmentGroups.FindChecked(PrimitiveSceneInfo->LightingAttachmentRoot);
        bShadowHandledByParent = AttachmentGroup.ParentSceneInfo && AttachmentGroup.ParentSceneInfo->Proxy->LightAttachmentsAsGroup();
    }

    // bShadowHandledByParent的陰影會被父節點組件處理.
    if (!bShadowHandledByParent)
    {
        const bool bCreateTranslucentObjectShadow = GUseTranslucencyShadowDepths && Interaction->HasTranslucentObjectShadow();
        const bool bCreateInsetObjectShadow = Interaction->HasInsetObjectShadow();
        const bool bCreateObjectShadowForStationaryLight = ShouldCreateObjectShadowForStationaryLight(Interaction->GetLight(), PrimitiveSceneInfo->Proxy, Interaction->IsShadowMapped());

        if (Interaction->HasShadow() 
            && (!bStaticSceneOnly || PrimitiveSceneInfo->Proxy->HasStaticLighting())
            && (bCreateTranslucentObjectShadow || bCreateInsetObjectShadow || bCreateObjectShadowForStationaryLight))
        {
            // 創建逐物體陰影.
            CreatePerObjectProjectedShadow(RHICmdList, Interaction, bCreateTranslucentObjectShadow, bCreateInsetObjectShadow || bCreateObjectShadowForStationaryLight, ViewDependentWholeSceneShadows, PreShadows);
        }
    }
}

5.6.3.5 CreatePerObjectProjectedShadow

上面代碼涉及了CreatePerObjectProjectedShadow,進入其實作代碼分析:

void FSceneRenderer::CreatePerObjectProjectedShadow(
    FRHICommandListImmediate& RHICmdList,
    FLightPrimitiveInteraction* Interaction, 
    bool bCreateTranslucentObjectShadow, 
    bool bCreateOpaqueObjectShadow,
    const TArray<FProjectedShadowInfo*,SceneRenderingAllocator>& ViewDependentWholeSceneShadows,
    TArray<FProjectedShadowInfo*, SceneRenderingAllocator>& OutPreShadows)
{
    FPrimitiveSceneInfo* PrimitiveSceneInfo = Interaction->GetPrimitiveSceneInfo();
    const int32 PrimitiveId = PrimitiveSceneInfo->GetIndex();

    FLightSceneInfo* LightSceneInfo = Interaction->GetLight();
    FVisibleLightInfo& VisibleLightInfo = VisibleLightInfos[LightSceneInfo->Id];

    // 檢測陰影是否在任意一個view內可見.
    bool bShadowIsPotentiallyVisibleNextFrame = false;
    bool bOpaqueShadowIsVisibleThisFrame = false;
    bool bSubjectIsVisible = false;
    bool bOpaque = false;
    bool bTranslucentRelevance = false;
    bool bTranslucentShadowIsVisibleThisFrame = false;
    int32 NumBufferedFrames = FOcclusionQueryHelpers::GetNumBufferedFrames(FeatureLevel);

    for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
    {
        const FViewInfo& View = Views[ViewIndex];

        // 獲取圖元的快取ViewRelevance.
        FPrimitiveViewRelevance ViewRelevance = View.PrimitiveViewRelevanceMap[PrimitiveId];

        if (!ViewRelevance.bInitializedThisFrame)
        {
            // Compute the subject primitive's view relevance since it wasn't cached
            ViewRelevance = PrimitiveSceneInfo->Proxy->GetViewRelevance(&View);
        }

        // Check if the subject primitive is shadow relevant.
        const bool bPrimitiveIsShadowRelevant = ViewRelevance.bShadowRelevance;

        // 不透明物體陰影鍵值, 依次是: 圖元id, 光源組件地址, 陰影拆分后的索引, 是否透明陰影.
        const FSceneViewState::FProjectedShadowKey OpaqueKey(PrimitiveSceneInfo->PrimitiveComponentId, LightSceneInfo->Proxy->GetLightComponent(), INDEX_NONE, false);

        // 檢測不透明物體陰影或預陰影是否被遮擋.
        const bool bOpaqueShadowIsOccluded = 
            !bCreateOpaqueObjectShadow ||
            (
                !View.bIgnoreExistingQueries &&    View.State &&
                ((FSceneViewState*)View.State)->IsShadowOccluded(RHICmdList, OpaqueKey, NumBufferedFrames)
            );

        // 透明物體陰影排序鍵值.
        const FSceneViewState::FProjectedShadowKey TranslucentKey(PrimitiveSceneInfo->PrimitiveComponentId, LightSceneInfo->Proxy->GetLightComponent(), INDEX_NONE, true);

        // 檢測透明物體陰影或預陰影是否被遮擋.
        const bool bTranslucentShadowIsOccluded = 
            !bCreateTranslucentObjectShadow ||
            (
                !View.bIgnoreExistingQueries && View.State &&
                ((FSceneViewState*)View.State)->IsShadowOccluded(RHICmdList, TranslucentKey, NumBufferedFrames)
            );

        // 忽略不在主Pass渲染的圖元.
        if (PrimitiveSceneInfo->Proxy->ShouldRenderInMainPass())
        {
            const bool bSubjectIsVisibleInThisView = View.PrimitiveVisibilityMap[PrimitiveSceneInfo->GetIndex()];
            bSubjectIsVisible |= bSubjectIsVisibleInThisView;
        }

        // 陰影如果與視圖關聯且未被遮擋, 則視為可見.
        bOpaqueShadowIsVisibleThisFrame |= (bPrimitiveIsShadowRelevant && !bOpaqueShadowIsOccluded);
        bTranslucentShadowIsVisibleThisFrame |= (bPrimitiveIsShadowRelevant && !bTranslucentShadowIsOccluded);
        bShadowIsPotentiallyVisibleNextFrame |= bPrimitiveIsShadowRelevant;
        bOpaque |= ViewRelevance.bOpaque;
        bTranslucentRelevance |= ViewRelevance.HasTranslucency();
    } // for

    // 如果本幀不可見且下一幀也沒有潛在可見, 則直接回傳, 以跳過后續的陰影創建和設定.
    if (!bOpaqueShadowIsVisibleThisFrame && !bTranslucentShadowIsVisibleThisFrame && !bShadowIsPotentiallyVisibleNextFrame)
    {
        return;
    }

    // 收集陰影組圖元.
    TArray<FPrimitiveSceneInfo*, SceneRenderingAllocator> ShadowGroupPrimitives;
    PrimitiveSceneInfo->GatherLightingAttachmentGroupPrimitives(ShadowGroupPrimitives);

#if ENABLE_NAN_DIAGNOSTIC
    // 沒有有效的陰影組圖元, 直接回傳.
    if (ShadowGroupPrimitives.Num() == 0)
    {
        return;
    }
#endif

    // 計算該組的陰影圖元的組合包圍盒.
    FBoxSphereBounds OriginalBounds = ShadowGroupPrimitives[0]->Proxy->GetBounds();
    // 修正非法的包圍盒.
    if (!ensureMsgf(OriginalBounds.ContainsNaN() == false, TEXT("OriginalBound contains NaN : %s"), *OriginalBounds.ToString()))
    {
        OriginalBounds = FBoxSphereBounds(FVector::ZeroVector, FVector(1.f), 1.f);
    }
    for (int32 ChildIndex = 1; ChildIndex < ShadowGroupPrimitives.Num(); ChildIndex++)
    {
        const FPrimitiveSceneInfo* ShadowChild = ShadowGroupPrimitives[ChildIndex];
        if (ShadowChild->Proxy->CastsDynamicShadow())
        {
            FBoxSphereBounds ChildBound = ShadowChild->Proxy->GetBounds();
            OriginalBounds = OriginalBounds + ChildBound;

            if (!ensureMsgf(OriginalBounds.ContainsNaN() == false, TEXT("Child %s contains NaN : %s"), *ShadowChild->Proxy->GetOwnerName().ToString(), *ChildBound.ToString()))
            {
                // fix up OriginalBounds. This is going to cause flickers
                OriginalBounds = FBoxSphereBounds(FVector::ZeroVector, FVector(1.f), 1.f);
            }
        }
    }

    // 下面的代碼和CreateWholeSceneProjectedShadow比較相似, 將省略分析...
    FSceneRenderTargets& SceneContext = FSceneRenderTargets::Get(RHICmdList);
    
    // 陰影常量.
    const uint32 MaxShadowResolutionSetting = GetCachedScalabilityCVars().MaxShadowResolution;
    const FIntPoint ShadowBufferResolution = SceneContext.GetShadowDepthTextureResolution();
    const uint32 MaxShadowResolution = FMath::Min<int32>(MaxShadowResolutionSetting, ShadowBufferResolution.X) - SHADOW_BORDER * 2;
    const uint32 MaxShadowResolutionY = FMath::Min<int32>(MaxShadowResolutionSetting, ShadowBufferResolution.Y) - SHADOW_BORDER * 2;
    const uint32 MinShadowResolution     = FMath::Max<int32>(0, CVarMinShadowResolution.GetValueOnRenderThread());
    const uint32 ShadowFadeResolution    = FMath::Max<int32>(0, CVarShadowFadeResolution.GetValueOnRenderThread());
    const uint32 MinPreShadowResolution  = FMath::Max<int32>(0, CVarMinPreShadowResolution.GetValueOnRenderThread());
    const uint32 PreShadowFadeResolution = FMath::Max<int32>(0, CVarPreShadowFadeResolution.GetValueOnRenderThread());
    
    // 陰影最大解析度.
    uint32 MaxDesiredResolution = 0;
    float MaxScreenPercent = 0;
    TArray<float, TInlineAllocator<2> > ResolutionFadeAlphas;
    TArray<float, TInlineAllocator<2> > ResolutionPreShadowFadeAlphas;
    float MaxResolutionFadeAlpha = 0;
    float MaxResolutionPreShadowFadeAlpha = 0;

    (......)

    FBoxSphereBounds Bounds = OriginalBounds;

    // 是否渲染預陰影(陰影快取).
    const bool bRenderPreShadow = 
        CVarAllowPreshadows.GetValueOnRenderThread() 
        && LightSceneInfo->Proxy->HasStaticShadowing()
        && bSubjectIsVisible 
        && (!PrimitiveSceneInfo->Proxy->HasStaticLighting() || !Interaction->IsShadowMapped())
        && !(PrimitiveSceneInfo->Proxy->UseSingleSampleShadowFromStationaryLights() && LightSceneInfo->Proxy->GetLightType() == LightType_Directional);

    // 如果需要渲染預陰影, 則擴大包圍盒, 以提升快取利用率.
    if (bRenderPreShadow && ShouldUseCachePreshadows())
    {
        float PreshadowExpandFraction = FMath::Max(CVarPreshadowExpandFraction.GetValueOnRenderThread(), 0.0f);
        Bounds.SphereRadius += (Bounds.BoxExtent * PreshadowExpandFraction).Size();
        Bounds.BoxExtent *= PreshadowExpandFraction + 1.0f;
    }

    // 陰影初始化器.
    FPerObjectProjectedShadowInitializer ShadowInitializer;

    if ((MaxResolutionFadeAlpha > 1.0f / 256.0f || (bRenderPreShadow && MaxResolutionPreShadowFadeAlpha > 1.0f / 256.0f))
        && LightSceneInfo->Proxy->GetPerObjectProjectedShadowInitializer(Bounds, ShadowInitializer))
    {
        const float MaxFadeAlpha = MaxResolutionFadeAlpha;

        // 沒有完全過渡掉的陰影才需要創建陰影投射實體.
        if (CVarAllowPerObjectShadows.GetValueOnRenderThread() && MaxFadeAlpha > 1.0f / 256.0f)
        {
            const int32 SizeX = MaxDesiredResolution >= MaxShadowResolution ? MaxShadowResolution : (1 << (FMath::CeilLogTwo(MaxDesiredResolution) - 1));

            if (bOpaque && bCreateOpaqueObjectShadow && (bOpaqueShadowIsVisibleThisFrame || bShadowIsPotentiallyVisibleNextFrame))
            {
                // 創建FProjectedShadowInfo實體.
                FProjectedShadowInfo* ProjectedShadowInfo = new(FMemStack::Get(),1,16) FProjectedShadowInfo;

                if(ProjectedShadowInfo->SetupPerObjectProjection(
                    LightSceneInfo,
                    PrimitiveSceneInfo,
                    ShadowInitializer,
                    false,                    // no preshadow
                    SizeX,
                    MaxShadowResolutionY,
                    SHADOW_BORDER,
                    MaxScreenPercent,
                    false))                    // no translucent shadow
                {
                    ProjectedShadowInfo->bPerObjectOpaqueShadow = true;
                    ProjectedShadowInfo->FadeAlphas = ResolutionFadeAlphas;
                    VisibleLightInfo.MemStackProjectedShadows.Add(ProjectedShadowInfo);

                    // 將ProjectedShadowInfo添加到對應的串列.
                    if (bOpaqueShadowIsVisibleThisFrame)
                    {
                        VisibleLightInfo.AllProjectedShadows.Add(ProjectedShadowInfo);

                        for (int32 ChildIndex = 0, ChildCount = ShadowGroupPrimitives.Num(); ChildIndex < ChildCount; ChildIndex++)
                        {
                            FPrimitiveSceneInfo* ShadowChild = ShadowGroupPrimitives[ChildIndex];
                            ProjectedShadowInfo->AddSubjectPrimitive(ShadowChild, &Views, FeatureLevel, false);
                        }
                    }
                    else if (bShadowIsPotentiallyVisibleNextFrame)
                    {
                        VisibleLightInfo.OccludedPerObjectShadows.Add(ProjectedShadowInfo);
                    }
                }
            }

            // 半透明物體陰影.
            if (bTranslucentRelevance
                && Scene->GetFeatureLevel() >= ERHIFeatureLevel::SM5
                && bCreateTranslucentObjectShadow 
                && (bTranslucentShadowIsVisibleThisFrame || bShadowIsPotentiallyVisibleNextFrame))
            {
                (......)
            }
        }

        const float MaxPreFadeAlpha = MaxResolutionPreShadowFadeAlpha;

        // 處理有效的預陰影.
        if (MaxPreFadeAlpha > 1.0f / 256.0f 
            && bRenderPreShadow
            && bOpaque
            && Scene->GetFeatureLevel() >= ERHIFeatureLevel::SM5)
        {
            int32 PreshadowSizeX = 1 << (FMath::CeilLogTwo(FMath::TruncToInt(MaxDesiredResolution * CVarPreShadowResolutionFactor.GetValueOnRenderThread())) - 1);

            const FIntPoint PreshadowCacheResolution = SceneContext.GetPreShadowCacheTextureResolution();
            
            // 檢測是否在全景陰影之內.
            bool bIsOutsideWholeSceneShadow = true;
            for (int32 i = 0; i < ViewDependentWholeSceneShadows.Num(); i++)
            {
                const FProjectedShadowInfo* WholeSceneShadow = ViewDependentWholeSceneShadows[i];
                const FVector2D DistanceFadeValues = WholeSceneShadow->GetLightSceneInfo().Proxy->GetDirectionalLightDistanceFadeParameters(Scene->GetFeatureLevel(), WholeSceneShadow->GetLightSceneInfo().IsPrecomputedLightingValid(), WholeSceneShadow->DependentView->MaxShadowCascades);
                const float DistanceFromShadowCenterSquared = (WholeSceneShadow->ShadowBounds.Center - Bounds.Origin).SizeSquared();
                const float DistanceFromViewSquared = ((FVector)WholeSceneShadow->DependentView->ShadowViewMatrices.GetViewOrigin() - Bounds.Origin).SizeSquared();
                // 如果preshadow的球體包圍盒在*過渡距離之內, 則表示它在全景陰影內.
                if (DistanceFromShadowCenterSquared < FMath::Square(FMath::Max(WholeSceneShadow->ShadowBounds.W - Bounds.SphereRadius, 0.0f))
                    && DistanceFromViewSquared < FMath::Square(FMath::Max(DistanceFadeValues.X - 200.0f - Bounds.SphereRadius, 0.0f)))
                {
                    bIsOutsideWholeSceneShadow = false;
                    break;
                }
            }

            // 只有部分陰影投射者在全景陰影之外才創建不透明preshadow.
            if (bIsOutsideWholeSceneShadow)
            {
                // 嘗試從快取中重用preshadow.
                TRefCountPtr<FProjectedShadowInfo> ProjectedPreShadowInfo = GetCachedPreshadow(Interaction, ShadowInitializer, OriginalBounds, PreshadowSizeX);

                bool bOk = true;

                // 創建和設定ProjectedPreShadowInfo.
                if(!ProjectedPreShadowInfo)
                {
                    ProjectedPreShadowInfo = new FProjectedShadowInfo;

                    bOk = ProjectedPreShadowInfo->SetupPerObjectProjection(
                        LightSceneInfo,
                        PrimitiveSceneInfo,
                        ShadowInitializer,
                        true,                // preshadow
                        PreshadowSizeX,
                        FMath::TruncToInt(MaxShadowResolutionY * CVarPreShadowResolutionFactor.GetValueOnRenderThread()),
                        SHADOW_BORDER,
                        MaxScreenPercent,
                        false                // not translucent shadow
                        );
                }

                // 繼續設定有效的ProjectedPreShadowInfo的其它資料, 并添加到VisibleLightInfo相關串列中.
                if (bOk)
                {
                    ProjectedPreShadowInfo->FadeAlphas = ResolutionPreShadowFadeAlphas;

                    VisibleLightInfo.AllProjectedShadows.Add(ProjectedPreShadowInfo);
                    VisibleLightInfo.ProjectedPreShadows.Add(ProjectedPreShadowInfo);

                    // 如果preshadow沒有深度快取, 則將它加進OutPreShadows串列中. OutPreShadows用于僅生成在渲染陰影圖所需的資訊.
                    if (!ProjectedPreShadowInfo->bDepthsCached && ProjectedPreShadowInfo->CasterFrustum.PermutedPlanes.Num())
                    {
                        OutPreShadows.Add(ProjectedPreShadowInfo);
                    }

                    // 將所有可見的圖元加進陰影的接收圖元串列.
                    for (int32 ChildIndex = 0; ChildIndex < ShadowGroupPrimitives.Num(); ChildIndex++)
                    {
                        FPrimitiveSceneInfo* ShadowChild = ShadowGroupPrimitives[ChildIndex];
                        bool bChildIsVisibleInAnyView = false;
                        for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
                        {
                            const FViewInfo& View = Views[ViewIndex];
                            if (View.PrimitiveVisibilityMap[ShadowChild->GetIndex()])
                            {
                                bChildIsVisibleInAnyView = true;
                                break;
                            }
                        }
                        if (bChildIsVisibleInAnyView)
                        {
                            ProjectedPreShadowInfo->AddReceiverPrimitive(ShadowChild);
                        }
                    }
                }
            }
        }
    }
}

5.6.3.6 InitProjectedShadowVisibility

陰影的可見性初始化由InitProjectedShadowVisibility擔當,它的部分代碼和決議如下:

void FSceneRenderer::InitProjectedShadowVisibility(FRHICommandListImmediate& RHICmdList)
{
    int32 NumBufferedFrames = FOcclusionQueryHelpers::GetNumBufferedFrames(FeatureLevel);

    // 遍歷場景的所有光源, 初始化視圖的ProjectedShadowVisibilityMaps, 洗掉沒有主體圖元的陰影.
    for(TSparseArray<FLightSceneInfoCompact>::TConstIterator LightIt(Scene->Lights);LightIt;++LightIt)
    {
        FVisibleLightInfo& VisibleLightInfo = VisibleLightInfos[LightIt.GetIndex()];

        // 分配視圖內的投射陰影可見性和關聯容器.
        for(int32 ViewIndex = 0;ViewIndex < Views.Num();ViewIndex++)
        {
            FViewInfo& View = Views[ViewIndex];
            FVisibleLightViewInfo& VisibleLightViewInfo = View.VisibleLightInfos[LightIt.GetIndex()];
            VisibleLightViewInfo.ProjectedShadowVisibilityMap.Init(false,VisibleLightInfo.AllProjectedShadows.Num());
            VisibleLightViewInfo.ProjectedShadowViewRelevanceMap.Empty(VisibleLightInfo.AllProjectedShadows.Num());
            VisibleLightViewInfo.ProjectedShadowViewRelevanceMap.AddZeroed(VisibleLightInfo.AllProjectedShadows.Num());
        }

        // 遍歷可見光源的所有投射陰影實體.
        for( int32 ShadowIndex=0; ShadowIndex<VisibleLightInfo.AllProjectedShadows.Num(); ShadowIndex++ )
        {
            FProjectedShadowInfo& ProjectedShadowInfo = *VisibleLightInfo.AllProjectedShadows[ShadowIndex];

            // 保存陰影索引.
            ProjectedShadowInfo.ShadowId = ShadowIndex;

            for(int32 ViewIndex = 0;ViewIndex < Views.Num();ViewIndex++)
            {
                FViewInfo& View = Views[ViewIndex];

                // 處理視圖關聯的陰影.
                if (ProjectedShadowInfo.DependentView && ProjectedShadowInfo.DependentView != &View)
                {
                    (......)
                }

                FVisibleLightViewInfo& VisibleLightViewInfo = View.VisibleLightInfos[LightIt.GetIndex()];

                // 確保光源處于視錐體內.
                if(VisibleLightViewInfo.bInViewFrustum)
                {
                    // 計算主體圖元的視圖關聯資料.
                    FPrimitiveViewRelevance ViewRelevance;
                    if(ProjectedShadowInfo.GetParentSceneInfo())
                    {
                        ViewRelevance = ProjectedShadowInfo.GetParentSceneInfo()->Proxy->GetViewRelevance(&View);
                    }
                    else
                    {
                        ViewRelevance.bDrawRelevance = ViewRelevance.bStaticRelevance = ViewRelevance.bDynamicRelevance = ViewRelevance.bShadowRelevance = true;
                    }                            
                    VisibleLightViewInfo.ProjectedShadowViewRelevanceMap[ShadowIndex] = ViewRelevance;

                    // Check if the subject primitive's shadow is view relevant.
                    const bool bPrimitiveIsShadowRelevant = ViewRelevance.bShadowRelevance;

                    // 判斷陰影是否被遮擋.
                    bool bShadowIsOccluded = false;
                    if (!View.bIgnoreExistingQueries && View.State)
                    {
                        // Check if the shadow is occluded.
                        bShadowIsOccluded =
                            ((FSceneViewState*)View.State)->IsShadowOccluded(
                            RHICmdList,
                            FSceneViewState::FProjectedShadowKey(ProjectedShadowInfo),
                            NumBufferedFrames
                            );
                    }

                    // 如果符合可見性條件, 則設定標記
                    if(bPrimitiveIsShadowRelevant && !bShadowIsOccluded)
                    {
                        VisibleLightViewInfo.ProjectedShadowVisibilityMap[ShadowIndex] = true;
                    }

                    // 如果陰影可見且不是RSM陰影, 則繪制陰影錐體.
                    if(bPrimitiveIsShadowRelevant && !bShadowIsOccluded && !ProjectedShadowInfo.bReflectiveShadowmap)  
                    {
                        bool bDrawPreshadowFrustum = CVarDrawPreshadowFrustum.GetValueOnRenderThread() != 0;

                        // 繪制preshadow錐體.
                        if ((ViewFamily.EngineShowFlags.ShadowFrustums)
                            && ((bDrawPreshadowFrustum && ProjectedShadowInfo.bPreShadow) || (!bDrawPreshadowFrustum && !ProjectedShadowInfo.bPreShadow)))
                        {
                            // 繪制陰影錐體的PDI.
                            FViewElementPDI ShadowFrustumPDI(&Views[ViewIndex], nullptr, &Views[ViewIndex].DynamicPrimitiveShaderData);
                            
                            // 全景*行陰影才需要繪制錐體.
                            if(ProjectedShadowInfo.IsWholeSceneDirectionalShadow())
                            {
                                // Get split color
                                FColor Color = FColor::White;
                                switch(ProjectedShadowInfo.CascadeSettings.ShadowSplitIndex)
                                {
                                    case 0: Color = FColor::Red; break;
                                    case 1: Color = FColor::Yellow; break;
                                    case 2: Color = FColor::Green; break;
                                    case 3: Color = FColor::Blue; break;
                                }

                                const FMatrix ViewMatrix = View.ViewMatrices.GetViewMatrix();
                                const FMatrix ProjectionMatrix = View.ViewMatrices.GetProjectionMatrix();
                                const FVector4 ViewOrigin = View.ViewMatrices.GetViewOrigin();

                                float AspectRatio = ProjectionMatrix.M[1][1] / ProjectionMatrix.M[0][0];
                                float ActualFOV = (ViewOrigin.W > 0.0f) ? FMath::Atan(1.0f / ProjectionMatrix.M[0][0]) : PI/4.0f;

                                float Near = ProjectedShadowInfo.CascadeSettings.SplitNear;
                                float Mid = ProjectedShadowInfo.CascadeSettings.FadePlaneOffset;
                                float Far = ProjectedShadowInfo.CascadeSettings.SplitFar;

                                // 攝像機子錐體.
                                DrawFrustumWireframe(&ShadowFrustumPDI, (ViewMatrix * FPerspectiveMatrix(ActualFOV, AspectRatio, 1.0f, Near, Mid)).Inverse(), Color, 0);
                                DrawFrustumWireframe(&ShadowFrustumPDI, (ViewMatrix * FPerspectiveMatrix(ActualFOV, AspectRatio, 1.0f, Mid, Far)).Inverse(), FColor::White, 0);

                                // 陰影圖投影包圍盒.
                                DrawFrustumWireframe(&ShadowFrustumPDI, ProjectedShadowInfo.SubjectAndReceiverMatrix.Inverse() * FTranslationMatrix(-ProjectedShadowInfo.PreShadowTranslation), Color, 0);
                            }
                            else // 非全景陰影, 直接呼叫ProjectedShadowInfo的繪制介面.
                            {
                                ProjectedShadowInfo.RenderFrustumWireframe(&ShadowFrustumPDI);
                            }
                        }
                    }
                }
            }
        }
    }
    
    (......)
}

以上呼叫DrawFrustumWireframeRenderFrustumWireframe后并不是立即繪制,而是通過FViewElementPDI生成了FBatchedElements,然后將它們保存到view內,以便后續在陰影渲染階段真正地執行繪制,

5.6.3.7 UpdatePreshadowCache

下面繼續分析初始化階段的UpdatePreshadowCache

void FSceneRenderer::UpdatePreshadowCache(FSceneRenderTargets& SceneContext)
{
    if (ShouldUseCachePreshadows() && !Views[0].bIsSceneCapture)
    {
        // 初始化紋理布局.
        if (Scene->PreshadowCacheLayout.GetSizeX() == 0)
        {
            const FIntPoint PreshadowCacheBufferSize = SceneContext.GetPreShadowCacheTextureResolution();
            Scene->PreshadowCacheLayout = FTextureLayout(1, 1, PreshadowCacheBufferSize.X, PreshadowCacheBufferSize.Y, false, ETextureLayoutAspectRatio::None, false);
        }

        // 遍歷所有快取的預陰影, 洗掉不在此幀渲染的實體.
        for (int32 CachedShadowIndex = Scene->CachedPreshadows.Num() - 1; CachedShadowIndex >= 0; CachedShadowIndex--)
        {
            TRefCountPtr<FProjectedShadowInfo> CachedShadow = Scene->CachedPreshadows[CachedShadowIndex];
            bool bShadowBeingRenderedThisFrame = false;

            for (int32 LightIndex = 0; LightIndex < VisibleLightInfos.Num() && !bShadowBeingRenderedThisFrame; LightIndex++)
            {
                bShadowBeingRenderedThisFrame = VisibleLightInfos[LightIndex].ProjectedPreShadows.Find(CachedShadow) != INDEX_NONE;
            }

            if (!bShadowBeingRenderedThisFrame)
            {
                Scene->CachedPreshadows.RemoveAt(CachedShadowIndex);
            }
        }

        TArray<TRefCountPtr<FProjectedShadowInfo>, SceneRenderingAllocator> UncachedPreShadows;
        
        // 收集可以被快取的preshadow串列.
        for (int32 LightIndex = 0; LightIndex < VisibleLightInfos.Num(); LightIndex++)
        {
            for (int32 ShadowIndex = 0; ShadowIndex < VisibleLightInfos[LightIndex].ProjectedPreShadows.Num(); ShadowIndex++)
            {
                TRefCountPtr<FProjectedShadowInfo> CurrentShadow = VisibleLightInfos[LightIndex].ProjectedPreShadows[ShadowIndex];
                checkSlow(CurrentShadow->bPreShadow);

                if (!CurrentShadow->bAllocatedInPreshadowCache)
                {
                    UncachedPreShadows.Add(CurrentShadow);
                }
            }
        }

        // 對preshadow從大到小排序, 假設更大的preshadow在渲染深度時會有更多的物體.
        UncachedPreShadows.Sort(FComparePreshadows());

        for (int32 ShadowIndex = 0; ShadowIndex < UncachedPreShadows.Num(); ShadowIndex++)
        {
            TRefCountPtr<FProjectedShadowInfo> CurrentShadow = UncachedPreShadows[ShadowIndex];
            // 嘗試從紋理布局中給preshadow找到空間, 若找到, 則設定相關資料.
            if (Scene->PreshadowCacheLayout.AddElement(CurrentShadow->X, CurrentShadow->Y, CurrentShadow->ResolutionX + CurrentShadow->BorderSize * 2, CurrentShadow->ResolutionY + CurrentShadow->BorderSize * 2))
            {
                CurrentShadow->bAllocatedInPreshadowCache = true;
                CurrentShadow->bAllocated = true;
                Scene->CachedPreshadows.Add(CurrentShadow);
            }
        }
    }
}

5.6.3.8 GatherShadowPrimitives

接下來分析初始化階段的GatherShadowPrimitives

void FSceneRenderer::GatherShadowPrimitives(const TArray<FProjectedShadowInfo*, SceneRenderingAllocator>& PreShadows, const TArray<FProjectedShadowInfo*, SceneRenderingAllocator>& ViewDependentWholeSceneShadows, bool bStaticSceneOnly)
{
    // 存在預陰影或視圖關聯全景陰影.
    if (PreShadows.Num() || ViewDependentWholeSceneShadows.Num())
    {
        TArray<FGatherShadowPrimitivesPacket*,SceneRenderingAllocator> Packets;

        // 八叉樹遍歷裁剪陰影.
        if (GUseOctreeForShadowCulling)
        {
            Packets.Reserve(100);

            // 查找和位于八叉樹的陰影錐體相交的圖元.
            for(FScenePrimitiveOctree::TConstIterator<SceneRenderingAllocator> PrimitiveOctreeIt(Scene->PrimitiveOctree); PrimitiveOctreeIt.HasPendingNodes(); PrimitiveOctreeIt.Advance())
            {
                const FScenePrimitiveOctree::FNode& PrimitiveOctreeNode = PrimitiveOctreeIt.GetCurrentNode();
                const FOctreeNodeContext& PrimitiveOctreeNodeContext = PrimitiveOctreeIt.GetCurrentContext();
                
                {
                    // 查找八叉樹節點可能包含關聯圖元的孩子節點.
                    FOREACH_OCTREE_CHILD_NODE(ChildRef)
                    {
                        if(PrimitiveOctreeNode.HasChild(ChildRef))
                        {
                            // 檢查孩子節點是否至少在一個陰影內.
                            const FOctreeNodeContext ChildContext = PrimitiveOctreeNodeContext.GetChildContext(ChildRef);
                            bool bIsInFrustum = false;
                            
                            if(!bIsInFrustum)
                            {
                                // 遍歷所有的preshadow, 判斷孩子節點是否和preshadow相交.
                                for(int32 ShadowIndex = 0, Num = PreShadows.Num(); ShadowIndex < Num; ShadowIndex++)
                                {
                                    FProjectedShadowInfo* ProjectedShadowInfo = PreShadows[ShadowIndex];
                                    // 檢測圖元是否在陰影錐體內.
                                    if(ProjectedShadowInfo->CasterFrustum.IntersectBox(ChildContext.Bounds.Center + ProjectedShadowInfo->PreShadowTranslation, ChildContext.Bounds.Extent))
                                    {
                                        bIsInFrustum = true;
                                        break;
                                    }
                                }
                            }

                            // 如果還不在錐體內, 則讓孩子節點的包圍盒和視圖相關的全景陰影執行相交檢測.
                            if (!bIsInFrustum)
                            {
                                for(int32 ShadowIndex = 0, Num = ViewDependentWholeSceneShadows.Num(); ShadowIndex < Num; ShadowIndex++)
                                {
                                    FProjectedShadowInfo* ProjectedShadowInfo = ViewDependentWholeSceneShadows[ShadowIndex];

                                    //check(ProjectedShadowInfo->CasterFrustum.PermutedPlanes.Num());
                                    // Check if this primitive is in the shadow's frustum.
                                    if(ProjectedShadowInfo->CasterFrustum.IntersectBox(
                                        ChildContext.Bounds.Center + ProjectedShadowInfo->PreShadowTranslation,
                                        ChildContext.Bounds.Extent
                                        ))
                                    {
                                        bIsInFrustum = true;
                                        break;
                                    }
                                }
                            }

                            // 如何圖元和至少一個陰影相交, 則加入圖元八叉樹迭代器的待定節點堆疊(pending node stack)中.
                            if(bIsInFrustum)
                            {
                                PrimitiveOctreeIt.PushChild(ChildRef);
                            }
                        }
                    } // FOREACH_OCTREE_CHILD_NODE
                }

                if (PrimitiveOctreeNode.GetElementCount() > 0)
                {
                    FGatherShadowPrimitivesPacket* Packet = new(FMemStack::Get()) FGatherShadowPrimitivesPacket(Scene, Views, &PrimitiveOctreeNode, 0, 0, PreShadows, ViewDependentWholeSceneShadows, FeatureLevel, bStaticSceneOnly);
                    Packets.Add(Packet);
                }
            } // for
        }
        else // 非八叉樹遍歷.
        {
            const int32 PacketSize = CVarParallelGatherNumPrimitivesPerPacket.GetValueOnRenderThread();
            const int32 NumPackets = FMath::DivideAndRoundUp(Scene->Primitives.Num(), PacketSize);
            
            Packets.Reserve(NumPackets);

            // 非八叉樹模式, 直接線性遍歷所有圖元, 添加同等數量的FGatherShadowPrimitivesPacket實體.
            for (int32 PacketIndex = 0; PacketIndex < NumPackets; PacketIndex++)
            {
                const int32 StartPrimitiveIndex = PacketIndex * PacketSize;
                const int32 NumPrimitives = FMath::Min(PacketSize, Scene->Primitives.Num() - StartPrimitiveIndex);
                FGatherShadowPrimitivesPacket* Packet = new(FMemStack::Get()) FGatherShadowPrimitivesPacket(Scene, Views, NULL, StartPrimitiveIndex, NumPrimitives, PreShadows, ViewDependentWholeSceneShadows, FeatureLevel, bStaticSceneOnly);
                Packets.Add(Packet);
            }
        }
        
        // 呼叫并行For過濾掉和陰影不相交的圖元
        ParallelFor(Packets.Num(), 
            [&Packets](int32 Index)
            {
                Packets[Index]->AnyThreadTask();
            },
            !(FApp::ShouldUseThreadingForPerformance() && CVarParallelGatherShadowPrimitives.GetValueOnRenderThread() > 0) );

        // 釋放資源.
        for (int32 PacketIndex = 0; PacketIndex < Packets.Num(); PacketIndex++)
        {
            FGatherShadowPrimitivesPacket* Packet = Packets[PacketIndex];
            Packet->RenderThreadFinalize();
            Packet->~FGatherShadowPrimitivesPacket();
        }
    }
}

5.6.3.9 FGatherShadowPrimitivesPacket

上面代碼中呼叫Packets[Index]->AnyThreadTask()以收集陰影圖元資料包,呼叫FilterPrimitiveForShadows過濾不受陰影影響的圖元,呼叫Packet->RenderThreadFinalize()將收集的圖元添加到對應的陰影串列中,下面分析它們的具體執行邏輯:

void FGatherShadowPrimitivesPacket::AnyThreadTask()
{
    if (Node) // 如果存在節點
    {
        // 利用八叉樹過濾和收集受陰影影響的圖元.
        for (FScenePrimitiveOctree::ElementConstIt NodePrimitiveIt(Node->GetElementIt()); NodePrimitiveIt; ++NodePrimitiveIt)
        {
            if (NodePrimitiveIt->PrimitiveFlagsCompact.bCastDynamicShadow)
            {
                FilterPrimitiveForShadows(NodePrimitiveIt->Bounds, NodePrimitiveIt->PrimitiveFlagsCompact, NodePrimitiveIt->PrimitiveSceneInfo, NodePrimitiveIt->Proxy);
            }
        }
    }
    else
    {
        // 利用資訊包的索引范圍遍歷, 逐個去過濾和收集受陰影影響的圖元.
        for (int32 PrimitiveIndex = StartPrimitiveIndex; PrimitiveIndex < StartPrimitiveIndex + NumPrimitives; PrimitiveIndex++)
        {
            FPrimitiveFlagsCompact PrimitiveFlagsCompact = Scene->PrimitiveFlagsCompact[PrimitiveIndex];

            if (PrimitiveFlagsCompact.bCastDynamicShadow)
            {
                FilterPrimitiveForShadows(Scene->PrimitiveBounds[PrimitiveIndex].BoxSphereBounds, PrimitiveFlagsCompact, Scene->Primitives[PrimitiveIndex], Scene->PrimitiveSceneProxies[PrimitiveIndex]);
            }
        }
    }
}

void FGatherShadowPrimitivesPacket::FilterPrimitiveForShadows(const FBoxSphereBounds& PrimitiveBounds, FPrimitiveFlagsCompact PrimitiveFlagsCompact, FPrimitiveSceneInfo* PrimitiveSceneInfo, FPrimitiveSceneProxy* PrimitiveProxy)
{
    // 檢測圖元是否受任意一個preshadow影響,
    if (PreShadows.Num() && PrimitiveFlagsCompact.bCastStaticShadow && PrimitiveFlagsCompact.bStaticLighting)
    {
        for (int32 ShadowIndex = 0, Num = PreShadows.Num(); ShadowIndex < Num; ShadowIndex++)
        {
            FProjectedShadowInfo* RESTRICT ProjectedShadowInfo = PreShadows[ShadowIndex];
            // 圖元包圍盒和陰影投射錐體相交測驗.
            bool bInFrustum = ProjectedShadowInfo->CasterFrustum.IntersectBox(PrimitiveBounds.Origin, ProjectedShadowInfo->PreShadowTranslation, PrimitiveBounds.BoxExtent);
            // 在投影錐體內且相互影響的加入PreShadowSubjectPrimitives中.
            if (bInFrustum && ProjectedShadowInfo->GetLightSceneInfoCompact().AffectsPrimitive(PrimitiveBounds, PrimitiveProxy))
            {
                PreShadowSubjectPrimitives[ShadowIndex].Add(PrimitiveSceneInfo);
            }
        }
    }

    // 圖元和全景陰影相交測驗.
    for (int32 ShadowIndex = 0, Num = ViewDependentWholeSceneShadows.Num();ShadowIndex < Num;ShadowIndex++)
    {
        const FProjectedShadowInfo* RESTRICT ProjectedShadowInfo = ViewDependentWholeSceneShadows[ShadowIndex];
        const FLightSceneInfo& RESTRICT LightSceneInfo = ProjectedShadowInfo->GetLightSceneInfo();
        const FLightSceneProxy& RESTRICT LightProxy = *LightSceneInfo.Proxy;

        const FVector LightDirection = LightProxy.GetDirection();
        const FVector PrimitiveToShadowCenter = ProjectedShadowInfo->ShadowBounds.Center - PrimitiveBounds.Origin;
        // 投影圖元的包圍盒到光源向量上.
        const float ProjectedDistanceFromShadowOriginAlongLightDir = PrimitiveToShadowCenter | LightDirection;
        const float PrimitiveDistanceFromCylinderAxisSq = (-LightDirection * ProjectedDistanceFromShadowOriginAlongLightDir + PrimitiveToShadowCenter).SizeSquared();
        const float CombinedRadiusSq = FMath::Square(ProjectedShadowInfo->ShadowBounds.W + PrimitiveBounds.SphereRadius);

        // 檢測圖元是否在陰影的[圓柱體]內.
        if (PrimitiveDistanceFromCylinderAxisSq < CombinedRadiusSq
            && !(ProjectedDistanceFromShadowOriginAlongLightDir < 0 && PrimitiveToShadowCenter.SizeSquared() > CombinedRadiusSq)
            && ProjectedShadowInfo->CascadeSettings.ShadowBoundsAccurate.IntersectBox(PrimitiveBounds.Origin, PrimitiveBounds.BoxExtent))
        {
            // 為RSM執行距離裁剪.
            const float MinScreenRadiusForShadowCaster = ProjectedShadowInfo->bReflectiveShadowmap ? GMinScreenRadiusForShadowCasterRSM : GMinScreenRadiusForShadowCaster;

            // 螢屏空間尺寸裁剪
            bool bScreenSpaceSizeCulled = false;
            {
                const float DistanceSquared = (PrimitiveBounds.Origin - ProjectedShadowInfo->DependentView->ShadowViewMatrices.GetViewOrigin()).SizeSquared();
                bScreenSpaceSizeCulled = FMath::Square(PrimitiveBounds.SphereRadius) < FMath::Square(MinScreenRadiusForShadowCaster) * DistanceSquared * ProjectedShadowInfo->DependentView->LODDistanceFactorSquared;
            }

            // 是否計算嵌入陰影(InsetShadow).
            bool bCastsInsetShadows = PrimitiveProxy->CastsInsetShadow();
            // 處理圖元的光源附加根組件.
            if (PrimitiveSceneInfo->LightingAttachmentRoot.IsValid())
            {
                FAttachmentGroupSceneInfo& AttachmentGroup = PrimitiveSceneInfo->Scene->AttachmentGroups.FindChecked(PrimitiveSceneInfo->LightingAttachmentRoot);
                bCastsInsetShadows = AttachmentGroup.ParentSceneInfo && AttachmentGroup.ParentSceneInfo->Proxy->CastsInsetShadow();
            }

            // 檢測各種各樣的條件判斷, 所有條件通過后才加入ViewDependentWholeSceneShadowSubjectPrimitives串列.
            if (!bScreenSpaceSizeCulled
                && ProjectedShadowInfo->GetLightSceneInfoCompact().AffectsPrimitive(PrimitiveBounds, PrimitiveProxy)
                && (!LightProxy.HasStaticLighting() || (!LightSceneInfo.IsPrecomputedLightingValid() || LightProxy.UseCSMForDynamicObjects()))
                && !(ProjectedShadowInfo->bReflectiveShadowmap && !PrimitiveProxy->AffectsDynamicIndirectLighting())
                && (!bCastsInsetShadows || ProjectedShadowInfo->bReflectiveShadowmap)
                && !ShouldCreateObjectShadowForStationaryLight(&LightSceneInfo, PrimitiveProxy, true)
                && (!bStaticSceneOnly || PrimitiveProxy->HasStaticLighting())
                && (!LightProxy.UseCSMForDynamicObjects() || !PrimitiveProxy->HasStaticLighting()))
            {
                ViewDependentWholeSceneShadowSubjectPrimitives[ShadowIndex].Add(PrimitiveSceneInfo);
            }
        }
    }
}

void FGatherShadowPrimitivesPacket::RenderThreadFinalize()
{
    // 將每個PreShadow實體內的所有圖元都加入該陰影實體中.
    for (int32 ShadowIndex = 0; ShadowIndex < PreShadowSubjectPrimitives.Num(); ShadowIndex++)
    {
        FProjectedShadowInfo* ProjectedShadowInfo = PreShadows[ShadowIndex];

        for (int32 PrimitiveIndex = 0; PrimitiveIndex < PreShadowSubjectPrimitives[ShadowIndex].Num(); PrimitiveIndex++)
        {
            ProjectedShadowInfo->AddSubjectPrimitive(PreShadowSubjectPrimitives[ShadowIndex][PrimitiveIndex], &Views, FeatureLevel, false);
        }
    }

    // 將每個全景陰影實體內的所有圖元都加入該陰影實體中.
    for (int32 ShadowIndex = 0; ShadowIndex < ViewDependentWholeSceneShadowSubjectPrimitives.Num(); ShadowIndex++)
    {
        FProjectedShadowInfo* ProjectedShadowInfo = ViewDependentWholeSceneShadows[ShadowIndex];

        (......)

        for (int32 PrimitiveIndex = 0; PrimitiveIndex < ViewDependentWholeSceneShadowSubjectPrimitives[ShadowIndex].Num(); PrimitiveIndex++)
        {
            ProjectedShadowInfo->AddSubjectPrimitive(ViewDependentWholeSceneShadowSubjectPrimitives[ShadowIndex][PrimitiveIndex], NULL, FeatureLevel, bRecordShadowSubjectsForMobile);
        }
    }
}

5.6.3.10 AllocateShadowDepthTargets

繼續分析初始化階段的AllocateShadowDepthTargets

void FSceneRenderer::AllocateShadowDepthTargets(FRHICommandListImmediate& RHICmdList)
{
    FSceneRenderTargets& SceneContext = FSceneRenderTargets::Get(RHICmdList);

    // 對可見陰影基于分配需求排序.
    // 此幀的2d陰影圖可以跨光源合并成圖集.
    TArray<FProjectedShadowInfo*, SceneRenderingAllocator> Shadows;
    // 橫跨持續存在于多幀的2d陰影圖不能合并成圖集.
    TArray<FProjectedShadowInfo*, SceneRenderingAllocator> CachedSpotlightShadows;
    TArray<FProjectedShadowInfo*, SceneRenderingAllocator> TranslucentShadows;
    // 橫跨持續存在于多幀的2d陰影圖
    TArray<FProjectedShadowInfo*, SceneRenderingAllocator> CachedPreShadows;
    TArray<FProjectedShadowInfo*, SceneRenderingAllocator> RSMShadows;
    // 點光源的cubemap, 不能合并成圖集.
    TArray<FProjectedShadowInfo*, SceneRenderingAllocator> WholeScenePointShadows;

    // 遍歷所有光源
    for (TSparseArray<FLightSceneInfoCompact>::TConstIterator LightIt(Scene->Lights); LightIt; ++LightIt)
    {
        const FLightSceneInfoCompact& LightSceneInfoCompact = *LightIt;
        FLightSceneInfo* LightSceneInfo = LightSceneInfoCompact.LightSceneInfo;
        FVisibleLightInfo& VisibleLightInfo = VisibleLightInfos[LightSceneInfo->Id];

        // 同一個光源的所有級聯陰影須在同一個紋理中.
        TArray<FProjectedShadowInfo*, SceneRenderingAllocator> WholeSceneDirectionalShadows;

        // 遍歷光源的所有投射陰影實體.
        for (int32 ShadowIndex = 0; ShadowIndex < VisibleLightInfo.AllProjectedShadows.Num(); ShadowIndex++)
        {
            FProjectedShadowInfo* ProjectedShadowInfo = VisibleLightInfo.AllProjectedShadows[ShadowIndex];

            // 檢測陰影是否至少在一個view中可見.
            bool bShadowIsVisible = false;
            for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
            {
                FViewInfo& View = Views[ViewIndex];

                if (ProjectedShadowInfo->DependentView && ProjectedShadowInfo->DependentView != &View)
                {
                    continue;
                }

                const FVisibleLightViewInfo& VisibleLightViewInfo = View.VisibleLightInfos[LightSceneInfo->Id];
                const FPrimitiveViewRelevance ViewRelevance = VisibleLightViewInfo.ProjectedShadowViewRelevanceMap[ShadowIndex];
                const bool bHasViewRelevance = (ProjectedShadowInfo->bTranslucentShadow && ViewRelevance.HasTranslucency()) 
                    || (!ProjectedShadowInfo->bTranslucentShadow && ViewRelevance.bOpaque);

                bShadowIsVisible |= bHasViewRelevance && VisibleLightViewInfo.ProjectedShadowVisibilityMap[ShadowIndex];
            }

            // 檢測陰影快取模式為可移動圖元時的條件可見性.
            if (ProjectedShadowInfo->CacheMode == SDCM_MovablePrimitivesOnly && !ProjectedShadowInfo->HasSubjectPrims())
            {
                FCachedShadowMapData& CachedShadowMapData = https://www.cnblogs.com/timlly/p/Scene->CachedShadowMaps.FindChecked(ProjectedShadowInfo->GetLightSceneInfo().Id);
                if (!CachedShadowMapData.bCachedShadowMapHasPrimitives)
                {
                    bShadowIsVisible = false;
                }
            }

            // 移動端渲染器只支持半透明物體逐陰影或CSM.
            if (FeatureLevel < ERHIFeatureLevel::SM5
                && (!ProjectedShadowInfo->bPerObjectOpaqueShadow && !(ProjectedShadowInfo->bDirectionalLight && ProjectedShadowInfo->bWholeSceneShadow)))
            {
                bShadowIsVisible = false;
            }

            // 前向渲染路徑中, 動態陰影會被投射到光源衰減紋理(light attenuation texture)的通道中.
            if (IsForwardShadingEnabled(ShaderPlatform)
                && ProjectedShadowInfo->GetLightSceneInfo().GetDynamicShadowMapChannel() == -1)
            {
                bShadowIsVisible = false;
            }

            // 陰影可見, 則根據不同型別加入到不同的陰影實體串列中.
            if (bShadowIsVisible)
            {
                (......)

                bool bNeedsProjection = ProjectedShadowInfo->CacheMode != SDCM_StaticPrimitivesOnly
                    // Mobile rendering only projects opaque per object shadows.
                    && (FeatureLevel >= ERHIFeatureLevel::SM5 || ProjectedShadowInfo->bPerObjectOpaqueShadow);

                if (bNeedsProjection)
                {
                    if (ProjectedShadowInfo->bReflectiveShadowmap)
                    {
                        VisibleLightInfo.RSMsToProject.Add(ProjectedShadowInfo);
                    }
                    else if (ProjectedShadowInfo->bCapsuleShadow)
                    {
                        VisibleLightInfo.CapsuleShadowsToProject.Add(ProjectedShadowInfo);
                    }
                    else
                    {
                        VisibleLightInfo.ShadowsToProject.Add(ProjectedShadowInfo);
                    }
                }

                const bool bNeedsShadowmapSetup = !ProjectedShadowInfo->bCapsuleShadow && !ProjectedShadowInfo->bRayTracedDistanceField;

                if (bNeedsShadowmapSetup)
                {
                    if (ProjectedShadowInfo->bReflectiveShadowmap)
                    {
                        check(ProjectedShadowInfo->bWholeSceneShadow);
                        RSMShadows.Add(ProjectedShadowInfo);
                    }
                    else if (ProjectedShadowInfo->bPreShadow && ProjectedShadowInfo->bAllocatedInPreshadowCache)
                    {
                        CachedPreShadows.Add(ProjectedShadowInfo);
                    }
                    else if (ProjectedShadowInfo->bDirectionalLight && ProjectedShadowInfo->bWholeSceneShadow)
                    {
                        WholeSceneDirectionalShadows.Add(ProjectedShadowInfo);
                    }
                    else if (ProjectedShadowInfo->bOnePassPointLightShadow)
                    {
                        WholeScenePointShadows.Add(ProjectedShadowInfo);
                    }
                    else if (ProjectedShadowInfo->bTranslucentShadow)
                    {
                        TranslucentShadows.Add(ProjectedShadowInfo);
                    }
                    else if (ProjectedShadowInfo->CacheMode == SDCM_StaticPrimitivesOnly)
                    {
                        check(ProjectedShadowInfo->bWholeSceneShadow);
                        CachedSpotlightShadows.Add(ProjectedShadowInfo);
                    }
                    else
                    {
                        Shadows.Add(ProjectedShadowInfo);
                    }
                }
            }
        }

        // 排序級聯陰影, 在級聯之間的混合是必須的.
        VisibleLightInfo.ShadowsToProject.Sort(FCompareFProjectedShadowInfoBySplitIndex());
        VisibleLightInfo.RSMsToProject.Sort(FCompareFProjectedShadowInfoBySplitIndex());

        // 分配CSM深度渲染紋理.
        AllocateCSMDepthTargets(RHICmdList, WholeSceneDirectionalShadows);
    }

    // 處理快取的PreShadow.
    if (CachedPreShadows.Num() > 0)
    {
        // 創建場景的PreShadow快取深度紋理.
        if (!Scene->PreShadowCacheDepthZ)
        {
            FPooledRenderTargetDesc Desc(FPooledRenderTargetDesc::Create2DDesc(SceneContext.GetPreShadowCacheTextureResolution(), PF_ShadowDepth, FClearValueBinding::None, TexCreate_None, TexCreate_DepthStencilTargetable | TexCreate_ShaderResource, false));
            Desc.AutoWritable = false;
            GRenderTargetPool.FindFreeElement(RHICmdList, Desc, Scene->PreShadowCacheDepthZ, TEXT("PreShadowCacheDepthZ"), true, ERenderTargetTransience::NonTransient);
        }

        SortedShadowsForShadowDepthPass.PreshadowCache.RenderTargets.DepthTarget = Scene->PreShadowCacheDepthZ;

        for (int32 ShadowIndex = 0; ShadowIndex < CachedPreShadows.Num(); ShadowIndex++)
        {
            FProjectedShadowInfo* ProjectedShadowInfo = CachedPreShadows[ShadowIndex];
            ProjectedShadowInfo->RenderTargets.DepthTarget = Scene->PreShadowCacheDepthZ.GetReference();
            // 設定陰影深度視圖, 會在場景渲染器中為陰影找到一個專用的視圖.
            ProjectedShadowInfo->SetupShadowDepthView(RHICmdList, this);
            SortedShadowsForShadowDepthPass.PreshadowCache.Shadows.Add(ProjectedShadowInfo);
        }
    }

    // 分配各類陰影的渲染紋理, 包含點光源cubemap,RSM,快取的聚光燈,逐物體,透明陰影等.(CSM已經在前面分配過了)
    AllocateOnePassPointLightDepthTargets(RHICmdList, WholeScenePointShadows);
    AllocateRSMDepthTargets(RHICmdList, RSMShadows);
    AllocateCachedSpotlightShadowDepthTargets(RHICmdList, CachedSpotlightShadows);
    AllocatePerObjectShadowDepthTargets(RHICmdList, Shadows);
    AllocateTranslucentShadowDepthTargets(RHICmdList, TranslucentShadows);

    // 更新透明陰影圖的uniform buffer.
    for (int32 TranslucentShadowIndex = 0; TranslucentShadowIndex < TranslucentShadows.Num(); ++TranslucentShadowIndex)
    {
        FProjectedShadowInfo* ShadowInfo = TranslucentShadows[TranslucentShadowIndex];
        const int32 PrimitiveIndex = ShadowInfo->GetParentSceneInfo()->GetIndex();

        for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ++ViewIndex)
        {
            FViewInfo& View = Views[ViewIndex];
            FUniformBufferRHIRef* UniformBufferPtr = View.TranslucentSelfShadowUniformBufferMap.Find(PrimitiveIndex);
            if (UniformBufferPtr)
            {
                FTranslucentSelfShadowUniformParameters Parameters;
                SetupTranslucentSelfShadowUniformParameters(ShadowInfo, Parameters);
                RHIUpdateUniformBuffer(*UniformBufferPtr, &Parameters);
            }
        }
    }

    // 洗掉完全沒有被使用的陰影快取.
    for (TMap<int32, FCachedShadowMapData>::TIterator CachedShadowMapIt(Scene->CachedShadowMaps); CachedShadowMapIt; ++CachedShadowMapIt)
    {
        FCachedShadowMapData& ShadowMapData = https://www.cnblogs.com/timlly/p/CachedShadowMapIt.Value();
        if (ShadowMapData.ShadowMap.IsValid() && ViewFamily.CurrentRealTime - ShadowMapData.LastUsedTime > 2.0f)
        {
            ShadowMapData.ShadowMap.Release();
        }
    }
}

5.6.3.11 GatherShadowDynamicMeshElements

陰影初始化階段的最后一步是GatherShadowDynamicMeshElements

void FSceneRenderer::GatherShadowDynamicMeshElements(FGlobalDynamicIndexBuffer& DynamicIndexBuffer, FGlobalDynamicVertexBuffer& DynamicVertexBuffer, FGlobalDynamicReadBuffer& DynamicReadBuffer)
{
    TArray<const FSceneView*> ReusedViewsArray;
    ReusedViewsArray.AddZeroed(1);

    // 遍歷所有陰影圖圖集.
    for (int32 AtlasIndex = 0; AtlasIndex < SortedShadowsForShadowDepthPass.ShadowMapAtlases.Num(); AtlasIndex++)
    {
        FSortedShadowMapAtlas& Atlas = SortedShadowsForShadowDepthPass.ShadowMapAtlases[AtlasIndex];

        // 遍歷陰影圖圖集上的所有陰影實體, 收集它們需要投射陰影的網格元素.
        for (int32 ShadowIndex = 0; ShadowIndex < Atlas.Shadows.Num(); ShadowIndex++)
        {
            FProjectedShadowInfo* ProjectedShadowInfo = Atlas.Shadows[ShadowIndex];
            FVisibleLightInfo& VisibleLightInfo = VisibleLightInfos[ProjectedShadowInfo->GetLightSceneInfo().Id];
            ProjectedShadowInfo->GatherDynamicMeshElements(*this, VisibleLightInfo, ReusedViewsArray, DynamicIndexBuffer, DynamicVertexBuffer, DynamicReadBuffer);
        }
    }

    // 遍歷所有RSM陰影圖圖集, 收集每個圖集內的所有陰影實體的網格元素.
    for (int32 AtlasIndex = 0; AtlasIndex < SortedShadowsForShadowDepthPass.RSMAtlases.Num(); AtlasIndex++)
    {
        FSortedShadowMapAtlas& Atlas = SortedShadowsForShadowDepthPass.RSMAtlases[AtlasIndex];

        for (int32 ShadowIndex = 0; ShadowIndex < Atlas.Shadows.Num(); ShadowIndex++)
        {
            FProjectedShadowInfo* ProjectedShadowInfo = Atlas.Shadows[ShadowIndex];
            FVisibleLightInfo& VisibleLightInfo = VisibleLightInfos[ProjectedShadowInfo->GetLightSceneInfo().Id];
            ProjectedShadowInfo->GatherDynamicMeshElements(*this, VisibleLightInfo, ReusedViewsArray, DynamicIndexBuffer, DynamicVertexBuffer, DynamicReadBuffer);
        }
    }

    // 遍歷所有點光源立方體陰影圖, 收集每個立方體陰影圖內的所有陰影實體的網格元素.
    for (int32 AtlasIndex = 0; AtlasIndex < SortedShadowsForShadowDepthPass.ShadowMapCubemaps.Num(); AtlasIndex++)
    {
        FSortedShadowMapAtlas& Atlas = SortedShadowsForShadowDepthPass.ShadowMapCubemaps[AtlasIndex];

        for (int32 ShadowIndex = 0; ShadowIndex < Atlas.Shadows.Num(); ShadowIndex++)
        {
            FProjectedShadowInfo* ProjectedShadowInfo = Atlas.Shadows[ShadowIndex];
            FVisibleLightInfo& VisibleLightInfo = VisibleLightInfos[ProjectedShadowInfo->GetLightSceneInfo().Id];
            ProjectedShadowInfo->GatherDynamicMeshElements(*this, VisibleLightInfo, ReusedViewsArray, DynamicIndexBuffer, DynamicVertexBuffer, DynamicReadBuffer);
        }
    }

    // 遍歷所有PreShadow快取的陰影圖, 收集陰影實體的網格元素.
    for (int32 ShadowIndex = 0; ShadowIndex < SortedShadowsForShadowDepthPass.PreshadowCache.Shadows.Num(); ShadowIndex++)
    {
        FProjectedShadowInfo* ProjectedShadowInfo = SortedShadowsForShadowDepthPass.PreshadowCache.Shadows[ShadowIndex];
        FVisibleLightInfo& VisibleLightInfo = VisibleLightInfos[ProjectedShadowInfo->GetLightSceneInfo().Id];
        ProjectedShadowInfo->GatherDynamicMeshElements(*this, VisibleLightInfo, ReusedViewsArray, DynamicIndexBuffer, DynamicVertexBuffer, DynamicReadBuffer);
    }

    // 遍歷所有透明物體陰影圖圖集, 收集每個圖集內的所有陰影實體的網格元素.
    for (int32 AtlasIndex = 0; AtlasIndex < SortedShadowsForShadowDepthPass.TranslucencyShadowMapAtlases.Num(); AtlasIndex++)
    {
        FSortedShadowMapAtlas& Atlas = SortedShadowsForShadowDepthPass.TranslucencyShadowMapAtlases[AtlasIndex];

        for (int32 ShadowIndex = 0; ShadowIndex < Atlas.Shadows.Num(); ShadowIndex++)
        {
            FProjectedShadowInfo* ProjectedShadowInfo = Atlas.Shadows[ShadowIndex];
            FVisibleLightInfo& VisibleLightInfo = VisibleLightInfos[ProjectedShadowInfo->GetLightSceneInfo().Id];
            ProjectedShadowInfo->GatherDynamicMeshElements(*this, VisibleLightInfo, ReusedViewsArray, DynamicIndexBuffer, DynamicVertexBuffer, DynamicReadBuffer);
        }
    }
}

5.6.3.12 陰影初始化總結

用于前面花費很多筆墨和小節來詳細剖析陰影初始化的具體步驟和細節,難免會讓很多童鞋望而生畏,那么本節就簡潔扼要地總結陰影初始化InitDynamicShadows的主要程序,如下:

  • 根據view、場景光源、控制臺變數初始化陰影相關標記,

  • 遍歷場景所有光源(Scene->Lights),執行以下操作:

    • 如果光源沒有開啟陰影或陰影質量太小,或者光源在所有view都不可見,忽略之,不執行陰影投射,

    • 如果是點光源全景陰影,則將該光源組件名字加入Scene的UsedWholeScenePointLightNames串列中,

    • 如果符合全景陰影的創建條件,呼叫CreateWholeSceneProjectedShadow:

      • 初始化陰影資料,計算陰影所需的解析度、過渡因子(FadeAlpha)等,
      • 若過渡因子太小(小于1/256),則直接回傳,
      • 遍歷光源的投射陰影數量,每次都執行解析度計算、位置(SizeX、SizeY)計算、需創建的陰影圖數量等,
        • 根據陰影圖數量創建同等個數的FProjectedShadowInfo陰影實體,對每個陰影實體:
          • 設定全景投射引數(視錐體邊界、變換矩陣、深度、深度偏移等)、過渡因子、快取模式等,
          • 加入VisibleLightInfo.MemStackProjectedShadows串列;如果是單通道點光源陰影,填充陰影實體的cubemap6個面的資料(視圖投影矩陣、包圍盒、遠*裁剪面等),并初始化錐體,
          • 對非光追距離場陰影,執行CPU側裁剪,構建光源視圖的凸面體,再遍歷光源的移動圖元串列,呼叫IntersectsConvexHulls讓光源包圍盒和圖元包圍盒求交測驗,相交的那些圖元才會添加到陰影實體的主體圖元串列中,對光源的靜態圖元做相似的操作,
          • 最后將需要渲染的陰影實體加入VisibleLightInfo.AllProjectedShadows串列中,
    • 針對兩類光源(移動和固定的光源、尚未構建的靜態光源)創建CSM(級聯陰影),呼叫AddViewDependentWholeSceneShadowsForView創建視圖關聯的CSM:

      • 遍歷所有view,針對每個view:
        • 如果不是主view,直接跳過后續步驟,
        • 獲取視圖相關的全景投影數量,創建同等數量的FProjectedShadowInfo陰影實體,給每個陰影實體設定全景投影引數,最后添加到陰影實體串列和待裁剪串列中,
    • 處理互動陰影(指光源和圖元之間的影響,包含PerObject陰影、透明陰影、自陰影等),遍歷光源的動態和靜態圖元,給每個圖元呼叫SetupInteractionShadows設定互動陰影:

      • 處理光源附加根組件,設定相關標記,如果存在附加根組件,跳過后續步驟,
      • 如果需要創建陰影實體,則呼叫CreatePerObjectProjectedShadow創建逐物體陰影:
        • 遍歷所有view,收集陰影相關的標記,
        • 如果本幀不可見且下一幀也沒有潛在可見,則直接回傳,跳過后續的陰影創建和設定,
        • 沒有有效的陰影組圖元,直接回傳,
        • 計算陰影的各類資料(陰影視錐、解析度、可見性標記、圖集位置等),
        • 若過渡因子(FadeAlpha)小于某個閾值(1.0/256.0),直接回傳,
        • 符合陰影創建條件且是不透明陰影,則創和設定建陰影實體,加入VisibleLightInfo.MemStackProjectedShadows串列中,如果本幀可見則加入到陰影實體的主體圖元串列,如果下一幀潛在可見則加入到VisibleLightInfo.OccludedPerObjectShadows的實體中,
        • 符合陰影創建條件且是半透明陰影,執行上步驟類似操作,
  • 呼叫InitProjectedShadowVisibility執行陰影可見性判定:

    • 遍歷場景的所有光源,針對每個光源:
      • 分配視圖內的投射陰影可見性和關聯的容器,
      • 遍歷可見光源所有的陰影實體,針對每個陰影實體:
        • 保存陰影索引,
        • 遍歷所有view,針對每個view:
          • 處理視圖關聯的陰影,跳過那些不在視圖視錐內的光源,
          • 計算主體圖元的視圖關聯資料,斷陰影是否被遮擋,設定陰影可見性標記,
          • 如果陰影可見且不是RSM,利用FViewElementPDI繪制陰影錐體,
  • 呼叫UpdatePreshadowCache清理舊的預計算陰影,嘗試增加新的到快取中:

    • 初始化紋理布局,
    • 遍歷所有快取的預陰影, 洗掉不在此幀渲染的實體,
    • 收集可以被快取的PreShadow串列,
    • 對PreShadow從大到小排序(更大的PreShadow在渲染深度時會有更多的物體),
    • 遍歷所有未快取的PreShadow,嘗試從紋理布局中給PreShadow找空間,若找到,則設定相關資料并添加到Scene->CachedPreshadows串列中,
  • 呼叫GatherShadowPrimitives收集圖元串列,以處理不同型別的陰影:

    • 如果沒有PreShadow且沒有視圖關聯的全景陰影(ViewDependentWholeSceneShadows),則直接回傳,
    • 如果允許八叉樹遍歷(GUseOctreeForShadowCulling決定),利用八叉樹遍歷Scene->PrimitiveOctree,針對每個孩子節點:
      • 檢查孩子節點是否至少在一個陰影(包含PreShadow和視圖相關的全景陰影)內,如果是,則push到節點容器中,
      • 如果圖元節點的元素大于0,從FMemStack創建一個FGatherShadowPrimitivesPacket實體,將該節點的相關資料存入其中,添加到FGatherShadowPrimitivesPacket實體串列中,
    • 如果是非八叉樹遍歷模式,則線性遍歷圖元,創建FGatherShadowPrimitivesPacket并加入到串列中,
    • 利用ParallelFor并行地過濾掉和陰影不相交的圖元,收集和陰影相交的圖元,
    • 收集最后階段,將受陰影影響的圖元加入陰影實體的SubjectPrimitive串列中,清理之前申請的資源,
  • 呼叫AllocateShadowDepthTargets分配陰影圖所需的渲染紋理:

    • 初始化不同型別的指定了分配器的陰影串列,
    • 遍歷所有光源,針對每個光源:
      • 遍歷光源的所有陰影實體,針對每個陰影實體:
        • 檢測陰影是否至少在一個view中可見,
        • 檢測陰影快取模式為可移動圖元時的條件可見性,
        • 其它特殊的可見性判斷,
        • 如果陰影可見,根據不同型別加入到不同的陰影實體串列中,
      • 排序級聯陰影,因為在級聯之間的混合要求是有序的,
      • 呼叫AllocateCSMDepthTargets分配CSM深度渲染紋理,
      • 處理PreShadow,
      • 依次分配點光源cubemap、RSM、快取的聚光燈、逐物體、透明陰影的渲染紋理,
      • 更新透明陰影圖的uniform buffer,
      • 洗掉完全沒有被使用的陰影快取,
  • 呼叫GatherShadowDynamicMeshElements收集陰影的動態網格元素:

    • 遍歷所有陰影圖圖集(ShadowMapAtlases),收集每個圖集內的所有陰影實體的網格元素,
    • 遍歷所有RSM陰影圖圖集(RSMAtlases),收集每個圖集內的所有陰影實體的網格元素,
    • 遍歷所有點光源立方體陰影圖(ShadowMapCubemaps),收集每個立方體陰影圖內的所有陰影實體的網格元素,
    • 遍歷所有PreShadow快取的陰影圖(PreshadowCache),收集陰影實體的網格元素,
    • 遍歷所有透明物體陰影圖圖集(TranslucencyShadowMapAtlases),收集每個圖集內的所有陰影實體的網格元素,

陰影初始化總結完了,由此可知陰影的處理非常非常復雜,涉及的邏輯和優化技術甚多,這里粗略總結一下陰影初始化階段涉及的優化技巧

  • 利用物體(視圖、光源、陰影、圖元)的簡單形狀做相交測驗,剔除不相交的陰影元素,
  • 利用各類標記(物體、視圖、控制臺、全域變數等等)及衍生標記,剔除不符合的陰影元素,
  • 利用中間資料(過渡因子、螢屏尺寸大小、深度值等等)剔除不符合的陰影元素,
  • 特殊資料結構(紋理布局、陰影圖集、八叉樹、連續線性陣列)和遍歷方式(并行、八叉樹、線性)提升執行效果,
  • 充分利用快取(PreShadowCache、潛在可見性等)減少渲染效果,
  • 對陰影型別進行排序,減少渲染狀態切換,減少CPU和GPU互動資料,提升快取命中率,
  • 不同粒度的遮擋剔除,

5.6.4 陰影渲染

上面分析完陰影的初始化階段,接下來分析陰影的渲染階段,陰影的渲染Pass在PrePass之后BasePass之前:

void FDeferredShadingSceneRenderer::Render(FRHICommandListImmediate& RHICmdList)
{
    (......)
    
    RenderPrePass(RHICmdList, ...);
    
    (......)
    
    // 陰影渲染Pass.
    RenderShadowDepthMaps(RHICmdList);
    
    (......)
    
    RenderBasePass(RHICmdList, ...);
    
    (......)
}

5.6.4.1 RenderShadowDepthMaps

下面直接進入RenderShadowDepthMaps分析原始碼:

// Engine\Source\Runtime\Renderer\Private\ShadowDepthRendering.cpp

void FSceneRenderer::RenderShadowDepthMaps(FRHICommandListImmediate& RHICmdList)
{
    (......)
    
    // 渲染陰影圖集. 
    FSceneRenderer::RenderShadowDepthMapAtlases(RHICmdList);

    // 渲染點光源陰影立方體圖.
    for (int32 CubemapIndex = 0; CubemapIndex < SortedShadowsForShadowDepthPass.ShadowMapCubemaps.Num(); CubemapIndex++)
    {
        const FSortedShadowMapAtlas& ShadowMap = SortedShadowsForShadowDepthPass.ShadowMapCubemaps[CubemapIndex];
        FSceneRenderTargetItem& RenderTarget = ShadowMap.RenderTargets.DepthTarget->GetRenderTargetItem();
        FIntPoint TargetSize = ShadowMap.RenderTargets.DepthTarget->GetDesc().Extent;

        FProjectedShadowInfo* ProjectedShadowInfo = ShadowMap.Shadows[0];

        // 是否可以并行繪制
        const bool bDoParallelDispatch = RHICmdList.IsImmediate() &&  // translucent shadows are draw on the render thread, using a recursive cmdlist (which is not immediate)
            GRHICommandList.UseParallelAlgorithms() && CVarParallelShadows.GetValueOnRenderThread() &&
            (ProjectedShadowInfo->IsWholeSceneDirectionalShadow() || CVarParallelShadowsNonWholeScene.GetValueOnRenderThread());
        
        FString LightNameWithLevel;
        GetLightNameForDrawEvent(ProjectedShadowInfo->GetLightSceneInfo().Proxy, LightNameWithLevel);

        // 設定Uniform Buffer.
        ProjectedShadowInfo->SetupShadowUniformBuffers(RHICmdList, Scene);

        // 陰影渲染Pass開始.
        auto BeginShadowRenderPass = [this, &RenderTarget, &SceneContext](FRHICommandList& InRHICmdList, bool bPerformClear)
        {
            FRHITexture* DepthTarget = RenderTarget.TargetableTexture;
            ERenderTargetLoadAction DepthLoadAction = bPerformClear ? ERenderTargetLoadAction::EClear : ERenderTargetLoadAction::ELoad;

            // 渲染Pass資訊.
            FRHIRenderPassInfo RPInfo(DepthTarget, MakeDepthStencilTargetActions(MakeRenderTargetActions(DepthLoadAction, ERenderTargetStoreAction::EStore), ERenderTargetActions::Load_Store), nullptr, FExclusiveDepthStencil::DepthWrite_StencilWrite);

            if (!GSupportsDepthRenderTargetWithoutColorRenderTarget)
            {
                RPInfo.ColorRenderTargets[0].Action = ERenderTargetActions::DontLoad_DontStore;
                RPInfo.ColorRenderTargets[0].ArraySlice = -1;
                RPInfo.ColorRenderTargets[0].MipIndex = 0;
                RPInfo.ColorRenderTargets[0].RenderTarget = SceneContext.GetOptionalShadowDepthColorSurface(InRHICmdList, DepthTarget->GetTexture2D()->GetSizeX(), DepthTarget->GetTexture2D()->GetSizeY());

                InRHICmdList.TransitionResource(EResourceTransitionAccess::EWritable, RPInfo.ColorRenderTargets[0].RenderTarget);
            }
            // 轉換渲染紋理狀態為可寫.
            InRHICmdList.TransitionResource(EResourceTransitionAccess::EWritable, DepthTarget);
            InRHICmdList.BeginRenderPass(RPInfo, TEXT("ShadowDepthCubeMaps"));
        };

        // 是否需要清理陰影圖.
        {
            bool bDoClear = true;

            if (ProjectedShadowInfo->CacheMode == SDCM_MovablePrimitivesOnly
                && Scene->CachedShadowMaps.FindChecked(ProjectedShadowInfo->GetLightSceneInfo().Id).bCachedShadowMapHasPrimitives)
            {
                // Skip the clear when we'll copy from a cached shadowmap
                bDoClear = false;
            }

            BeginShadowRenderPass(RHICmdList, bDoClear);
        }

        if (bDoParallelDispatch)
        {
            // In parallel mode this first pass will just be the clear.
            RHICmdList.EndRenderPass();
        }

        // 真正開始渲染陰影圖.
        ProjectedShadowInfo->RenderDepth(RHICmdList, this, BeginShadowRenderPass, bDoParallelDispatch);

        if (!bDoParallelDispatch)
        {
            RHICmdList.EndRenderPass();
        }

        // 轉換渲染紋理狀態為可讀.
        RHICmdList.TransitionResource(EResourceTransitionAccess::EReadable, RenderTarget.TargetableTexture);
    }
    
    // Preshadow快取.
    if (SortedShadowsForShadowDepthPass.PreshadowCache.Shadows.Num() > 0)
    {
        FSceneRenderTargetItem& RenderTarget = SortedShadowsForShadowDepthPass.PreshadowCache.RenderTargets.DepthTarget->GetRenderTargetItem();

        // 遍歷所有PreshadowCache的所有陰影實體.
        for (int32 ShadowIndex = 0; ShadowIndex < SortedShadowsForShadowDepthPass.PreshadowCache.Shadows.Num(); ShadowIndex++)
        {
            FProjectedShadowInfo* ProjectedShadowInfo = SortedShadowsForShadowDepthPass.PreshadowCache.Shadows[ShadowIndex];

            // 沒有被快取的才需要繪制.
            if (!ProjectedShadowInfo->bDepthsCached)
            {
                const bool bDoParallelDispatch = RHICmdList.IsImmediate() && GRHICommandList.UseParallelAlgorithms() && CVarParallelShadows.GetValueOnRenderThread() && (ProjectedShadowInfo->IsWholeSceneDirectionalShadow() || CVarParallelShadowsNonWholeScene.GetValueOnRenderThread());

                ProjectedShadowInfo->SetupShadowUniformBuffers(RHICmdList, Scene);

                auto BeginShadowRenderPass = [this, ProjectedShadowInfo](FRHICommandList& InRHICmdList, bool bPerformClear)
                {
                    FRHITexture* PreShadowCacheDepthZ = Scene->PreShadowCacheDepthZ->GetRenderTargetItem().TargetableTexture.GetReference();
                    InRHICmdList.TransitionResources(EResourceTransitionAccess::EWritable, &PreShadowCacheDepthZ, 1);

                    FRHIRenderPassInfo RPInfo(PreShadowCacheDepthZ, EDepthStencilTargetActions::LoadDepthStencil_StoreDepthStencil, nullptr, FExclusiveDepthStencil::DepthWrite_StencilWrite);

                    // Must preserve existing contents as the clear will be scissored
                    InRHICmdList.BeginRenderPass(RPInfo, TEXT("ShadowDepthMaps"));
                    ProjectedShadowInfo->ClearDepth(InRHICmdList, this, 0, nullptr, PreShadowCacheDepthZ, bPerformClear);
                };

                BeginShadowRenderPass(RHICmdList, true);

                if (bDoParallelDispatch)
                {
                    // In parallel mode the first pass is just the clear.
                    RHICmdList.EndRenderPass();
                }

                // 開始繪制.
                ProjectedShadowInfo->RenderDepth(RHICmdList, this, BeginShadowRenderPass, bDoParallelDispatch);

                if (!bDoParallelDispatch)
                {
                    RHICmdList.EndRenderPass();
                }

                // 已經繪制過, 標記已快取.
                ProjectedShadowInfo->bDepthsCached = true;
            }
        }

        RHICmdList.TransitionResource(EResourceTransitionAccess::EReadable, RenderTarget.TargetableTexture);
    }

    // 半透明物體陰影圖集.
    for (int32 AtlasIndex = 0; AtlasIndex < SortedShadowsForShadowDepthPass.TranslucencyShadowMapAtlases.Num(); AtlasIndex++)
    {
        const FSortedShadowMapAtlas& ShadowMapAtlas = SortedShadowsForShadowDepthPass.TranslucencyShadowMapAtlases[AtlasIndex];
        FIntPoint TargetSize = ShadowMapAtlas.RenderTargets.ColorTargets[0]->GetDesc().Extent;

        // 半透明陰影需要用到兩張渲染紋理.
        FSceneRenderTargetItem ColorTarget0 = ShadowMapAtlas.RenderTargets.ColorTargets[0]->GetRenderTargetItem();
        FSceneRenderTargetItem ColorTarget1 = ShadowMapAtlas.RenderTargets.ColorTargets[1]->GetRenderTargetItem();

        FRHITexture* RenderTargetArray[2] =
        {
            ColorTarget0.TargetableTexture,
            ColorTarget1.TargetableTexture
        };

        FRHIRenderPassInfo RPInfo(UE_ARRAY_COUNT(RenderTargetArray), RenderTargetArray, ERenderTargetActions::Load_Store);
        TransitionRenderPassTargets(RHICmdList, RPInfo);
        RHICmdList.BeginRenderPass(RPInfo, TEXT("RenderTranslucencyDepths"));
        {
            // 遍歷半透明陰影圖集的所有陰影實體, 執行半透明陰影深度繪制.
            for (int32 ShadowIndex = 0; ShadowIndex < ShadowMapAtlas.Shadows.Num(); ShadowIndex++)
            {
                FProjectedShadowInfo* ProjectedShadowInfo = ShadowMapAtlas.Shadows[ShadowIndex];
                // 渲染半透明陰影深度.
                ProjectedShadowInfo->RenderTranslucencyDepths(RHICmdList, this);
            }
        }
        RHICmdList.EndRenderPass();

        RHICmdList.TransitionResource(EResourceTransitionAccess::EReadable, ColorTarget0.TargetableTexture);
        RHICmdList.TransitionResource(EResourceTransitionAccess::EReadable, ColorTarget1.TargetableTexture);
    }

    // 設定LPV的RSM uniform Buffer, 以便后面可以并行提交繪制.
    {
        for (int32 ViewIdx = 0; ViewIdx < Views.Num(); ++ViewIdx)
        {
            FViewInfo& View = Views[ViewIdx];
            FSceneViewState* ViewState = View.ViewState;

            if (ViewState)
            {
                FLightPropagationVolume* Lpv = ViewState->GetLightPropagationVolume(FeatureLevel);

                if (Lpv)
                {
                    Lpv->SetRsmUniformBuffer();
                }
            }
        }
    }

    // 渲染RSM(Reflective Shadow Map, 反射陰影圖)圖集.
    for (int32 AtlasIndex = 0; AtlasIndex < SortedShadowsForShadowDepthPass.RSMAtlases.Num(); AtlasIndex++)
    {
        checkSlow(RHICmdList.IsOutsideRenderPass());

        const FSortedShadowMapAtlas& ShadowMapAtlas = SortedShadowsForShadowDepthPass.RSMAtlases[AtlasIndex];
        FSceneRenderTargetItem ColorTarget0 = ShadowMapAtlas.RenderTargets.ColorTargets[0]->GetRenderTargetItem();
        FSceneRenderTargetItem ColorTarget1 = ShadowMapAtlas.RenderTargets.ColorTargets[1]->GetRenderTargetItem();
        FSceneRenderTargetItem DepthTarget = ShadowMapAtlas.RenderTargets.DepthTarget->GetRenderTargetItem();
        FIntPoint TargetSize = ShadowMapAtlas.RenderTargets.DepthTarget->GetDesc().Extent;

        SCOPED_DRAW_EVENTF(RHICmdList, EventShadowDepths, TEXT("RSM%u %ux%u"), AtlasIndex, TargetSize.X, TargetSize.Y);

        for (int32 ShadowIndex = 0; ShadowIndex < ShadowMapAtlas.Shadows.Num(); ShadowIndex++)
        {
            FProjectedShadowInfo* ProjectedShadowInfo = ShadowMapAtlas.Shadows[ShadowIndex];
            SCOPED_GPU_MASK(RHICmdList, GetGPUMaskForShadow(ProjectedShadowInfo));

            const bool bDoParallelDispatch = RHICmdList.IsImmediate() &&  // translucent shadows are draw on the render thread, using a recursive cmdlist (which is not immediate)
                GRHICommandList.UseParallelAlgorithms() && CVarParallelShadows.GetValueOnRenderThread() &&
                (ProjectedShadowInfo->IsWholeSceneDirectionalShadow() || CVarParallelShadowsNonWholeScene.GetValueOnRenderThread());

            FSceneViewState* ViewState = (FSceneViewState*)ProjectedShadowInfo->DependentView->State;
            FLightPropagationVolume* LightPropagationVolume = ViewState->GetLightPropagationVolume(FeatureLevel);

            ProjectedShadowInfo->SetupShadowUniformBuffers(RHICmdList, Scene, LightPropagationVolume);

            auto BeginShadowRenderPass = [this, LightPropagationVolume, ProjectedShadowInfo, &ColorTarget0, &ColorTarget1, &DepthTarget](FRHICommandList& InRHICmdList, bool bPerformClear)
            {
                FRHITexture* RenderTargets[2];
                RenderTargets[0] = ColorTarget0.TargetableTexture;
                RenderTargets[1] = ColorTarget1.TargetableTexture;

                // Hook up the geometry volume UAVs
                FRHIUnorderedAccessView* Uavs[4];
                Uavs[0] = LightPropagationVolume->GetGvListBufferUav();
                Uavs[1] = LightPropagationVolume->GetGvListHeadBufferUav();
                Uavs[2] = LightPropagationVolume->GetVplListBufferUav();
                Uavs[3] = LightPropagationVolume->GetVplListHeadBufferUav();

                FRHIRenderPassInfo RPInfo(UE_ARRAY_COUNT(RenderTargets), RenderTargets, ERenderTargetActions::Load_Store);
                RPInfo.DepthStencilRenderTarget.Action = EDepthStencilTargetActions::LoadDepthStencil_StoreDepthStencil;
                RPInfo.DepthStencilRenderTarget.DepthStencilTarget = DepthTarget.TargetableTexture;
                RPInfo.DepthStencilRenderTarget.ExclusiveDepthStencil = FExclusiveDepthStencil::DepthWrite_StencilWrite;


                InRHICmdList.TransitionResources(EResourceTransitionAccess::ERWBarrier, EResourceTransitionPipeline::EGfxToGfx, Uavs, UE_ARRAY_COUNT(Uavs));
                InRHICmdList.BeginRenderPass(RPInfo, TEXT("ShadowAtlas"));

                ProjectedShadowInfo->ClearDepth(InRHICmdList, this, UE_ARRAY_COUNT(RenderTargets), RenderTargets, DepthTarget.TargetableTexture, bPerformClear);
            };

            {
                SCOPED_DRAW_EVENT(RHICmdList, Clear);
                BeginShadowRenderPass(RHICmdList, true);
            }

            // In parallel mode the first renderpass is just the clear.
            if (bDoParallelDispatch)
            {
                RHICmdList.EndRenderPass();
            }

            ProjectedShadowInfo->RenderDepth(RHICmdList, this, BeginShadowRenderPass, bDoParallelDispatch);

            if (!bDoParallelDispatch)
            {
                RHICmdList.EndRenderPass();
            }
            {
                // Resolve the shadow depth z surface.
                RHICmdList.CopyToResolveTarget(DepthTarget.TargetableTexture, DepthTarget.ShaderResourceTexture, FResolveParams());
                RHICmdList.CopyToResolveTarget(ColorTarget0.TargetableTexture, ColorTarget0.ShaderResourceTexture, FResolveParams());
                RHICmdList.CopyToResolveTarget(ColorTarget1.TargetableTexture, ColorTarget1.ShaderResourceTexture, FResolveParams());

                FRHIUnorderedAccessView* UavsToReadable[2];
                UavsToReadable[0] = LightPropagationVolume->GetGvListBufferUav();
                UavsToReadable[1] = LightPropagationVolume->GetGvListHeadBufferUav();
                RHICmdList.TransitionResources(EResourceTransitionAccess::EReadable, EResourceTransitionPipeline::EGfxToGfx, UavsToReadable, UE_ARRAY_COUNT(UavsToReadable));
            }
        }
    }
}

總結陰影渲染流程,就是先后繪制全景陰影圖集、點光源陰影立方體圖、PreShadow、半透明物體陰影圖集、RSM,期間會呼叫FProjectedShadowInfo的RenderDepth和RenderTranslucencyDepths渲染不透明陰影和半透明陰影,

5.6.4.2 FProjectedShadowInfo::RenderDepth

// Engine\Source\Runtime\Renderer\Private\ShadowDepthRendering.cpp

void FProjectedShadowInfo::RenderDepth(FRHICommandListImmediate& RHICmdList, FSceneRenderer* SceneRenderer, FBeginShadowRenderPassFunction BeginShadowRenderPass, bool bDoParallelDispatch)
{
    RenderDepthInner(RHICmdList, SceneRenderer, BeginShadowRenderPass, bDoParallelDispatch);
}

void FProjectedShadowInfo::RenderDepthInner(FRHICommandListImmediate& RHICmdList, FSceneRenderer* SceneRenderer, FBeginShadowRenderPassFunction BeginShadowRenderPass, bool bDoParallelDispatch)
{
    const ERHIFeatureLevel::Type FeatureLevel = ShadowDepthView->FeatureLevel;
    FRHIUniformBuffer* PassUniformBuffer = ShadowDepthPassUniformBuffer;

    const bool bIsWholeSceneDirectionalShadow = IsWholeSceneDirectionalShadow();

    // *行光全景陰影的Uniform Buffer更新.
    if (bIsWholeSceneDirectionalShadow)
    {
        // 利用快取的Uniform Buffer更新陰影視圖的Uniform Buffer.
        ShadowDepthView->ViewUniformBuffer.UpdateUniformBufferImmediate(*ShadowDepthView->CachedViewUniformShaderParameters);
        
        // 如果存在依賴視圖, 遍歷所有持續存在的視圖UB擴展, 執行開始渲染命令.
        if (DependentView)
        {
            extern TSet<IPersistentViewUniformBufferExtension*> PersistentViewUniformBufferExtensions;

            for (IPersistentViewUniformBufferExtension* Extension : PersistentViewUniformBufferExtensions)
            {
                Extension->BeginRenderView(DependentView);
            }
        }
    }

    // 移動端*臺的繪制的Uniform Buffer更新.
    if (FSceneInterface::GetShadingPath(FeatureLevel) == EShadingPath::Mobile)
    {
        FMobileShadowDepthPassUniformParameters ShadowDepthPassParameters;
        SetupShadowDepthPassUniformBuffer(this, RHICmdList, *ShadowDepthView, ShadowDepthPassParameters);
        SceneRenderer->Scene->UniformBuffers.MobileCSMShadowDepthPassUniformBuffer.UpdateUniformBufferImmediate(ShadowDepthPassParameters);
        MobileShadowDepthPassUniformBuffer.UpdateUniformBufferImmediate(ShadowDepthPassParameters);
        PassUniformBuffer = SceneRenderer->Scene->UniformBuffers.MobileCSMShadowDepthPassUniformBuffer;
    }

    // 設定網格Pass的渲染狀態.
    FMeshPassProcessorRenderState DrawRenderState(*ShadowDepthView, PassUniformBuffer);
    SetStateForShadowDepth(bReflectiveShadowmap, bOnePassPointLightShadow, DrawRenderState);
    SetStateForView(RHICmdList);

    if (CacheMode == SDCM_MovablePrimitivesOnly)
    {
        if (bDoParallelDispatch)
        {
            BeginShadowRenderPass(RHICmdList, false);
        }

        // 在渲染可移動圖元的深度之前拷貝靜態圖元的深度.
        CopyCachedShadowMap(RHICmdList, DrawRenderState, SceneRenderer, *ShadowDepthView);

        if (bDoParallelDispatch)
        {
            RHICmdList.EndRenderPass();
        }
    }

    // 并行繪制
    if (bDoParallelDispatch)
    {
        bool bFlush = CVarRHICmdFlushRenderThreadTasksShadowPass.GetValueOnRenderThread() > 0
            || CVarRHICmdFlushRenderThreadTasks.GetValueOnRenderThread() > 0;
        FScopedCommandListWaitForTasks Flusher(bFlush);

        // 發送渲染命令.
        {
            FShadowParallelCommandListSet ParallelCommandListSet(*ShadowDepthView, SceneRenderer, RHICmdList, CVarRHICmdShadowDeferredContexts.GetValueOnRenderThread() > 0, !bFlush, DrawRenderState, *this, BeginShadowRenderPass);

            ShadowDepthPass.DispatchDraw(&ParallelCommandListSet, RHICmdList);
        }
    }
    // 非并行繪制
    else
    {
        ShadowDepthPass.DispatchDraw(nullptr, RHICmdList);
    }
}

由此看到,深度渲染的Pass和場景的深度Pass大同小異,只是部分狀態處理略有差異,

5.6.4.3 FProjectedShadowInfo::RenderTranslucencyDepths

// Engine\Source\Runtime\Renderer\Private\TranslucentLighting.cpp

void FProjectedShadowInfo::RenderTranslucencyDepths(FRHICommandList& RHICmdList, FSceneRenderer* SceneRenderer)
{
    // 設定透明深度Pass的Uniform Buffer.
    FTranslucencyDepthPassUniformParameters TranslucencyDepthPassParameters;
    SetupTranslucencyDepthPassUniformBuffer(this, RHICmdList, *ShadowDepthView, TranslucencyDepthPassParameters);
    TUniformBufferRef<FTranslucencyDepthPassUniformParameters> PassUniformBuffer = TUniformBufferRef<FTranslucencyDepthPassUniformParameters>::CreateUniformBufferImmediate(TranslucencyDepthPassParameters, UniformBuffer_SingleFrame, EUniformBufferValidation::None);

    // 設定MeshPassProcessor的渲染狀態.
    FMeshPassProcessorRenderState DrawRenderState(*ShadowDepthView, PassUniformBuffer);
    {
        // 清理陰影和邊框.
        RHICmdList.SetViewport(
            X, Y, 0.0f,
            // 注意視口長寬包含了2*BorderSize.
            (X + BorderSize * 2 + ResolutionX),
            (Y + BorderSize * 2 + ResolutionY), 1.0f);

        FLinearColor ClearColors[2] = {FLinearColor(0,0,0,0), FLinearColor(0,0,0,0)};
        DrawClearQuadMRT(RHICmdList, true, UE_ARRAY_COUNT(ClearColors), ClearColors, false, 1.0f, false, 0);

        // Set the viewport for the shadow.
        RHICmdList.SetViewport(
            (X + BorderSize), (Y + BorderSize), 0.0f,
            // 注意視口長寬包含了BorderSize(不是上面的2*BorderSize).
            (X + BorderSize + ResolutionX),
            (Y + BorderSize + ResolutionY), 1.0f);

        DrawRenderState.SetDepthStencilState(TStaticDepthStencilState<false, CF_Always>::GetRHI());
        // 混合狀態為疊加.
        DrawRenderState.SetBlendState(TStaticBlendState<
            CW_RGBA, BO_Add, BF_One, BF_One, BO_Add, BF_One, BF_One,
            CW_RGBA, BO_Add, BF_One, BF_One, BO_Add, BF_One, BF_One>::GetRHI());

        // 可見的MeshDrawCommand串列.
        FMeshCommandOneFrameArray VisibleMeshDrawCommands;
        FDynamicPassMeshDrawListContext TranslucencyDepthContext(DynamicMeshDrawCommandStorage, VisibleMeshDrawCommands, GraphicsMinimalPipelineStateSet, NeedsShaderInitialisation);

        // 宣告透明深度Pass處理器.
        FTranslucencyDepthPassMeshProcessor TranslucencyDepthPassMeshProcessor(
            SceneRenderer->Scene,
            ShadowDepthView,
            DrawRenderState,
            this,
            &TranslucencyDepthContext);

        // 遍歷所有動態透明網格元素
        for (int32 MeshBatchIndex = 0; MeshBatchIndex < DynamicSubjectTranslucentMeshElements.Num(); MeshBatchIndex++)
        {
            const FMeshBatchAndRelevance& MeshAndRelevance = DynamicSubjectTranslucentMeshElements[MeshBatchIndex];
            const uint64 BatchElementMask = ~0ull;
            // 將透明MeshBatch加入到Processor中, 以便轉換成MeshDrawCommand.
            TranslucencyDepthPassMeshProcessor.AddMeshBatch(*MeshAndRelevance.Mesh, BatchElementMask, MeshAndRelevance.PrimitiveSceneProxy);
        }

        // 遍歷所有靜態透明網格圖元.
        for (int32 PrimitiveIndex = 0; PrimitiveIndex < SubjectTranslucentPrimitives.Num(); PrimitiveIndex++)
        {
            const FPrimitiveSceneInfo* PrimitiveSceneInfo = SubjectTranslucentPrimitives[PrimitiveIndex];
            int32 PrimitiveId = PrimitiveSceneInfo->GetIndex();
            FPrimitiveViewRelevance ViewRelevance = ShadowDepthView->PrimitiveViewRelevanceMap[PrimitiveId];

            if (!ViewRelevance.bInitializedThisFrame)
            {
                // Compute the subject primitive's view relevance since it wasn't cached
                ViewRelevance = PrimitiveSceneInfo->Proxy->GetViewRelevance(ShadowDepthView);
            }

            if (ViewRelevance.bDrawRelevance && ViewRelevance.bStaticRelevance)
            {
                for (int32 MeshIndex = 0; MeshIndex < PrimitiveSceneInfo->StaticMeshes.Num(); MeshIndex++)
                {
                    const FStaticMeshBatch& StaticMeshBatch = PrimitiveSceneInfo->StaticMeshes[MeshIndex];
                    const uint64 BatchElementMask = StaticMeshBatch.bRequiresPerElementVisibility ? ShadowDepthView->StaticMeshBatchVisibility[StaticMeshBatch.BatchVisibilityId] : ~0ull;
                    TranslucencyDepthPassMeshProcessor.AddMeshBatch(StaticMeshBatch, BatchElementMask, StaticMeshBatch.PrimitiveSceneInfo->Proxy, StaticMeshBatch.Id);
                }
            }
        }

        // 存在有效的網格繪制指令才真正提交繪制指令.
        if (VisibleMeshDrawCommands.Num() > 0)
        {
            const bool bDynamicInstancing = IsDynamicInstancingEnabled(ShadowDepthView->FeatureLevel);

            FRHIVertexBuffer* PrimitiveIdVertexBuffer = nullptr;
            // 視圖的資料到網格繪制指令.
            ApplyViewOverridesToMeshDrawCommands(*ShadowDepthView, VisibleMeshDrawCommands, DynamicMeshDrawCommandStorage, GraphicsMinimalPipelineStateSet, NeedsShaderInitialisation);
            // 排序網格繪制指令.
            SortAndMergeDynamicPassMeshDrawCommands(SceneRenderer->FeatureLevel, VisibleMeshDrawCommands, DynamicMeshDrawCommandStorage, PrimitiveIdVertexBuffer, 1);
            // 提交網格繪制指令.
            SubmitMeshDrawCommands(VisibleMeshDrawCommands, GraphicsMinimalPipelineStateSet, PrimitiveIdVertexBuffer, 0, bDynamicInstancing, 1, RHICmdList);
        }
    }
}

由此可知,渲染半透明物體陰影時,跟不透明不一樣的是,它在渲染階段才會通過FTranslucencyDepthPassMeshProcessor將FMesh轉成FMeshDrawCommand,存在有效指令才會對指令排序并真正提交繪制,

5.6.4.4 RenderShadowDepthMapAtlases

RenderShadowDepthMaps的最開頭就是呼叫RenderShadowDepthMapAtlases繪制CSM陰影圖集,下面是它的代碼剖析:

void FSceneRenderer::RenderShadowDepthMapAtlases(FRHICommandListImmediate& RHICmdList)
{
    FSceneRenderTargets& SceneContext = FSceneRenderTargets::Get(RHICmdList);

    // 是否可用并行繪制.
    bool bCanUseParallelDispatch = RHICmdList.IsImmediate() &&
        GRHICommandList.UseParallelAlgorithms() && CVarParallelShadows.GetValueOnRenderThread();

    // 遍歷所有陰影圖圖集.
    for (int32 AtlasIndex = 0; AtlasIndex < SortedShadowsForShadowDepthPass.ShadowMapAtlases.Num(); AtlasIndex++)
    {
        const FSortedShadowMapAtlas& ShadowMapAtlas = SortedShadowsForShadowDepthPass.ShadowMapAtlases[AtlasIndex];
        // 渲染紋理.
        FSceneRenderTargetItem& RenderTarget = ShadowMapAtlas.RenderTargets.DepthTarget->GetRenderTargetItem();
        FIntPoint AtlasSize = ShadowMapAtlas.RenderTargets.DepthTarget->GetDesc().Extent;

        // 開始陰影渲染Pass.
        auto BeginShadowRenderPass = [this, &RenderTarget, &SceneContext](FRHICommandList& InRHICmdList, bool bPerformClear)
        {
            ERenderTargetLoadAction DepthLoadAction = bPerformClear ? ERenderTargetLoadAction::EClear : ERenderTargetLoadAction::ELoad;

            // 渲染Pass資訊.
            FRHIRenderPassInfo RPInfo(RenderTarget.TargetableTexture, MakeDepthStencilTargetActions(MakeRenderTargetActions(DepthLoadAction, ERenderTargetStoreAction::EStore), ERenderTargetActions::Load_Store), nullptr, FExclusiveDepthStencil::DepthWrite_StencilWrite);

            // 如果不支持不帶顏色的深度渲染紋理, 則重新分配渲染紋理, 改變其Action狀態.
            if (!GSupportsDepthRenderTargetWithoutColorRenderTarget)
            {
                RPInfo.ColorRenderTargets[0].Action = ERenderTargetActions::DontLoad_DontStore;
                RPInfo.ColorRenderTargets[0].RenderTarget = SceneContext.GetOptionalShadowDepthColorSurface(InRHICmdList, RPInfo.DepthStencilRenderTarget.DepthStencilTarget->GetTexture2D()->GetSizeX(), RPInfo.DepthStencilRenderTarget.DepthStencilTarget->GetTexture2D()->GetSizeY());
                InRHICmdList.TransitionResource(EResourceTransitionAccess::EWritable, RPInfo.ColorRenderTargets[0].RenderTarget);
            }
            InRHICmdList.TransitionResource(EResourceTransitionAccess::EWritable, RPInfo.DepthStencilRenderTarget.DepthStencilTarget);
            InRHICmdList.BeginRenderPass(RPInfo, TEXT("ShadowMapAtlases"));

            if (!bPerformClear)
            {
                InRHICmdList.BindClearMRTValues(false, true, false);
            }
        };

        TArray<FProjectedShadowInfo*, SceneRenderingAllocator> ParallelShadowPasses;
        TArray<FProjectedShadowInfo*, SceneRenderingAllocator> SerialShadowPasses;

        // 在此處收集渲染Pass, 以最小化切換渲染Pass.
        for (int32 ShadowIndex = 0; ShadowIndex < ShadowMapAtlas.Shadows.Num(); ShadowIndex++)
        {
            FProjectedShadowInfo* ProjectedShadowInfo = ShadowMapAtlas.Shadows[ShadowIndex];

            const bool bDoParallelDispatch = bCanUseParallelDispatch &&
                (ProjectedShadowInfo->IsWholeSceneDirectionalShadow() || CVarParallelShadowsNonWholeScene.GetValueOnRenderThread());

            // 根據是否并行加入到不同的渲染Pass串列.
            if (bDoParallelDispatch)
            {
                // 并行串列.
                ParallelShadowPasses.Add(ProjectedShadowInfo);
            }
            else
            {
                // 連續串列.
                SerialShadowPasses.Add(ProjectedShadowInfo);
            }
        }

        FLightSceneProxy* CurrentLightForDrawEvent = NULL;

        // 并行渲染佇列.
        if (ParallelShadowPasses.Num() > 0)
        {
            {
                // Clear before going wide.
                BeginShadowRenderPass(RHICmdList, true);
                RHICmdList.EndRenderPass();
            }

            for (int32 ShadowIndex = 0; ShadowIndex < ParallelShadowPasses.Num(); ShadowIndex++)
            {
                FProjectedShadowInfo* ProjectedShadowInfo = ParallelShadowPasses[ShadowIndex];
                
                (......)
                
                ProjectedShadowInfo->SetupShadowUniformBuffers(RHICmdList, Scene);
                ProjectedShadowInfo->TransitionCachedShadowmap(RHICmdList, Scene);
                // 呼叫陰影實體的渲染深度介面.
                ProjectedShadowInfo->RenderDepth(RHICmdList, this, BeginShadowRenderPass, true);
            }
        }

        CurrentLightForDrawEvent = nullptr;

        // 非并行繪制方式.
        if (SerialShadowPasses.Num() > 0)
        {
            bool bForceSingleRenderPass = CVarShadowForceSerialSingleRenderPass.GetValueOnAnyThread() != 0;
            if(bForceSingleRenderPass)
            {
                BeginShadowRenderPass(RHICmdList, true);
            }

            for (int32 ShadowIndex = 0; ShadowIndex < SerialShadowPasses.Num(); ShadowIndex++)
            {
                FProjectedShadowInfo* ProjectedShadowInfo = SerialShadowPasses[ShadowIndex];

                (......)

                ProjectedShadowInfo->SetupShadowUniformBuffers(RHICmdList, Scene);
                ProjectedShadowInfo->TransitionCachedShadowmap(RHICmdList, Scene);
                if (!bForceSingleRenderPass)
                {
                    BeginShadowRenderPass(RHICmdList, ShadowIndex == 0);
                }
                
                // 呼叫陰影實體的渲染深度介面.
                ProjectedShadowInfo->RenderDepth(RHICmdList, this, BeginShadowRenderPass, false);
                
                if(!bForceSingleRenderPass)
                {
                    RHICmdList.EndRenderPass();
                }
            }
            if(bForceSingleRenderPass)
            {
                RHICmdList.EndRenderPass();
            }
        }
        
        // 轉換渲染紋理為可讀取.
        RHICmdList.TransitionResource(EResourceTransitionAccess::EReadable, RenderTarget.TargetableTexture);
    }
}

5.6.5 陰影應用

前面兩節詳盡地剖析了陰影的初始化和渲染邏輯,那么渲染完成的深度圖是怎么被應用到光照當中的呢?本節將揭曉期間的技術細節,

5.6.5.1 RenderLights

陰影圖的應用還是要從RenderLights介面開始著色分析,雖然上一篇文章已經介紹過RenderLights的邏輯,不過這里主要聚焦在陰影的應用邏輯:

void FDeferredShadingSceneRenderer::RenderLights(FRHICommandListImmediate& RHICmdList, FSortedLightSetSceneInfo &SortedLightSet, ...)
{
    (......)
    
    const TArray<FSortedLightSceneInfo, SceneRenderingAllocator> &SortedLights = SortedLightSet.SortedLights;
    const int32 AttenuationLightStart = SortedLightSet.AttenuationLightStart;

    (......)
    
    {
        SCOPED_DRAW_EVENT(RHICmdList, DirectLighting);

        FSceneRenderTargets& SceneContext = FSceneRenderTargets::Get(RHICmdList);
        EShaderPlatform ShaderPlatformForFeatureLevel = GShaderPlatformForFeatureLevel[FeatureLevel];
        
        (......)

        // 處理帶陰影的光照.
        {
            SCOPED_DRAW_EVENT(RHICmdList, ShadowedLights);

            (......)

            bool bDirectLighting = ViewFamily.EngineShowFlags.DirectLighting;
            bool bShadowMaskReadable = false;
            TRefCountPtr<IPooledRenderTarget> ScreenShadowMaskTexture;
            TRefCountPtr<IPooledRenderTarget> ScreenShadowMaskSubPixelTexture;
            
            // 遍歷所有光源,繪制帶陰影和光照函式的光源.
            for (int32 LightIndex = AttenuationLightStart; LightIndex < SortedLights.Num(); LightIndex++)
            {
                const FSortedLightSceneInfo& SortedLightInfo = SortedLights[LightIndex];
                const FLightSceneInfo& LightSceneInfo = *SortedLightInfo.LightSceneInfo;

                const bool bDrawShadows = SortedLightInfo.SortKey.Fields.bShadowed && !ShouldRenderRayTracingStochasticRectLight(LightSceneInfo);
                bool bUsedShadowMaskTexture = false;
                
                (......)

                // 分配螢屏陰影遮蔽紋理.
                if ((bDrawShadows || bDrawLightFunction || bDrawPreviewIndicator) && !ScreenShadowMaskTexture.IsValid())
                {
                    SceneContext.AllocateScreenShadowMask(RHICmdList, ScreenShadowMaskTexture);
                    bShadowMaskReadable = false;
                    
                    (......)
                }

                // 繪制陰影.
                if (bDrawShadows)
                {
                    INC_DWORD_STAT(STAT_NumShadowedLights);

                    const FLightOcclusionType OcclusionType = GetLightOcclusionType(*LightSceneInfo.Proxy);

                    (......)
                    // 陰影圖遮蔽型別.
                    else // (OcclusionType == FOcclusionType::Shadowmap)
                    {
                        (......)
                    
                        // 清理陰影遮蔽.
                        auto ClearShadowMask = [&](TRefCountPtr<IPooledRenderTarget>& InScreenShadowMaskTexture)
                        {
                            const bool bClearLightScreenExtentsOnly = CVarAllowClearLightSceneExtentsOnly.GetValueOnRenderThread() && SortedLightInfo.SortKey.Fields.LightType != LightType_Directional;
                            bool bClearToWhite = !bClearLightScreenExtentsOnly;

                            // 陰影渲染通道資訊.
                            FRHIRenderPassInfo RPInfo(InScreenShadowMaskTexture->GetRenderTargetItem().TargetableTexture, ERenderTargetActions::Load_Store);
                            RPInfo.DepthStencilRenderTarget.Action = MakeDepthStencilTargetActions(ERenderTargetActions::Load_DontStore, ERenderTargetActions::Load_Store);
                            RPInfo.DepthStencilRenderTarget.DepthStencilTarget = SceneContext.GetSceneDepthSurface();
                            RPInfo.DepthStencilRenderTarget.ExclusiveDepthStencil = FExclusiveDepthStencil::DepthRead_StencilWrite;
                            if (bClearToWhite)
                            {
                                RPInfo.ColorRenderTargets[0].Action = ERenderTargetActions::Clear_Store;
                            }

                            // 轉換渲染Pass的渲染目標.
                            TransitionRenderPassTargets(RHICmdList, RPInfo);
                            RHICmdList.BeginRenderPass(RPInfo, TEXT("ClearScreenShadowMask"));
                            if (bClearLightScreenExtentsOnly)
                            {
                                SCOPED_DRAW_EVENT(RHICmdList, ClearQuad);
    
                                for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
                                {
                                    const FViewInfo& View = Views[ViewIndex];
                                    FIntRect ScissorRect;

                                    if (!LightSceneInfo.Proxy->GetScissorRect(ScissorRect, View, View.ViewRect))
                                    {
                                        ScissorRect = View.ViewRect;
                                    }

                                    if (ScissorRect.Min.X < ScissorRect.Max.X && ScissorRect.Min.Y < ScissorRect.Max.Y)
                                    {
                                        RHICmdList.SetViewport(ScissorRect.Min.X, ScissorRect.Min.Y, 0.0f, ScissorRect.Max.X, ScissorRect.Max.Y, 1.0f);
                                        // 陰影遮蔽紋理原始狀態是全白.
                                        DrawClearQuad(RHICmdList, true, FLinearColor(1, 1, 1, 1), false, 0, false, 0);
                                    }
                                    else
                                    {
                                        LightSceneInfo.Proxy->GetScissorRect(ScissorRect, View, View.ViewRect);
                                    }
                                }
                            }
                            RHICmdList.EndRenderPass();
                        };

                        // 清理螢屏的陰影遮蔽.
                        ClearShadowMask(ScreenShadowMaskTexture);
                        // 清理螢屏的陰影遮蔽子像素紋理.
                        if (ScreenShadowMaskSubPixelTexture)
                        {
                            ClearShadowMask(ScreenShadowMaskSubPixelTexture);
                        }

                        // 渲染陰影投射.
                        RenderShadowProjections(RHICmdList, &LightSceneInfo, ScreenShadowMaskTexture, ScreenShadowMaskSubPixelTexture, HairDatas, bInjectedTranslucentVolume);
                    }

                    // 標記已渲染陰影遮蔽紋理.
                    bUsedShadowMaskTexture = true;
                }

                (......)

                // 使用陰影遮蔽紋理.
                if (bUsedShadowMaskTexture)
                {
                    // 拷貝并決議陰影遮蔽紋理.
                    RHICmdList.CopyToResolveTarget(ScreenShadowMaskTexture->GetRenderTargetItem().TargetableTexture, ScreenShadowMaskTexture->GetRenderTargetItem().ShaderResourceTexture, FResolveParams(FResolveRect()));
                    if (ScreenShadowMaskSubPixelTexture)
                    {
                        RHICmdList.CopyToResolveTarget(ScreenShadowMaskSubPixelTexture->GetRenderTargetItem().TargetableTexture, ScreenShadowMaskSubPixelTexture->GetRenderTargetItem().ShaderResourceTexture, FResolveParams(FResolveRect()));
                    }

                    // 轉換ScreenShadowMaskTexture為可讀.
                    if (!bShadowMaskReadable)
                    {
                        RHICmdList.TransitionResource(EResourceTransitionAccess::EReadable, ScreenShadowMaskTexture->GetRenderTargetItem().ShaderResourceTexture);
                        if (ScreenShadowMaskSubPixelTexture)
                        {
                            RHICmdList.TransitionResource(EResourceTransitionAccess::EReadable, ScreenShadowMaskSubPixelTexture->GetRenderTargetItem().ShaderResourceTexture);
                        }
                        bShadowMaskReadable = true;
                    }
                }

                (......)
                
                else
                {
                    // 計算標準延遲光照, 包含陰影.
                    SCOPED_DRAW_EVENT(RHICmdList, StandardDeferredLighting);
                    SceneContext.BeginRenderingSceneColor(RHICmdList, ESimpleRenderTargetMode::EExistingColorAndDepth, FExclusiveDepthStencil::DepthRead_StencilWrite, true);

                    // 光照圖可能已被上一個燈光創建過, 但只有本光源有寫入有效資料, 才可用. 故而用bUsedShadowMaskTexture來判斷而不是用ScreenShadowMaskTexture的地址判斷.
                    IPooledRenderTarget* LightShadowMaskTexture = nullptr;
                    IPooledRenderTarget* LightShadowMaskSubPixelTexture = nullptr;
                    if (bUsedShadowMaskTexture)
                    {
                        LightShadowMaskTexture = ScreenShadowMaskTexture;
                        LightShadowMaskSubPixelTexture = ScreenShadowMaskSubPixelTexture;
                    }

                    // 渲染延遲光照, 其中LightShadowMaskTexture就是光源LightSceneInfo的陰影資訊.
                    if (bDirectLighting)
                    {
                        RenderLight(RHICmdList, &LightSceneInfo, LightShadowMaskTexture, InHairVisibilityViews, false, true);
                    }

                    SceneContext.FinishRenderingSceneColor(RHICmdList);

                    (......)
                }
            }
        } // shadowed lights
    }
}

5.6.5.2 RenderShadowProjections

上一小節有呼叫RenderShadowProjections為光源再次渲染螢屏空間的陰影遮蔽紋理,代碼如下:

// Engine\Source\Runtime\Renderer\Private\ShadowRendering.cpp

bool FDeferredShadingSceneRenderer::RenderShadowProjections(FRHICommandListImmediate& RHICmdList, const FLightSceneInfo* LightSceneInfo, IPooledRenderTarget* ScreenShadowMaskTexture, IPooledRenderTarget* ScreenShadowMaskSubPixelTexture, ...)
{
    (......)

    FVisibleLightInfo& VisibleLightInfo = VisibleLightInfos[LightSceneInfo->Id];

    // 呼叫父類渲染陰影投射.
    FSceneRenderer::RenderShadowProjections(RHICmdList, LightSceneInfo, ScreenShadowMaskTexture, ScreenShadowMaskSubPixelTexture, false, false, ...);

    // 遍歷所有可見光源待投射的陰影.
    for (int32 ShadowIndex = 0; ShadowIndex < VisibleLightInfo.ShadowsToProject.Num(); ShadowIndex++)
    {
        FProjectedShadowInfo* ProjectedShadowInfo = VisibleLightInfo.ShadowsToProject[ShadowIndex];

        // 處理透明體積陰影.
        if (ProjectedShadowInfo->bAllocated
            && ProjectedShadowInfo->bWholeSceneShadow
            && !ProjectedShadowInfo->bRayTracedDistanceField
            && (!LightSceneInfo->Proxy->HasStaticShadowing() || ProjectedShadowInfo->IsWholeSceneDirectionalShadow()))
        {
            (......)
        }
    }

    // 渲染膠囊體直接陰影.
    RenderCapsuleDirectShadows(RHICmdList, *LightSceneInfo, ScreenShadowMaskTexture, VisibleLightInfo.CapsuleShadowsToProject, false);

    // 高度場陰影.
    for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
    {
        (......)
    }

    // 頭發陰影.
    if (HairDatas)
    {
        RenderHairStrandsShadowMask(RHICmdList, Views, LightSceneInfo, HairDatas, ScreenShadowMaskTexture);
    }

    return true;
}

bool FSceneRenderer::RenderShadowProjections(FRHICommandListImmediate& RHICmdList, const FLightSceneInfo* LightSceneInfo, IPooledRenderTarget* ScreenShadowMaskTexture, IPooledRenderTarget* ScreenShadowMaskSubPixelTexture, ...)
{
    FVisibleLightInfo& VisibleLightInfo = VisibleLightInfos[LightSceneInfo->Id];
    FSceneRenderTargets& SceneContext = FSceneRenderTargets::Get(RHICmdList);

    // 收集光源的所有陰影資訊, 以便后面只需一個Pass可以渲染完成.
    TArray<FProjectedShadowInfo*> DistanceFieldShadows; // 距離場陰影.
    TArray<FProjectedShadowInfo*> NormalShadows; // 普遍陰影.

    // 收集光源的陰影實體, 按型別分別放到距離場或普遍陰影串列中.
    for (int32 ShadowIndex = 0; ShadowIndex < VisibleLightInfo.ShadowsToProject.Num(); ShadowIndex++)
    {
        FProjectedShadowInfo* ProjectedShadowInfo = VisibleLightInfo.ShadowsToProject[ShadowIndex];
        // 收集距離場或普遍陰影.
        if (ProjectedShadowInfo->bRayTracedDistanceField)
        {
            DistanceFieldShadows.Add(ProjectedShadowInfo);
        }
        else
        {
            NormalShadows.Add(ProjectedShadowInfo);
            if (ProjectedShadowInfo->bAllocated && ProjectedShadowInfo->RenderTargets.DepthTarget 
                && !bMobileModulatedProjections) 
            {
                // 轉換資源狀態為可讀.
                RHICmdList.TransitionResource(EResourceTransitionAccess::EReadable, ProjectedShadowInfo->RenderTargets.DepthTarget->GetRenderTargetItem().ShaderResourceTexture.GetReference());
            }
        }
    }

    // 普通陰影渲染.
    if (NormalShadows.Num() > 0)
    {
        // 渲染陰影遮蔽介面.
        auto RenderShadowMask = [&](const FHairStrandsVisibilityViews* HairVisibilityViews)
        {
            // 為所有視圖渲染陰影.
            for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
            {
                const FViewInfo& View = Views[ViewIndex];

                (......)

                RHICmdList.SetViewport(View.ViewRect.Min.X, View.ViewRect.Min.Y, 0.0f, View.ViewRect.Max.X, View.ViewRect.Max.Y, 1.0f);
                LightSceneInfo->Proxy->SetScissorRect(RHICmdList, View, View.ViewRect);

                // 更新場景的Uniform Buffer.
                Scene->UniformBuffers.UpdateViewUniformBuffer(View);

                // 投影所有普通陰影深度緩沖到場景.
                for (int32 ShadowIndex = 0; ShadowIndex < NormalShadows.Num(); ShadowIndex++)
                {
                    FProjectedShadowInfo* ProjectedShadowInfo = NormalShadows[ShadowIndex];

                    if (ProjectedShadowInfo->bAllocated && ProjectedShadowInfo->FadeAlphas[ViewIndex] > 1.0f / 256.0f)
                    {
                        // 渲染點光源陰影.
                        if (ProjectedShadowInfo->bOnePassPointLightShadow)
                        {
                            ProjectedShadowInfo->RenderOnePassPointLightProjection(RHICmdList, ViewIndex, View, bProjectingForForwardShading, HairVisibilityData);
                        }
                        else // 普遍光源陰影.
                        {
                            ProjectedShadowInfo->RenderProjection(RHICmdList, ViewIndex, &View, this, bProjectingForForwardShading, bMobileModulatedProjections, HairVisibilityData);
                        }
                    }
                }
            }
        };

        (......)
        else // 渲染普通陰影.
        {
            // 構造渲染Pass資訊, 渲染紋理是ScreenShadowMaskTexture.
            FRHIRenderPassInfo RPInfo(ScreenShadowMaskTexture->GetRenderTargetItem().TargetableTexture, ERenderTargetActions::Load_Store);
            RPInfo.DepthStencilRenderTarget.Action = MakeDepthStencilTargetActions(ERenderTargetActions::Load_DontStore, ERenderTargetActions::Load_Store);
            RPInfo.DepthStencilRenderTarget.DepthStencilTarget = SceneContext.GetSceneDepthSurface();
            RPInfo.DepthStencilRenderTarget.ExclusiveDepthStencil = FExclusiveDepthStencil::DepthRead_StencilWrite;

            TransitionRenderPassTargets(RHICmdList, RPInfo);
            RHICmdList.BeginRenderPass(RPInfo, TEXT("RenderShadowProjection"));
            
            // 渲染陰影遮蔽.
            RenderShadowMask(nullptr);
            
            RHICmdList.SetScissorRect(false, 0, 0, 0, 0);
            RHICmdList.EndRenderPass();
        }

        // 子像素陰影.
        if (!bMobileModulatedProjections && ScreenShadowMaskSubPixelTexture && InHairVisibilityViews)
        {            
            (......)
        }
    }

    // 距離場陰影.
    if (DistanceFieldShadows.Num() > 0)
    {
        (......)
    }

    return true;
}

由此可見,利用RenderShadowProjections可以將光源關聯的所有陰影投射到ScreenShadowMaskTexture中,此外,還可能包含距離場陰影、子像素陰影、透明體積陰影、膠囊體陰影、高度場陰影、頭發陰影等型別,

5.6.5.3 FProjectedShadowInfo::RenderProjection

為了查探ScreenShadowMaskTexture的渲染細節,進入FProjectedShadowInfo::RenderProjection(注意,陰影渲染階段剖析的是FProjectedShadowInfo::RenderDepthFProjectedShadowInfo::RenderTranslucencyDepth):

void FProjectedShadowInfo::RenderProjection(FRHICommandListImmediate& RHICmdList, int32 ViewIndex, const FViewInfo* View, const FSceneRenderer* SceneRender, bool bProjectingForForwardShading, bool bMobileModulatedProjections, ...) const
{
    (......)

    FGraphicsPipelineStateInitializer GraphicsPSOInit;
    RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit);

    // 檢測陰影的視圖示記是否開啟, 沒開啟直接回傳.
    const FVisibleLightViewInfo& VisibleLightViewInfo = View->VisibleLightInfos[LightSceneInfo->Id];
    {
        FPrimitiveViewRelevance ViewRelevance = VisibleLightViewInfo.ProjectedShadowViewRelevanceMap[ShadowId];
        if (ViewRelevance.bShadowRelevance == false)
        {
            return;
        }
    }

    bool bCameraInsideShadowFrustum;
    TArray<FVector4, TInlineAllocator<8>> FrustumVertices;
    SetupFrustumForProjection(View, FrustumVertices, bCameraInsideShadowFrustum);

    const bool bSubPixelSupport = HairVisibilityData != nullptr;
    const bool bStencilTestEnabled = !bSubPixelSupport;
    const bool bDepthBoundsTestEnabled = IsWholeSceneDirectionalShadow() && GSupportsDepthBoundsTest && CVarCSMDepthBoundsTest.GetValueOnRenderThread() != 0 && !bSubPixelSupport;
    
    if (!bDepthBoundsTestEnabled && bStencilTestEnabled)
    {
        SetupProjectionStencilMask(RHICmdList, View, ViewIndex, SceneRender, FrustumVertices, bMobileModulatedProjections, bCameraInsideShadowFrustum);
    }

    // 光柵化狀態.
    GraphicsPSOInit.RasterizerState = (View->bReverseCulling || IsWholeSceneDirectionalShadow()) ? TStaticRasterizerState<FM_Solid,CM_CCW>::GetRHI() : TStaticRasterizerState<FM_Solid,CM_CW>::GetRHI();

    // 深度模板狀態.
    GraphicsPSOInit.bDepthBounds = bDepthBoundsTestEnabled;
    if (bDepthBoundsTestEnabled)
    {
        GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState<false, CF_Always>::GetRHI();
    }
    else if (bStencilTestEnabled)
    {
        if (GStencilOptimization)
        {
            // No depth test or writes, zero the stencil
            // Note: this will disable hi-stencil on many GPUs, but still seems 
            // to be faster. However, early stencil still works 
            GraphicsPSOInit.DepthStencilState =
                TStaticDepthStencilState<
                false, CF_Always,
                true, CF_NotEqual, SO_Zero, SO_Zero, SO_Zero,
                false, CF_Always, SO_Zero, SO_Zero, SO_Zero,
                0xff, 0xff
                >::GetRHI();
        }
        else
        {
            // no depth test or writes, Test stencil for non-zero.
            GraphicsPSOInit.DepthStencilState = 
                TStaticDepthStencilState<
                false, CF_Always,
                true, CF_NotEqual, SO_Keep, SO_Keep, SO_Keep,
                false, CF_Always, SO_Keep, SO_Keep, SO_Keep,
                0xff, 0xff
                >::GetRHI();
        }
    }
    else
    {
        GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState<false, CF_Always>::GetRHI();
    }

    // 獲取混合狀態, 具體邏輯見后面的剖析.
    GraphicsPSOInit.BlendState = GetBlendStateForProjection(bProjectingForForwardShading, bMobileModulatedProjections);
    GraphicsPSOInit.PrimitiveType = IsWholeSceneDirectionalShadow() ? PT_TriangleStrip : PT_TriangleList;

    {
        uint32 LocalQuality = GetShadowQuality();

        // 處理陰影質量和陰影解析度.
        if (LocalQuality > 1)
        {
            if (IsWholeSceneDirectionalShadow() && CascadeSettings.ShadowSplitIndex > 0)
            {
                // adjust kernel size so that the penumbra size of distant splits will better match up with the closer ones
                const float SizeScale = CascadeSettings.ShadowSplitIndex / FMath::Max(0.001f, CVarCSMSplitPenumbraScale.GetValueOnRenderThread());
            }
            else if (LocalQuality > 2 && !bWholeSceneShadow)
            {
                static auto CVarPreShadowResolutionFactor = IConsoleManager::Get().FindTConsoleVariableDataFloat(TEXT("r.Shadow.PreShadowResolutionFactor"));
                const int32 TargetResolution = bPreShadow ? FMath::TruncToInt(512 * CVarPreShadowResolutionFactor->GetValueOnRenderThread()) : 512;

                int32 Reduce = 0;

                {
                    int32 Res = ResolutionX;

                    while (Res < TargetResolution)
                    {
                        Res *= 2;
                        ++Reduce;
                    }
                }

                // Never drop to quality 1 due to low resolution, aliasing is too bad
                LocalQuality = FMath::Clamp((int32)LocalQuality - Reduce, 3, 5);
            }
        }

        // 系結頂點布局
        GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GetVertexDeclarationFVector4();
        // 系結陰影投影shader.
        BindShadowProjectionShaders(LocalQuality, RHICmdList, GraphicsPSOInit, ViewIndex, *View, HairVisibilityData, this, bMobileModulatedProjections);

        if (bDepthBoundsTestEnabled)
        {
            SetDepthBoundsTest(RHICmdList, CascadeSettings.SplitNear, CascadeSettings.SplitFar, View->ViewMatrices.GetProjectionMatrix());
        }

        RHICmdList.SetStencilRef(0);
    }

    // 執行螢屏空間的繪制.
    if (IsWholeSceneDirectionalShadow()) // 全景*行光陰影.
    {
        // 設定頂點buffer.
        RHICmdList.SetStreamSource(0, GClearVertexBuffer.VertexBufferRHI, 0);
        // 呼叫繪制.
        RHICmdList.DrawPrimitive(0, 2, 1);
    }
    else
    {
        // 動態創建頂點緩沖.
        FRHIResourceCreateInfo CreateInfo;
        FVertexBufferRHIRef VertexBufferRHI = RHICreateVertexBuffer(sizeof(FVector4) * FrustumVertices.Num(), BUF_Volatile, CreateInfo);
        // 上傳資料到頂點緩沖.
        void* VoidPtr = RHILockVertexBuffer(VertexBufferRHI, 0, sizeof(FVector4) * FrustumVertices.Num(), RLM_WriteOnly);
        FPlatformMemory::Memcpy(VoidPtr, FrustumVertices.GetData(), sizeof(FVector4) * FrustumVertices.Num());
        RHIUnlockVertexBuffer(VertexBufferRHI);

        // 設定頂點緩沖并繪制.
        RHICmdList.SetStreamSource(0, VertexBufferRHI, 0);
        // Draw the frustum using the projection shader..
        RHICmdList.DrawIndexedPrimitive(GCubeIndexBuffer.IndexBufferRHI, 0, 0, 8, 0, 12, 1);
        // 釋放頂點緩沖.
        VertexBufferRHI.SafeRelease();
    }

    // 清理模板緩沖成0.
    if (!bDepthBoundsTestEnabled && bStencilTestEnabled)
    {
        if (!GStencilOptimization)
        {
            DrawClearQuad(RHICmdList, false, FLinearColor::Transparent, false, 0, true, 0);
        }
    }
}

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/285711.html

標籤:其他

上一篇:Action Sheets 和 Activity Views

下一篇:【工程應用四】 基于形狀的多目標多角度的高速模板匹配演算法進一步研究。

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 網閘典型架構簡述

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的2+1架構網閘。 三主機架構分別為內端機、外端機和仲裁機。三機無論從軟體和硬體上均各自獨立。首先從硬體上來看,三機都用各自獨立的主板、記憶體及存盤設備。從軟體上來看,三機有各自獨立的作業系統。這樣能達到完全的三機獨立。對于“2+1”系統,“2”分為 ......

    uj5u.com 2020-09-10 02:00:44 more
  • 如何從xshell上傳檔案到centos linux虛擬機里

    如何從xshell上傳檔案到centos linux虛擬機里及:虛擬機CentOs下執行 yum -y install lrzsz命令,出現錯誤:鏡像無法找到軟體包 前言 一、安裝lrzsz步驟 二、上傳檔案 三、遇到的問題及解決方案 總結 前言 提示:其實很簡單,往虛擬機上安裝一個上傳檔案的工具 ......

    uj5u.com 2020-09-10 02:00:47 more
  • 一、SQLMAP入門

    一、SQLMAP入門 1、判斷是否存在注入 sqlmap.py -u 網址/id=1 id=1不可缺少。當注入點后面的引數大于兩個時。需要加雙引號, sqlmap.py -u "網址/id=1&uid=1" 2、判斷文本中的請求是否存在注入 從文本中加載http請求,SQLMAP可以從一個文本檔案中 ......

    uj5u.com 2020-09-10 02:00:50 more
  • Metasploit 簡單使用教程

    metasploit 簡單使用教程 浩先生, 2020-08-28 16:18:25 分類專欄: kail 網路安全 linux 文章標簽: linux資訊安全 編輯 著作權 metasploit 使用教程 前言 一、Metasploit是什么? 二、準備作業 三、具體步驟 前言 Msfconsole ......

    uj5u.com 2020-09-10 02:00:53 more
  • 游戲逆向之驅動層與用戶層通訊

    驅動層代碼: #pragma once #include <ntifs.h> #define add_code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) /* 更多游戲逆向視頻www.yxfzedu.com ......

    uj5u.com 2020-09-10 02:00:56 more
  • 北斗電力時鐘(北斗授時服務器)讓網路資料更精準

    北斗電力時鐘(北斗授時服務器)讓網路資料更精準 北斗電力時鐘(北斗授時服務器)讓網路資料更精準 京準電子科技官微——ahjzsz 近幾年,資訊技術的得了快速發展,互聯網在逐漸普及,其在人們生活和生產中都得到了廣泛應用,并且取得了不錯的應用效果。計算機網路資訊在電力系統中的應用,一方面使電力系統的運行 ......

    uj5u.com 2020-09-10 02:01:03 more
  • 【CTF】CTFHub 技能樹 彩蛋 writeup

    ?碎碎念 CTFHub:https://www.ctfhub.com/ 筆者入門CTF時時剛開始刷的是bugku的舊平臺,后來才有了CTFHub。 感覺不論是網頁UI設計,還是題目質量,賽事跟蹤,工具軟體都做得很不錯。 而且因為獨到的金幣制度的確讓人有一種想去刷題賺金幣的感覺。 個人還是非常喜歡這個 ......

    uj5u.com 2020-09-10 02:04:05 more
  • 02windows基礎操作

    我學到了一下幾點 Windows系統目錄結構與滲透的作用 常見Windows的服務詳解 Windows埠詳解 常用的Windows注冊表詳解 hacker DOS命令詳解(net user / type /md /rd/ dir /cd /net use copy、批處理 等) 利用dos命令制作 ......

    uj5u.com 2020-09-10 02:04:18 more
  • 03.Linux基礎操作

    我學到了以下幾點 01Linux系統介紹02系統安裝,密碼啊破解03Linux常用命令04LAMP 01LINUX windows: win03 8 12 16 19 配置不繁瑣 Linux:redhat,centos(紅帽社區版),Ubuntu server,suse unix:金融機構,證券,銀 ......

    uj5u.com 2020-09-10 02:04:30 more
  • 05HTML

    01HTML介紹 02頭部標簽講解03基礎標簽講解04表單標簽講解 HTML前段語言 js1.了解代碼2.根據代碼 懂得挖掘漏洞 (POST注入/XSS漏洞上傳)3.黑帽seo 白帽seo 客戶網站被黑帽植入劫持代碼如何處理4.熟悉html表單 <html><head><title>TDK標題,描述 ......

    uj5u.com 2020-09-10 02:04:36 more
最新发布
  • 2023年最新微信小程式抓包教程

    01 開門見山 隔一個月發一篇文章,不過分。 首先回顧一下《微信系結手機號資料庫被脫庫事件》,我也是第一時間得知了這個訊息,然后跟蹤了整件事情的經過。下面是這起事件的相關截圖以及近日流出的一萬條資料樣本: 個人認為這件事也沒什么,還不如關注一下之前45億快遞資料查詢渠道疑似在近日復活的訊息。 訊息是 ......

    uj5u.com 2023-04-20 08:48:24 more
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:47:46 more
  • vulnhub_Earth

    前言 靶機地址->>>vulnhub_Earth 攻擊機ip:192.168.20.121 靶機ip:192.168.20.122 參考文章 https://www.cnblogs.com/Jing-X/archive/2022/04/03/16097695.html https://www.cnb ......

    uj5u.com 2023-04-20 07:46:20 more
  • 從4k到42k,軟體測驗工程師的漲薪史,給我看哭了

    清明節一過,盲猜大家已經無心上班,在數著日子準備過五一,但一想到銀行卡里的余額……瞬間心情就不美麗了。最近,2023年高校畢業生就業調查顯示,本科畢業月平均起薪為5825元。調查一出,便有很多同學表示自己又被平均了。看著這一資料,不免讓人想到前不久中國青年報的一項調查:近六成大學生認為畢業10年內會 ......

    uj5u.com 2023-04-20 07:44:00 more
  • 最新版本 Stable Diffusion 開源 AI 繪畫工具之中文自動提詞篇

    🎈 標簽生成器 由于輸入正向提示詞 prompt 和反向提示詞 negative prompt 都是使用英文,所以對學習母語的我們非常不友好 使用網址:https://tinygeeker.github.io/p/ai-prompt-generator 這個網址是為了讓大家在使用 AI 繪畫的時候 ......

    uj5u.com 2023-04-20 07:43:36 more
  • 漫談前端自動化測驗演進之路及測驗工具分析

    隨著前端技術的不斷發展和應用程式的日益復雜,前端自動化測驗也在不斷演進。隨著 Web 應用程式變得越來越復雜,自動化測驗的需求也越來越高。如今,自動化測驗已經成為 Web 應用程式開發程序中不可或缺的一部分,它們可以幫助開發人員更快地發現和修復錯誤,提高應用程式的性能和可靠性。 ......

    uj5u.com 2023-04-20 07:43:16 more
  • CANN開發實踐:4個DVPP記憶體問題的典型案例解讀

    摘要:由于DVPP媒體資料處理功能對存放輸入、輸出資料的記憶體有更高的要求(例如,記憶體首地址128位元組對齊),因此需呼叫專用的記憶體申請介面,那么本期就分享幾個關于DVPP記憶體問題的典型案例,并給出原因分析及解決方法。 本文分享自華為云社區《FAQ_DVPP記憶體問題案例》,作者:昇騰CANN。 DVPP ......

    uj5u.com 2023-04-20 07:43:03 more
  • msf學習

    msf學習 以kali自帶的msf為例 一、msf核心模塊與功能 msf模塊都放在/usr/share/metasploit-framework/modules目錄下 1、auxiliary 輔助模塊,輔助滲透(埠掃描、登錄密碼爆破、漏洞驗證等) 2、encoders 編碼器模塊,主要包含各種編碼 ......

    uj5u.com 2023-04-20 07:42:59 more
  • Halcon軟體安裝與界面簡介

    1. 下載Halcon17版本到到本地 2. 雙擊安裝包后 3. 步驟如下 1.2 Halcon軟體安裝 界面分為四大塊 1. Halcon的五個助手 1) 影像采集助手:與相機連接,設定相機引數,采集影像 2) 標定助手:九點標定或是其它的標定,生成標定檔案及內參外參,可以將像素單位轉換為長度單位 ......

    uj5u.com 2023-04-20 07:42:17 more
  • 在MacOS下使用Unity3D開發游戲

    第一次發博客,先發一下我的游戲開發環境吧。 去年2月份買了一臺MacBookPro2021 M1pro(以下簡稱mbp),這一年來一直在用mbp開發游戲。我大致分享一下我的開發工具以及使用體驗。 1、Unity 官網鏈接: https://unity.cn/releases 我一般使用的Apple ......

    uj5u.com 2023-04-20 07:40:19 more