剖析虛幻渲染體系(05)- 光源和陰影
- 5.1 本篇概述
- 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.4 UE的光源
- 5.5 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.5 陰影應用
- 5.6.6 UE陰影總結
- 5.7 本篇總結
- 特別說明
- 參考文獻
5.1 本篇概述
5.1.1 本篇內容
本篇主要闡述UE的以下內容:
- Shader程式碼及主要模組。
- UE的光源。
- UE的陰影。
- BasePass的機制和實現,包含C++和Shader。
- LightingPass的機制和實現,包含C++和Shader。
其中BasePass和LightingPass在上一篇中已經粗略地淺析過,本篇繼續深入地剖析。
本篇文章將首次引入mermaid語法,它是類似於\(\LaTeX\)的以文本描述作為畫圖工具的語法,簡單、輕便、小巧,高清無損畫質,和Markdown搭配,渾然天成。
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 )
中的向量X
和Y
代表著世界空間的切線和切線和法線的垂直向量,使用案例:
half3 X = GBuffer.WorldTangent;
half3 Y = normalize(cross(N, X));
Init(Context, N, X, Y, V, L);
回顧一下Cook-Torrance的BRDF公式:
\]
上面程式碼中,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, ...);
(......)
}
下面是RenderBasePass
和RenderBasePassViewParallel
並行渲染的邏輯:
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分別是TBasePassVS
和TBasePassPS
:
// 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被執行的次數是:
\]
這也側面說明了對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 = 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 = 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 = 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;
若將其分解成圖例,則如下圖:
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兩人聯合提出,該模型考慮在同一景物中不同材料和不同光源的相對亮度。它描述反射光線在方向上的分布和當反射隨入射角而改變時顏色的變化,並能求得從具體的實際材料製成的物體反射出來的光線的光譜能量分布,並根據這種光譜能量分布精確地再現顏色。它的公式如下:
\]
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法線分布函數的公式:
\]
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_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運算:
\]
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聯合遮蔽函數:
它們的聯合公式如下:
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將光照積分用黎曼和方法來*似模擬,光照函數結合了蒙特卡洛和重要性取樣,公式如下:
\]
對應的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採用了如下的物理*似:
\]
對應的實現程式碼:
// 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項進行了修改,基於立體角的更寬的高光分布。其中粗糙度的改變公式如下:
\]
這也會導致新的問題:光滑的物體看起來不再光滑(下圖)。
同時選取了區域光內的一個代表點(Representative Point)來模擬整片區域,可以直接用來光照著色。對於具有很大範圍分布的點是個很好的選擇,可以使用足夠小的角度*似反射光線。
對於球形光(Sphere Light),輻照度被當成點光源(前提是球體在反射光線的水*面上),然後用反射光線和球體求得最*點:
其中計算球體和反射光線的最小距離的公式如下:
\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\)在著色點的反射向量。
接著可以按照以下公式算出著色點的能量積分:
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}\),為了獲得代表點的*似歸一化,由下面的公式承擔:
\]
由此獲得非常不錯的球形光照效果:
下面將闡述管狀光源(Tube Light, Capsule Light)的演算法。對於從著色點指向管狀光源兩端的向量\(L_0\)和\(L_1\),它們之間線段的輻照度積分的解析解為:
\]
UE為此做了大量的簡化和*似,其中最主要的是類似於球體光源,*似最小的角度並用最短距離代替:
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\)),由此得到線性的歸一化公式:
\]
由於只需改變光源原點和應用能量守恆項,這些操作可以被累積。因此管狀光源的*似卷積效果非常好:
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 = View.ForwardLightingResources->ForwardLightData;
ForwardLightData = FForwardLightData();
TArray<FForwardLocalLightData, SceneRenderingAllocator> 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<FSortedLightSceneInfo, SceneRenderingAllocator>& 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<FForwardLightData>::CreateUniformBufferImmediate(ForwardLightData, UniformBuffer_SingleFrame);
// 下面使用RDG和Compute Shader去裁剪局部光源和反射探針.
// 執行緒組數.
const FIntVector NumGroups = FIntVector::DivideAndRoundUp(FIntVector(LightGridSizeXY.X, LightGridSizeXY.Y, GLightGridSizeZ), LightGridInjectionGroupSize);
TArray<FRHIUnorderedAccessView*, TInlineAllocator<2>> 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 = 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, ...);
(......)
}
下面再次簡單回顧RenderLights
和RenderLight
的渲染邏輯:
// 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都是疊加狀態,公式如下:
\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被執行的次數是:
\]
這也側面說明了對光源和陰影進行排序的必要性,可以減少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 = 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 = 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總結
下面總結光照計算像素著色器的主要流程和步驟:
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 = 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);
}
}
}
}
}
}
}
(......)
}
以上調用DrawFrustumWireframe
和RenderFrustumWireframe
後並不是立即繪製,而是通過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 = 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 = 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列表中。
- 根據陰影圖數量創建同等個數的FProjectedShadowInfo陰影實例,對每個陰影實例:
-
針對兩類光源(移動和固定的光源、尚未構建的靜態光源)創建CSM(級聯陰影)。調用AddViewDependentWholeSceneShadowsForView創建視圖關聯的CSM:
- 遍歷所有view,針對每個view:
- 如果不是主view,直接跳過後續步驟。
- 獲取視圖相關的全景投影數量,創建同等數量的FProjectedShadowInfo陰影實例,給每個陰影實例設置全景投影參數,最後添加到陰影實例列表和待裁剪列表中。
- 遍歷所有view,針對每個view:
-
處理交互陰影(指光源和圖元之間的影響,包含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::RenderDepth
和FProjectedShadowInfo::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);
}
}
}
以上程式碼調用了幾個關鍵的介面:GetBlendStateForProjection
和BindShadowProjectionShaders
,下面對它們進行分析:
// Engine\Source\Runtime\Renderer\Private\ShadowRendering.cpp
FRHIBlendState* FProjectedShadowInfo::GetBlendStateForProjection(bool bProjectingForForwardShading, bool bMobileModulatedProjections) const
{
return GetBlendStateForProjection(
GetLightSceneInfo().GetDynamicShadowMapChannel(),
IsWholeSceneDirectionalShadow(),
CascadeSettings.FadePlaneLength > 0 && !bRayTracedDistanceField,
bProjectingForForwardShading,
bMobileModulatedProjections);
}
FRHIBlendState* FProjectedShadowInfo::GetBlendStateForProjection(
int32 ShadowMapChannel,
bool bIsWholeSceneDirectionalShadow,
bool bUseFadePlane,
bool bProjectingForForwardShading,
bool bMobileModulatedProjections)
{
// 延遲渲染模式每個光源有4個通道(RGBA).
// CSM和逐物體陰影被存在獨立的通道, 以允許CSM過渡到預計算陰影而逐物體陰影可以超過漸隱距離.
// 次表面陰影需要一個額外的通道.
FRHIBlendState* BlendState = nullptr;
// 前向渲染模式
if (bProjectingForForwardShading)
{
(......)
}
else // 延遲渲染模式
{
// 光照衰減(ScreenShadowMaskTexture)的通道分配:
// R: WholeSceneShadows, non SSS
// G: WholeSceneShadows, SSS
// B: non WholeSceneShadows, non SSS
// A: non WholeSceneShadows, SSS
//
// 以上名詞解析:
// SSS: 次表面散射材質.
// non SSS: 不透明物體陰影.
// WholeSceneShadows: *行光CSM.
// non WholeSceneShadows: 聚光燈、逐物體陰影、透明照明、全方位*行光(omni-directional lights).
if (bIsWholeSceneDirectionalShadow) // 全景*行光陰影.
{
// 混合邏輯需要匹配FCompareFProjectedShadowInfoBySplitIndex的順序. 比如漸隱*面混合模式需要陰影先被渲染.
// 全景陰影只啟用了R和G通道.
if (bUseFadePlane) // 使用漸隱*面.
{
// alpha通道用來在層級之間過渡. 不需要BO_Min, 因為B和A通道被用作透明陰影.
BlendState = TStaticBlendState<CW_RG, BO_Add, BF_SourceAlpha, BF_InverseSourceAlpha>::GetRHI();
}
else // 不使用漸隱*面.
{
// CSM首個層級不需要過渡. BO_Min是為了組合多個陰影通道.
BlendState = TStaticBlendState<CW_RG, BO_Min, BF_One, BF_One>::GetRHI();
}
}
else // 非全景*行光陰影.
{
if (bMobileModulatedProjections) // 顏色調製陰影.
{
// 顏色調製陰影, 忽略Alpha.
BlendState = TStaticBlendState<CW_RGB, BO_Add, BF_Zero, BF_SourceColor, BO_Add, BF_Zero, BF_One>::GetRHI();
}
else // 非顏色調製陰影.
{
// BO_Min是為了組合多個陰影通道.
BlendState = TStaticBlendState<CW_BA, BO_Min, BF_One, BF_One, BO_Min, BF_One, BF_One>::GetRHI();
}
}
}
return BlendState;
}
// 綁定陰影投影shader.
static void BindShadowProjectionShaders(int32 Quality, FRHICommandList& RHICmdList, FGraphicsPipelineStateInitializer GraphicsPSOInit, int32 ViewIndex, const FViewInfo& View, const FHairStrandsVisibilityData* HairVisibilityData, const FProjectedShadowInfo* ShadowInfo, bool bMobileModulatedProjections)
{
if (HairVisibilityData)
{
(......)
return;
}
// 透明陰影.
if (ShadowInfo->bTranslucentShadow)
{
(......)
}
// 全景*行光陰影.
else if (ShadowInfo->IsWholeSceneDirectionalShadow())
{
// PCF軟陰影.
if (CVarFilterMethod.GetValueOnRenderThread() == 1)
{
if (ShadowInfo->CascadeSettings.FadePlaneLength > 0)
BindShaderShaders<FShadowProjectionNoTransformVS, TDirectionalPercentageCloserShadowProjectionPS<5, true> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, HairVisibilityData, ShadowInfo);
else
BindShaderShaders<FShadowProjectionNoTransformVS, TDirectionalPercentageCloserShadowProjectionPS<5, false> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, HairVisibilityData, ShadowInfo);
}
else if (ShadowInfo->CascadeSettings.FadePlaneLength > 0)
{
if (ShadowInfo->bTransmission)
{
(......)
}
else
{
switch (Quality)
{
case 1: BindShaderShaders<FShadowProjectionNoTransformVS, TShadowProjectionPS<1, true> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, HairVisibilityData, ShadowInfo); break;
case 2: BindShaderShaders<FShadowProjectionNoTransformVS, TShadowProjectionPS<2, true> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, HairVisibilityData, ShadowInfo); break;
case 3: BindShaderShaders<FShadowProjectionNoTransformVS, TShadowProjectionPS<3, true> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, HairVisibilityData, ShadowInfo); break;
case 4: BindShaderShaders<FShadowProjectionNoTransformVS, TShadowProjectionPS<4, true> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, HairVisibilityData, ShadowInfo); break;
case 5: BindShaderShaders<FShadowProjectionNoTransformVS, TShadowProjectionPS<5, true> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, HairVisibilityData, ShadowInfo); break;
default:
check(0);
}
}
}
else
{
if (ShadowInfo->bTransmission)
{
(......)
}
else
{
switch (Quality)
{
case 1: BindShaderShaders<FShadowProjectionNoTransformVS, TShadowProjectionPS<1, false> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, HairVisibilityData, ShadowInfo); break;
case 2: BindShaderShaders<FShadowProjectionNoTransformVS, TShadowProjectionPS<2, false> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, HairVisibilityData, ShadowInfo); break;
case 3: BindShaderShaders<FShadowProjectionNoTransformVS, TShadowProjectionPS<3, false> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, HairVisibilityData, ShadowInfo); break;
case 4: BindShaderShaders<FShadowProjectionNoTransformVS, TShadowProjectionPS<4, false> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, HairVisibilityData, ShadowInfo); break;
case 5: BindShaderShaders<FShadowProjectionNoTransformVS, TShadowProjectionPS<5, false> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, HairVisibilityData, ShadowInfo); break;
default:
check(0);
}
}
}
}
// 局部光源/逐物體等陰影.
else
{
if(bMobileModulatedProjections)
{
(......)
}
else if (ShadowInfo->bTransmission)
{
(......)
}
else
{
if (CVarFilterMethod.GetValueOnRenderThread() == 1 && ShadowInfo->GetLightSceneInfo().Proxy->GetLightType() == LightType_Spot)
{
BindShaderShaders<FShadowVolumeBoundProjectionVS, TSpotPercentageCloserShadowProjectionPS<5, false> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, HairVisibilityData, ShadowInfo);
}
else
{
switch (Quality)
{
case 1: BindShaderShaders<FShadowVolumeBoundProjectionVS, TShadowProjectionPS<1, false> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, HairVisibilityData, ShadowInfo); break;
case 2: BindShaderShaders<FShadowVolumeBoundProjectionVS, TShadowProjectionPS<2, false> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, HairVisibilityData, ShadowInfo); break;
case 3: BindShaderShaders<FShadowVolumeBoundProjectionVS, TShadowProjectionPS<3, false> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, HairVisibilityData, ShadowInfo); break;
case 4: BindShaderShaders<FShadowVolumeBoundProjectionVS, TShadowProjectionPS<4, false> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, HairVisibilityData, ShadowInfo); break;
case 5: BindShaderShaders<FShadowVolumeBoundProjectionVS, TShadowProjectionPS<5, false> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, HairVisibilityData, ShadowInfo); break;
default:
check(0);
}
}
}
}
}
// 綁定陰影投射VS和PS.
template<typename VertexShaderType, typename PixelShaderType>
static void BindShaderShaders(FRHICommandList& RHICmdList, FGraphicsPipelineStateInitializer& GraphicsPSOInit, int32 ViewIndex, const FViewInfo& View, const FHairStrandsVisibilityData* HairVisibilityData, const FProjectedShadowInfo* ShadowInfo)
{
TShaderRef<VertexShaderType> VertexShader = View.ShaderMap->GetShader<VertexShaderType>();
TShaderRef<PixelShaderType> PixelShader = View.ShaderMap->GetShader<PixelShaderType>();
// 綁定shader和渲染狀態.
GraphicsPSOInit.BoundShaderState.VertexShaderRHI = VertexShader.GetVertexShader();
GraphicsPSOInit.BoundShaderState.PixelShaderRHI = PixelShader.GetPixelShader();
SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit);
// 設置shader參數.
VertexShader->SetParameters(RHICmdList, View, ShadowInfo);
PixelShader->SetParameters(RHICmdList, ViewIndex, View, HairVisibilityData, ShadowInfo);
}
5.6.5.4 FShadowProjectionNoTransformVS和TShadowProjectionPS
上一小節可以看出,不同類型的陰影會調用不同的VS和PS。其中以具有代表性的全景陰影為例,分析其使用的FShadowProjectionNoTransformVS和TShadowProjectionPS在C++和Shader邏輯:
// Engine\Source\Runtime\Renderer\Private\ShadowRendering.h
// 陰影投射VS.
class FShadowProjectionNoTransformVS : public FShadowProjectionVertexShaderInterface
{
DECLARE_SHADER_TYPE(FShadowProjectionNoTransformVS,Global);
public:
(......)
static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
{
FShadowProjectionVertexShaderInterface::ModifyCompilationEnvironment(Parameters, OutEnvironment);
// 不使用USE_TRANSFORM.
OutEnvironment.SetDefine(TEXT("USE_TRANSFORM"), (uint32)0);
}
static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
{
return true;
}
// 設置view的Uniform Buffer.
void SetParameters(FRHICommandList& RHICmdList, FRHIUniformBuffer* ViewUniformBuffer)
{
FGlobalShader::SetParameters<FViewUniformShaderParameters>(RHICmdList, RHICmdList.GetBoundVertexShader(), ViewUniformBuffer);
}
void SetParameters(FRHICommandList& RHICmdList, const FSceneView& View, const FProjectedShadowInfo*)
{
FGlobalShader::SetParameters<FViewUniformShaderParameters>(RHICmdList, RHICmdList.GetBoundVertexShader(), View.ViewUniformBuffer);
}
};
// 陰影投射PS.
template<uint32 Quality, bool bUseFadePlane = false, bool bModulatedShadows = false, bool bUseTransmission = false, bool SubPixelShadow = false>
class TShadowProjectionPS : public FShadowProjectionPixelShaderInterface
{
DECLARE_SHADER_TYPE(TShadowProjectionPS,Global);
public:
(.....)
TShadowProjectionPS(const ShaderMetaType::CompiledShaderInitializerType& Initializer):
FShadowProjectionPixelShaderInterface(Initializer)
{
ProjectionParameters.Bind(Initializer);
ShadowFadeFraction.Bind(Initializer.ParameterMap,TEXT("ShadowFadeFraction"));
ShadowSharpen.Bind(Initializer.ParameterMap,TEXT("ShadowSharpen"));
TransmissionProfilesTexture.Bind(Initializer.ParameterMap, TEXT("SSProfilesTexture"));
LightPosition.Bind(Initializer.ParameterMap, TEXT("LightPositionAndInvRadius"));
}
static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
{
return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::SM5);
}
// 修改宏定義.
static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
{
FShadowProjectionPixelShaderInterface::ModifyCompilationEnvironment(Parameters,OutEnvironment);
OutEnvironment.SetDefine(TEXT("SHADOW_QUALITY"), Quality);
OutEnvironment.SetDefine(TEXT("SUBPIXEL_SHADOW"), (uint32)(SubPixelShadow ? 1 : 0));
OutEnvironment.SetDefine(TEXT("USE_FADE_PLANE"), (uint32)(bUseFadePlane ? 1 : 0));
OutEnvironment.SetDefine(TEXT("USE_TRANSMISSION"), (uint32)(bUseTransmission ? 1 : 0));
}
void SetParameters(
FRHICommandList& RHICmdList,
int32 ViewIndex,
const FSceneView& View,
const FHairStrandsVisibilityData* HairVisibilityData,
const FProjectedShadowInfo* ShadowInfo)
{
FRHIPixelShader* ShaderRHI = RHICmdList.GetBoundPixelShader();
// 這裡會設置跟陰影渲染相關的很多shader綁定.(見後面程式碼)
FShadowProjectionPixelShaderInterface::SetParameters(RHICmdList, ViewIndex, View, HairVisibilityData, ShadowInfo);
const bool bUseFadePlaneEnable = ShadowInfo->CascadeSettings.FadePlaneLength > 0;
// 設置shader值.
ProjectionParameters.Set(RHICmdList, this, View, ShadowInfo, HairVisibilityData, bModulatedShadows, bUseFadePlaneEnable);
const FLightSceneProxy& LightProxy = *(ShadowInfo->GetLightSceneInfo().Proxy);
SetShaderValue(RHICmdList, ShaderRHI, ShadowFadeFraction, ShadowInfo->FadeAlphas[ViewIndex] );
SetShaderValue(RHICmdList, ShaderRHI, ShadowSharpen, LightProxy.GetShadowSharpen() * 7.0f + 1.0f );
SetShaderValue(RHICmdList, ShaderRHI, LightPosition, FVector4(LightProxy.GetPosition(), 1.0f / LightProxy.GetRadius()));
// 綁定延遲光源的Uniform Buffer.
auto DeferredLightParameter = GetUniformBufferParameter<FDeferredLightUniformStruct>();
if (DeferredLightParameter.IsBound())
{
SetDeferredLightParameters(RHICmdList, ShaderRHI, DeferredLightParameter, &ShadowInfo->GetLightSceneInfo(), View);
}
FScene* Scene = nullptr;
if (View.Family->Scene)
{
Scene = View.Family->Scene->GetRenderScene();
}
// 綁定紋理資源.
FSceneRenderTargets& SceneContext = FSceneRenderTargets::Get(RHICmdList);
{
const IPooledRenderTarget* PooledRT = GetSubsufaceProfileTexture_RT((FRHICommandListImmediate&)RHICmdList);
if (!PooledRT)
{
PooledRT = GSystemTextures.BlackDummy;
}
const FSceneRenderTargetItem& Item = PooledRT->GetRenderTargetItem();
SetTextureParameter(RHICmdList, ShaderRHI, TransmissionProfilesTexture, Item.ShaderResourceTexture);
}
}
protected:
LAYOUT_FIELD(FShadowProjectionShaderParameters, ProjectionParameters);
LAYOUT_FIELD(FShaderParameter, ShadowFadeFraction);
LAYOUT_FIELD(FShaderParameter, ShadowSharpen);
LAYOUT_FIELD(FShaderParameter, LightPosition);
LAYOUT_FIELD(FShaderResourceParameter, TransmissionProfilesTexture);
};
// 設置陰影投射相關的很多shader綁定參數.
void FShadowProjectionShaderParameters::Set(FRHICommandList& RHICmdList, FShader* Shader, const FSceneView& View, const FProjectedShadowInfo* ShadowInfo, const FHairStrandsVisibilityData* HairVisibilityData, bool bModulatedShadows, bool bUseFadePlane)
{
FRHIPixelShader* ShaderRHI = RHICmdList.GetBoundPixelShader();
// 設置場景紋理.
SceneTextureParameters.Set(RHICmdList, ShaderRHI, View.FeatureLevel, ESceneTextureSetupMode::All);
const FIntPoint ShadowBufferResolution = ShadowInfo->GetShadowBufferResolution();
// 陰影偏移和尺寸.
if (ShadowTileOffsetAndSizeParam.IsBound())
{
FVector2D InverseShadowBufferResolution(1.0f / ShadowBufferResolution.X, 1.0f / ShadowBufferResolution.Y);
FVector4 ShadowTileOffsetAndSize(
(ShadowInfo->BorderSize + ShadowInfo->X) * InverseShadowBufferResolution.X,
(ShadowInfo->BorderSize + ShadowInfo->Y) * InverseShadowBufferResolution.Y,
ShadowInfo->ResolutionX * InverseShadowBufferResolution.X,
ShadowInfo->ResolutionY * InverseShadowBufferResolution.Y);
SetShaderValue(RHICmdList, ShaderRHI, ShadowTileOffsetAndSizeParam, ShadowTileOffsetAndSize);
}
// 設置從螢幕坐標轉換到陰影深度紋理坐標的變換矩陣.
if (bModulatedShadows)
{
const FMatrix ScreenToShadow = ShadowInfo->GetScreenToShadowMatrix(View, 0, 0, ShadowBufferResolution.X, ShadowBufferResolution.Y);
SetShaderValue(RHICmdList, ShaderRHI, ScreenToShadowMatrix, ScreenToShadow);
}
else
{
const FMatrix ScreenToShadow = ShadowInfo->GetScreenToShadowMatrix(View);
SetShaderValue(RHICmdList, ShaderRHI, ScreenToShadowMatrix, ScreenToShadow);
}
// 陰影過渡縮放.
if (SoftTransitionScale.IsBound())
{
const float TransitionSize = ShadowInfo->ComputeTransitionSize();
SetShaderValue(RHICmdList, ShaderRHI, SoftTransitionScale, FVector(0, 0, 1.0f / TransitionSize));
}
// 陰影緩衝尺寸.
if (ShadowBufferSize.IsBound())
{
FVector2D ShadowBufferSizeValue(ShadowBufferResolution.X, ShadowBufferResolution.Y);
SetShaderValue(RHICmdList, ShaderRHI, ShadowBufferSize,
FVector4(ShadowBufferSizeValue.X, ShadowBufferSizeValue.Y, 1.0f / ShadowBufferSizeValue.X, 1.0f / ShadowBufferSizeValue.Y));
}
// ------ 處理陰影深度紋理 ------
FRHITexture* ShadowDepthTextureValue;
if (ShadowInfo->RenderTargets.DepthTarget)
{
// 如果陰影存在深度紋理, 則取之作為PS的ShadowDepthTextureValue.
ShadowDepthTextureValue = ShadowInfo->RenderTargets.DepthTarget->GetRenderTargetItem().ShaderResourceTexture.GetReference();
}
// 透明陰影沒有深度紋理.
else
{
ShadowDepthTextureValue = GSystemTextures.BlackDummy->GetRenderTargetItem().ShaderResourceTexture.GetReference();
}
// 陰影深度紋理的取樣器, 注意是點取樣, 且是Clamp模式.
FRHISamplerState* DepthSamplerState = TStaticSamplerState<SF_Point,AM_Clamp,AM_Clamp,AM_Clamp>::GetRHI();
// 設置陰影深度紋理和取樣器.
SetTextureParameter(RHICmdList, ShaderRHI, ShadowDepthTexture, ShadowDepthTextureSampler, DepthSamplerState, ShadowDepthTextureValue);
if (ShadowDepthTextureSampler.IsBound())
{
RHICmdList.SetShaderSampler(
ShaderRHI,
ShadowDepthTextureSampler.GetBaseIndex(),
DepthSamplerState
);
}
// 深度偏移和漸隱*面偏移.
SetShaderValue(RHICmdList, ShaderRHI, ProjectionDepthBias, FVector4(ShadowInfo->GetShaderDepthBias(), ShadowInfo->GetShaderSlopeDepthBias(), ShadowInfo->GetShaderReceiverDepthBias(), ShadowInfo->MaxSubjectZ - ShadowInfo->MinSubjectZ));
SetShaderValue(RHICmdList, ShaderRHI, FadePlaneOffset, ShadowInfo->CascadeSettings.FadePlaneOffset);
if(InvFadePlaneLength.IsBound() && bUseFadePlane)
{
SetShaderValue(RHICmdList, ShaderRHI, InvFadePlaneLength, 1.0f / ShadowInfo->CascadeSettings.FadePlaneLength);
}
// 光源方向或位置.
if (LightPositionOrDirection.IsBound())
{
const FVector LightDirection = ShadowInfo->GetLightSceneInfo().Proxy->GetDirection();
const FVector LightPosition = ShadowInfo->GetLightSceneInfo().Proxy->GetPosition();
const bool bIsDirectional = ShadowInfo->GetLightSceneInfo().Proxy->GetLightType() == LightType_Directional;
SetShaderValue(RHICmdList, ShaderRHI, LightPositionOrDirection, bIsDirectional ? FVector4(LightDirection,0) : FVector4(LightPosition,1));
}
(......)
// 逐物體陰影參數.
SetShaderValue(RHICmdList, ShaderRHI, PerObjectShadowFadeStart, ShadowInfo->PerObjectShadowFadeStart);
SetShaderValue(RHICmdList, ShaderRHI, InvPerObjectShadowFadeLength, ShadowInfo->InvPerObjectShadowFadeLength);
}
上面詳盡地剖析了FShadowProjectionNoTransformVS和TShadowProjectionPS在C++層的邏輯,下面轉到它們對應的Shader程式碼:
// Engine\Shaders\Private\ShadowProjectionVertexShader.usf
#include "Common.ush"
#ifndef USE_TRANSFORM
#define USE_TRANSFORM 1
#endif
#if USE_TRANSFORM
float4 StencilingGeometryPosAndScale;
#endif
// VS主入口.
void Main(in float4 InPosition : ATTRIBUTE0, out float4 OutPosition : SV_POSITION)
{
#if USE_TRANSFORM // 使用變換.
// 轉換物體位置到裁剪空間.
float3 WorldPosition = InPosition.xyz * StencilingGeometryPosAndScale.w + StencilingGeometryPosAndScale.xyz;
OutPosition = mul(float4(WorldPosition,1), View.TranslatedWorldToClip);
#else // 不使用變換.
// 物體位置已經在裁剪空間, 不需要再變換.
OutPosition = float4(InPosition.xyz, 1);
#endif
}
// Engine\Shaders\Private\ShadowProjectionPixelShader.usf
(......)
float ShadowFadeFraction;
float ShadowSharpen;
float4 LightPositionAndInvRadius;
float PerObjectShadowFadeStart;
float InvPerObjectShadowFadeLength;
float4 LightPositionOrDirection;
float ShadowReceiverBias;
#if USE_FADE_PLANE || SUBPIXEL_SHADOW
float FadePlaneOffset;
float InvFadePlaneLength;
uint bCascadeUseFadePlane;
#endif
#if USE_PCSS
// PCSS specific parameters.
// - x: tan(0.5 * Directional Light Angle) in shadow projection space;
// - y: Max filter size in shadow tile UV space.
float4 PCSSParameters;
#endif
float4 ModulatedShadowColor;
float4 ShadowTileOffsetAndSize;
(.....)
// PS主入口.
void Main(in float4 SVPos : SV_POSITION,
out float4 OutColor : SV_Target0)
{
#if USE_FADE_PLANE
const bool bUseFadePlane = true;
#endif
const FPQMPContext PQMPContext = PQMPInit(SVPos.xy);
float2 ScreenUV = float2(SVPos.xy * View.BufferSizeAndInvSize.zw);
float SceneW = CalcSceneDepth(ScreenUV);
(......)
// 計算螢幕空間/陰影空間/世界空間的坐標.
float4 ScreenPosition = float4(((ScreenUV.xy - View.ScreenPositionScaleBias.wz ) / View.ScreenPositionScaleBias.xy) * SceneW, SceneW, 1);
float4 ShadowPosition = mul(ScreenPosition, ScreenToShadowMatrix);
float3 WorldPosition = mul(ScreenPosition, View.ScreenToWorld).xyz;
// 計算陰影空間的坐標(其中陰影裁剪空間坐標需要除以w, 以轉換到陰影的螢幕空間)
float ShadowZ = ShadowPosition.z;
ShadowPosition.xyz /= ShadowPosition.w;
#if MODULATED_SHADOWS
ShadowPosition.xy *= ShadowTileOffsetAndSize.zw;
ShadowPosition.xy += ShadowTileOffsetAndSize.xy;
#endif
// PCSS軟陰影.
#if USE_PCSS
float3 ScreenPositionDDX = DDX(ScreenPosition.xyz);
float3 ScreenPositionDDY = DDY(ScreenPosition.xyz);
float4 ShadowPositionDDX = mul(float4(ScreenPositionDDX, 0), ScreenToShadowMatrix);
float4 ShadowPositionDDY = mul(float4(ScreenPositionDDY, 0), ScreenToShadowMatrix);
#if SPOT_LIGHT_PCSS
ShadowPositionDDX.xyz -= ShadowPosition.xyz * ShadowPositionDDX.w;
ShadowPositionDDY.xyz -= ShadowPosition.xyz * ShadowPositionDDY.w;
#endif
#endif
// 調整陰影空間(光源空間)的深度到0.99999f, 因為陰影深度緩衝無法被清除到1的值. 也可以強制光源空間遠*面的像素不被遮擋.
float LightSpacePixelDepthForOpaque = min(ShadowZ, 0.99999f);
// SSS的深度不能調整, 因為次表面的漸變必須超過遠*面.
float LightSpacePixelDepthForSSS = ShadowZ;
// 逐物體陰影在被剪掉之前執行過渡, 開始*面是1而結束*面是0.
float PerObjectDistanceFadeFraction = 1.0f - saturate((LightSpacePixelDepthForSSS - PerObjectShadowFadeStart) * InvPerObjectShadowFadeLength);
// 初始化陰影和投射等參數.
float Shadow = 1;
float SSSTransmission = 1;
float BlendFactor = 1;
// 未過濾的陰影投射.
#if UNFILTERED_SHADOW_PROJECTION
{
// 直接對比調整過的光源空間(陰影空間)的像素深度和對應像素坐標的陰影深度的r通道值.
// 如果前者<後者, 說明未被遮擋, Shadow=1; 反之, Shadow=0.
Shadow = LightSpacePixelDepthForOpaque < Texture2DSampleLevel(ShadowDepthTexture, ShadowDepthTextureSampler, ShadowPosition.xy, 0).r;
}
// 透明陰影.
#elif APPLY_TRANSLUCENCY_SHADOWS
{
Shadow = CalculateTranslucencyShadowing(ShadowPosition.xy, ShadowZ);
}
// PCSS軟陰影.
#elif USE_PCSS
{
FPCSSSamplerSettings Settings;
#if SPOT_LIGHT_PCSS
{
float CotanOuterCone = DeferredLightUniforms.SpotAngles.x * rsqrt(1. - DeferredLightUniforms.SpotAngles.x * DeferredLightUniforms.SpotAngles.x);
float WorldLightDistance = dot(DeferredLightUniforms.Direction, DeferredLightUniforms.Position - WorldPosition);
Settings.ProjectedSourceRadius = 0.5 * DeferredLightUniforms.SourceRadius * CotanOuterCone / WorldLightDistance;
Settings.TanLightSourceAngle = 0;
}
#else
{
Settings.ProjectedSourceRadius = 0;
Settings.TanLightSourceAngle = PCSSParameters.x;
}
#endif
Settings.ShadowDepthTexture = ShadowDepthTexture;
Settings.ShadowDepthTextureSampler = ShadowDepthTextureSampler;
Settings.ShadowBufferSize = ShadowBufferSize;
Settings.ShadowTileOffsetAndSize = ShadowTileOffsetAndSize;
Settings.SceneDepth = LightSpacePixelDepthForOpaque;
Settings.TransitionScale = SoftTransitionScale.z;
Settings.MaxKernelSize = PCSSParameters.y;
Settings.SvPosition = SVPos.xy;
Settings.PQMPContext = PQMPContext;
Settings.DebugViewportUV = ScreenUV;
Shadow = DirectionalPCSS(Settings, ShadowPosition.xy, ShadowPositionDDX.xyz, ShadowPositionDDY.xyz);
}
// 自定義的PCF陰影.
#else
{
#if SHADING_PATH_DEFERRED && !FORWARD_SHADING
FGBufferData GBufferData = GetGBufferData(ScreenUV);
const bool bIsDirectional = LightPositionOrDirection.w == 0;
const float3 LightDirection = bIsDirectional ? -LightPositionOrDirection.xyz : normalize(LightPositionOrDirection.xyz - WorldPosition);
const float NoL = saturate(dot(GBufferData.WorldNormal, LightDirection));
#endif
FPCFSamplerSettings Settings;
Settings.ShadowDepthTexture = ShadowDepthTexture;
Settings.ShadowDepthTextureSampler = ShadowDepthTextureSampler;
Settings.ShadowBufferSize = ShadowBufferSize;
#if SHADING_PATH_DEFERRED && !FORWARD_SHADING
Settings.TransitionScale = SoftTransitionScale.z * lerp(ProjectionDepthBiasParameters.z, 1.0, NoL);
#else
Settings.TransitionScale = SoftTransitionScale.z;
#endif
Settings.SceneDepth = LightSpacePixelDepthForOpaque;
Settings.bSubsurface = false;
Settings.bTreatMaxDepthUnshadowed = false;
Settings.DensityMulConstant = 0;
Settings.ProjectionDepthBiasParameters = 0;
// 自定義的PCF陰影.
Shadow = ManualPCF(ShadowPosition.xy, Settings);
}
#endif // !USE_PCSS
#if USE_FADE_PLANE || SUBPIXEL_SHADOW
if (bUseFadePlane)
{
// Create a blend factor which is one before and at the fade plane, and lerps to zero at the far plane.
BlendFactor = 1.0f - saturate((SceneW - FadePlaneOffset) * InvFadePlaneLength);
}
#endif
// 次表面陰影.
#if FEATURE_LEVEL >= FEATURE_LEVEL_SM4 && !FORWARD_SHADING && !APPLY_TRANSLUCENCY_SHADOWS
FGBufferData GBufferData = GetGBufferData(ScreenUV);
BRANCH
if (bIsSubsurfaceCompatible && IsSubsurfaceModel(GBufferData.ShadingModelID))
{
float Opacity = GBufferData.CustomData.a;
float Density = -.05f * log(1 - min(Opacity, .999f));
if( GBufferData.ShadingModelID == SHADINGMODELID_HAIR || GBufferData.ShadingModelID == SHADINGMODELID_EYE )
{
Opacity = 1;
Density = 1;
}
float SquareRootFilterScale = lerp(1.999f, 0, Opacity);
int SquareRootFilterScaleInt = int(SquareRootFilterScale) + 1;
#if UNFILTERED_SHADOW_PROJECTION
float ShadowMapDepth = Texture2DSampleLevel(ShadowDepthTexture, ShadowDepthTextureSampler, ShadowPosition.xy, 0).x;
SSSTransmission = CalculateSubsurfaceOcclusion(Density, LightSpacePixelDepthForSSS, ShadowMapDepth.xxx).x;
#else
// default code path
FPCFSamplerSettings Settings;
Settings.ShadowDepthTexture = ShadowDepthTexture;
Settings.ShadowDepthTextureSampler = ShadowDepthTextureSampler;
Settings.ShadowBufferSize = ShadowBufferSize;
Settings.TransitionScale = SoftTransitionScale.z;
Settings.SceneDepth = LightSpacePixelDepthForSSS + ProjectionDepthBiasParameters.x;
Settings.bSubsurface = true;
Settings.bTreatMaxDepthUnshadowed = false;
Settings.DensityMulConstant = Density * ProjectionDepthBiasParameters.w;
Settings.ProjectionDepthBiasParameters = ProjectionDepthBiasParameters.xw;
#if USE_TRANSMISSION
if (GBufferData.ShadingModelID == SHADINGMODELID_SUBSURFACE_PROFILE)
{
SSSTransmission = CalcTransmissionThickness(ScreenPosition.xyz, LightPositionAndInvRadius.xyz, GBufferData, Settings);
}
else
#endif
{
SSSTransmission = ManualPCF(ShadowPosition.xy, Settings);
}
#endif
}
#endif
// 如果不使用PCSS軟陰影, 則用ShadowSharpen來調整陰影的強度, 類似Half Lambert的做法.
#if !USE_PCSS
Shadow = saturate( (Shadow - 0.5) * ShadowSharpen + 0.5 );
#endif
// 過渡陰影. 0是被陰影遮蔽, 1是未被遮蔽. 無需返回顏色, 除非是調製陰影模式需要寫入場景顏色.
float FadedShadow = lerp(1.0f, Square(Shadow), ShadowFadeFraction * PerObjectDistanceFadeFraction);
#if FORWARD_SHADING
(......)
#else
// 點光源陰影被寫到b通道.
OutColor.b = EncodeLightAttenuation(FadedShadow);
OutColor.rga = 1;
// SSS陰影模擬, 雖然不夠準確, 但至少有被陰影遮蔽.
OutColor.a = OutColor.b;
#endif
#if USE_FADE_PLANE || SUBPIXEL_SHADOW
// 如果是CSM, 則輸出Alpha通道來混合.
if (bUseFadePlane)
{
OutColor.a = BlendFactor;
}
#endif
// 調製陰影.
#if MODULATED_SHADOWS
OutColor.rgb = lerp(ModulatedShadowColor.rgb, float3(1, 1, 1), FadedShadow);
OutColor.a = 0;
#endif
}
(......)
以上可知,對ScreenShadowMaskTexture執行繪製時,是在螢幕空間進行的,並且ScreenShadowMask會被疊加多次(視光源的陰影實例個數而定),不同的通道存儲了不同類型的陰影資訊。
5.6.5.5 RenderLight
本小節將分析有了光源和ScreenShadowMaskTexture資訊之後如何使用它們。直接進入RenderLight分析陰影相關的邏輯:
void FDeferredShadingSceneRenderer::RenderLight(FRHICommandList& RHICmdList, const FLightSceneInfo* LightSceneInfo, IPooledRenderTarget* ScreenShadowMaskTexture, ...)
{
(......)
// 遍歷所有view
for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
{
(......)
// *行光.
if (LightSceneInfo->Proxy->GetLightType() == LightType_Directional)
{
(......)
else
{
FDeferredLightPS::FPermutationDomain PermutationVector;
(......)
// 聲明光照像素著色器FDeferredLightPS實例.
TShaderMapRef< FDeferredLightPS > PixelShader( View.ShaderMap, PermutationVector );
GraphicsPSOInit.BoundShaderState.PixelShaderRHI = PixelShader.GetPixelShader();
(......)
SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit);
// 將ScreenShadowMaskTexture綁定到像素著色器中.
PixelShader->SetParameters(RHICmdList, View, LightSceneInfo, ScreenShadowMaskTexture, (bHairLighting) ? &RenderLightParams : nullptr);
}
(......)
// 繪製全螢幕幕矩形, 以便在螢幕空間計算*行光光照.
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 // 非*行光.
{
(......)
}
}
}
上面可知,是通過調用PixelShader->SetParameters
將ScreenShadowMaskTexture
綁定到shader中,下面分析其綁定過程:
// Engine\Source\Runtime\Renderer\Private\LightRendering.cpp
void FDeferredLightPS::SetParameters(FRHICommandList& RHICmdList, const FSceneView& View, const FLightSceneInfo* LightSceneInfo, IPooledRenderTarget* ScreenShadowMaskTexture, ...)
{
FRHIPixelShader* ShaderRHI = RHICmdList.GetBoundPixelShader();
SetParametersBase(RHICmdList, ShaderRHI, View, ScreenShadowMaskTexture, LightSceneInfo->Proxy->GetIESTextureResource(), RenderLightParams);
(......)
}
void FDeferredLightPS::SetParametersBase(FRHICommandList& RHICmdList, FRHIPixelShader* ShaderRHI, const FSceneView& View, IPooledRenderTarget* ScreenShadowMaskTexture, ...)
{
if(LightAttenuationTexture.IsBound())
{
// 綁定ScreenShadowMaskTexture到LightAttenuationTexture
SetTextureParameter(
RHICmdList,
ShaderRHI,
LightAttenuationTexture,
LightAttenuationTextureSampler,
TStaticSamplerState<SF_Point,AM_Wrap,AM_Wrap,AM_Wrap>::GetRHI(),
ScreenShadowMaskTexture ? ScreenShadowMaskTexture->GetRenderTargetItem().ShaderResourceTexture : GWhiteTexture->TextureRHI
);
}
(......)
}
從上面可知,光源的陰影遮蔽紋理在shader的名字叫:LightAttenuationTexture,採用器名字叫:LightAttenuationTextureSampler。
5.6.5.6 陰影Shader邏輯
搜索所有Shader文件的關鍵字LightAttenuationTexture,不難在common.ush中找到相關的邏輯:
// Engine\Shaders\Private\Common.ush
(......)
// 聲明陰影遮蔽變數.
Texture2D LightAttenuationTexture;
SamplerState LightAttenuationTextureSampler;
(......)
// 獲取像素的光照衰減(即陰影)的*方.
float4 GetPerPixelLightAttenuation(float2 UV)
{
return Square(Texture2DSampleLevel(LightAttenuationTexture, LightAttenuationTextureSampler, UV, 0));
}
(......)
繼續追蹤GetPerPixelLightAttenuation
介面,最終發現唯一的一處調用,在DeferredLightPixelShaders.usf中:
// Engine\Shaders\Private\DeferredLightPixelShaders.usf
(.....)
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;
(......)
const float SceneDepth = CalcSceneDepth(InputParams.ScreenUV);
FDeferredLightData LightData = SetupLightDataForStandardDeferred();
(......)
float SurfaceShadow = 1.0f;
// 注意此處調用了GetPerPixelLightAttenuation(InputParams.ScreenUV)作為GetDynamicLighting的float4 LightAttenuation實參.
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;
(......)
OutColor.rgba *= GetExposure();
}
注意上面的GetDynamicLighting
的形參float4 LightAttenuation
正是調用了GetPerPixelLightAttenuation(InputParams.ScreenUV)
進行賦值。要理解這個參數的具體計算邏輯,還得深入GetDynamicLighting
:
// Engine\Shaders\Private\DeferredLightingCommon.ush
float4 GetDynamicLighting(..., float4 LightAttenuation, ..., inout float SurfaceShadow)
{
// 此處調用了GetDynamicLightingSplit(分析見下面)
FDeferredLightingSplit SplitLighting = GetDynamicLightingSplit(
WorldPosition, CameraVector, GBuffer, AmbientOcclusion, ShadingModelID,
LightData, LightAttenuation, Dither, SVPos, SourceTexture,
SurfaceShadow);
return SplitLighting.SpecularLighting + SplitLighting.DiffuseLighting;
}
FDeferredLightingSplit GetDynamicLightingSplit(..., float4 LightAttenuation, ..., inout float SurfaceShadow)
{
(......)
BRANCH
if( LightMask > 0 )
{
FShadowTerms Shadow;
Shadow.SurfaceShadow = AmbientOcclusion;
(......)
// 獲取陰影數據項, 這裡會傳入LightAttenuation, 後面會繼續追蹤GetShadowTerms.
GetShadowTerms(GBuffer, LightData, WorldPosition, L, LightAttenuation, Dither, Shadow);
SurfaceShadow = Shadow.SurfaceShadow;
if( Shadow.SurfaceShadow + Shadow.TransmissionShadow > 0 )
{
const bool bNeedsSeparateSubsurfaceLightAccumulation = UseSubsurfaceProfile(GBuffer.ShadingModelID);
float3 LightColor = LightData.Color;
(......)
float3 LightingDiffuse = Diffuse_Lambert( GBuffer.DiffuseColor ) * Lighting;
LightAccumulator_AddSplit(LightAccumulator, LightingDiffuse, 0.0f, 0, LightColor * LightMask * Shadow.SurfaceShadow, bNeedsSeparateSubsurfaceLightAccumulation);
(......)
}
}
return LightAccumulator_GetResultSplit(LightAccumulator);
}
void GetShadowTerms(FGBufferData GBuffer, FDeferredLightData LightData, float3 WorldPosition, float3 L, float4 LightAttenuation, float Dither, inout FShadowTerms Shadow)
{
float ContactShadowLength = 0.0f;
const float ContactShadowLengthScreenScale = View.ClipToView[1][1] * GBuffer.Depth;
BRANCH
if (LightData.ShadowedBits)
{
// 重映射光照衰減緩衝. (具體參見ShadowRendering.cpp)
// LightAttenuation的數據布局:
// LightAttenuation.x: 全景*行光陰影.
// LightAttenuation.y: 全景*行光SSS陰影.
// LightAttenuation.z: 光照函數+逐物體陰影.
// LightAttenuation.w: 逐物體SSS陰影.
// 從*似的GBuffer通道獲取靜態陰影.
float UsesStaticShadowMap = dot(LightData.ShadowMapChannelMask, float4(1, 1, 1, 1));
// 恢復靜態陰影.
float StaticShadowing = lerp(1, dot(GBuffer.PrecomputedShadowFactors, LightData.ShadowMapChannelMask), UsesStaticShadowMap);
if (LightData.bRadialLight) // 徑向光源
{
// 恢復陰影數據.
// 表面陰影 = (光照函數+逐物體陰影) * 靜態陰影.
Shadow.SurfaceShadow = LightAttenuation.z * StaticShadowing;
Shadow.TransmissionShadow = LightAttenuation.w * StaticShadowing;
Shadow.TransmissionThickness = LightAttenuation.w;
}
else // 非徑向光源(*行光)
{
// Remapping the light attenuation buffer (see ShadowRendering.cpp)
// Also fix up the fade between dynamic and static shadows
// to work with plane splits rather than spheres.
float DynamicShadowFraction = DistanceFromCameraFade(GBuffer.Depth, LightData, WorldPosition, View.WorldCameraOrigin);
// For a directional light, fade between static shadowing and the whole scene dynamic shadowing based on distance + per object shadows
Shadow.SurfaceShadow = lerp(LightAttenuation.x, StaticShadowing, DynamicShadowFraction);
// Fade between SSS dynamic shadowing and static shadowing based on distance
Shadow.TransmissionShadow = min(lerp(LightAttenuation.y, StaticShadowing, DynamicShadowFraction), LightAttenuation.w);
Shadow.SurfaceShadow *= LightAttenuation.z;
Shadow.TransmissionShadow *= LightAttenuation.z;
// Need this min or backscattering will leak when in shadow which cast by non perobject shadow(Only for directional light)
Shadow.TransmissionThickness = min(LightAttenuation.y, LightAttenuation.w);
}
FLATTEN
if (LightData.ShadowedBits > 1 && LightData.ContactShadowLength > 0)
{
ContactShadowLength = LightData.ContactShadowLength * (LightData.ContactShadowLengthInWS ? 1.0f : ContactShadowLengthScreenScale);
}
}
// 接觸陰影.
#if SUPPORT_CONTACT_SHADOWS
if ((LightData.ShadowedBits < 2 && (GBuffer.ShadingModelID == SHADINGMODELID_HAIR))
|| GBuffer.ShadingModelID == SHADINGMODELID_EYE)
{
ContactShadowLength = 0.2 * ContactShadowLengthScreenScale;
}
#if MATERIAL_CONTACT_SHADOWS
ContactShadowLength = 0.2 * ContactShadowLengthScreenScale;
#endif
BRANCH
if (ContactShadowLength > 0.0)
{
float StepOffset = Dither - 0.5;
float ContactShadow = ShadowRayCast( WorldPosition + View.PreViewTranslation, L, ContactShadowLength, 8, StepOffset );
Shadow.SurfaceShadow *= ContactShadow;
FLATTEN
if( GBuffer.ShadingModelID == SHADINGMODELID_HAIR || GBuffer.ShadingModelID == SHADINGMODELID_EYE )
{
const bool bUseComplexTransmittance = (LightData.HairTransmittance.ScatteringComponent & HAIR_COMPONENT_MULTISCATTER) > 0;
if (!bUseComplexTransmittance)
{
Shadow.TransmissionShadow *= ContactShadow;
}
}
else
Shadow.TransmissionShadow *= ContactShadow * 0.5 + 0.5;
}
#endif
Shadow.HairTransmittance = LightData.HairTransmittance;
Shadow.HairTransmittance.OpaqueVisibility = Shadow.SurfaceShadow;
}
在計算表面陰影時,直接取樣一次LightAttenuationTexture的值,恢復出各類陰影的參數,最後應用於光照計算中。
5.6.5.7 陰影應用總結
陰影應用階段可以關注幾個重要步驟和額外說明:
-
每個開啟了陰影且擁有有效陰影的光源都會渲染一次ScreenShadowMaskTexture。
-
渲染ScreenShadowMaskTexture前,會調用ClearShadowMask清理ScreenShadowMaskTexture,保證ScreenShadowMaskTexture是原始狀態(全白)。
-
調用RenderShadowProjections,將光源下的所有陰影實例投射併疊加到螢幕空間的ScreenShadowMaskTexture。這樣做的目的是類似於延遲著色,將多個陰影提前合併到View的視圖空間,以便後續光照階段只需要取樣一次即可得到陰影資訊,提升渲染效率。缺點是要多一個Pass來合併光源的陰影,增加了Draw Call,也增加了少量的顯示記憶體消耗。
-
ScreenShadowMaskTexture除了常規的深度圖陰影,還可能包含距離場陰影、次表面陰影、透明體積陰影、膠囊體陰影、高度場陰影、頭髮陰影、RT陰影等類型。它的每個通道都有特殊的用途,用於細分和存儲不同類型的陰影資訊。
-
渲染ScreenShadowMaskTexture的shader在過濾陰影圖時,支援三種過濾方式:無過濾、PCSS和自定義PCF。
-
在光照計算的shader中,ScreenShadowMaskTexture被綁定到LightAttenuationTexture(光照衰減紋理)的變數中,亦即C++層的ScreenShadowMaskTexture其實就是光照Shader的LightAttenuationTexture。
-
在光照Shader文件DeferredLightPixelShaders.usf中,計算動態光照時,只調用一次了GetPerPixelLightAttenuation()的值(亦即只取樣一次LightAttenuationTexture),即可計算出物體表面受陰影影響的光照結果。
5.6.6 UE陰影總結
UE陰影系統的複雜度大大超乎了筆者原有的想像,陰影篇章只不過是分析了陰影的主要渲染流程和基礎技術,不包含其他類型的陰影,都已經有2萬多字的篇幅了,遠遠超出了原先的設想和規劃。
下圖總結了陰影渲染的主要流程和關鍵調用:
A(初始化陰影) –>|InitDynamicShadows| B(渲染陰影圖)
B –>|RenderShadowDepthMaps| C(渲染ScreenShadowMaskTexture)
C –>|RenderShadowProjections| D(取樣LightAttenuationTexture)
D –>|GetPerPixelLightAttenuation| E(計算表面陰影)
E –>|GetShadowTerms| F(計算表面光照)
F –>|GetDynamicLighting| G(輸出結果)
上面最出彩的莫過於增加了ScreenShadowMaskTexture的渲染步驟,充分利用GBuffer的幾何數據、特殊混合模式和精細的通道分配,提前將光源複雜的陰影數據渲染到螢幕空間的光照衰減紋理中,以便在光照計算時,只需要取樣一次光照衰減紋理即可重建出表面陰影。
需要特意指出的是,增加額外的Pass來渲染ScreenShadowMaskTexture會增加Draw Call,增加顯示記憶體消耗,對於簡單光影的場景,可能會帶來性能的下降。
5.7 本篇總結
本篇主要闡述了UE的直接光影的渲染流程和主要演算法,使得讀者對光影的渲染有著大致的理解,至於更多技術細節和原理,需要讀者自己去研讀UE源碼發掘。
本篇篇幅達到5萬多字,是目前本系列文章最長的一篇,但也是撰寫時間最短的一篇,5月果然是高效的月份。
恰逢昨天(2021.5.27),千呼萬喚使出來的UE5終於發布了預覽版,筆者第一時間下載並簡略體驗了一下,領略UE5的次世代光影的魅力。
UE5炫酷的啟動畫面。
筆者興許會抽時間撰寫一篇UE5特輯,剖析其使用的Lumen和Nanite的技術原理和實現細節。
5.7.1 本篇思考
按慣例,本篇也布置一些小思考,以助理解和加深UE BasePass和LightingPass的掌握和理解:
- 請簡述UE的光源的類型和主要渲染流程。
- 請簡述UE的陰影的類型和主要渲染流程。
- 請修復UE官方版本中透明物體在區域光下無法被照亮的問題。
- 擴充UE的燈光通道數量。
- 請實現逐網格的虛擬光源,使得每個網格可以被單獨打光,並支援不透明、Masked、半透明物體。
特別說明
- 感謝所有參考文獻的作者,部分圖片來自參考文獻和網路,侵刪。
- 本系列文章為筆者原創,只發表在部落格園上,歡迎分享本文鏈接,但未經同意,不允許轉載!
- 系列文章,未完待續,完整目錄請戳內容綱目。
- 系列文章,未完待續,完整目錄請戳內容綱目。
- 系列文章,未完待續,完整目錄請戳內容綱目。
參考文獻
- Unreal Engine 4 Sources
- Unreal Engine 4 Documentation
- Rendering and Graphics
- Graphics Programming Overview
- Materials
- Unreal Engine 4 Rendering
- Rendering – Schematic Overview
- UE4 Render System Sheet
- Lighting the Environment
- Shadow Casting
- 由淺入深學習PBR的原理和實現
- Real Shading in Unreal Engine 4
- Lighting the Environment
- Reflective Shadow Map
- RSM System
- HOW UNREAL RENDERS A FRAME
- Unreal Frame Breakdown
- LaTeX/Mathematics
- DECIMA ENGINE: ADVANCES IN LIGHTING AND AA
- Real-Time Polygonal-Light Shading with Linearly Transformed Cosines
- Cascaded Shadow Maps