언리얼 렌더링 시스템 분석(05) - 광원과 그림자
🌐 원문 링크: 剖析虚幻渲染体系(05)- 光源和阴影 (cnblogs.com/timlly)
📅 원문 발행일: 2021-05-27 | ✍️ 저자: Timlly (00 / )💡 시리즈 분류:
Unreal Engine Rendering| 국내 업계 표준 용어 감수 및 수식/도해 복원 적용 완료
이 문서에서는 UE의 다음 내용을 주로 설명합니다.
셰이더 코드 및 메인 모듈.
UE의 광원.
UE 그림자.
C++ 및 Shader를 포함한 BasePass의 메커니즘 및 구현.
C++ 및 Shader를 포함한 LightingPass의 메커니즘 및 구현.
그 중 BasePass와 LightingPass는 이전 글에서 간략하게 분석하였고, 이번 글에서는 계속해서 심도있게 분석해보겠습니다.
이 글에서는 처음으로 mermaid 구문을 소개합니다. 텍스트 설명을 그리기 도구로 사용하는
그래프 TD A[언리얼 엔진] --> B(렌더링 시스템) B --> C{RHI} C -->|하나| D[다이렉트X] C -->|2| E[금속] C -->|3| F[불칸]
인어 구문을 사용하여 만든 벡터 일러스트레이션은 텍스트 드로잉의 새로운 시대를 엽니다.
전방에 고에너지 경고가 있습니다. 이 글의 길이는 50,000단어가 넘습니다. 현재 이 시리즈 중 가장 긴 글이므로 정신적으로 준비가 필요합니다.
5.1.2 기본 개념
이 기사에 포함된 렌더링의 기본 개념과 분석 중 일부는 다음과 같습니다.
개념 약어 중국어 번역 분석하다
셰이더
- 셰이더, 셰이딩 코드 GPU 실행에 사용되는 코드 조각입니다. 일반적인 유형에는 VS(버텍스 셰이더) 및 PS(픽셀 셰이더)가 포함됩니다. 또한 일반적으로 셰이더 코드 파일을 참조합니다.
버텍스 셰이더 VS 버텍스 셰이더 GPU의 버텍스 처리 단계에서 실행되는 코드 조각으로, 버텍스 변환을 처리하는 데 자주 사용됩니다.
픽셀 셰이더 추신 픽셀 셰이더 OpenGL에서는 픽셀 처리 단계에서 구체적으로 실행되는 코드 조각인 Fragment Shader라고 합니다. 조명 계산 등과 같이 래스터라이제이션된 픽셀을 처리하는 데 사용됩니다.
고급 셰이딩 언어 HLSL 고급 셰이딩 언어 DirectX의 전용 Shader 언어인 Vulkan과 OpenGL을 GLSL이라고 하고 Metal의 전용 언어를 MSL이라고 합니다.
5.2 셰이더 시스템
다음 장에서는 셰이더 코드를 직접 분석할 예정이므로, 이번 장에서는 먼저 UE의 셰이더 시스템에 대한 간략한 분석을 제공하여 모두가 다음 장으로 원활하게 전환할 수 있도록 하겠습니다.
5.2.1 셰이더 개요
UE의 내장 셰이더 파일은 Engine\Shaders 디렉터리에 있습니다:

모든 셰이더 파일의 총 개수는 600개를 초과합니다. 셰이더 헤더 파일의 접미사는 ush로 다른 셰이더 파일에 포함될 수 있으며, 구현 파일은 usf로 일반적으로 다른 셰이더 파일에 포함될 수 없습니다. 다음은 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 셰이더 모듈 수준
첫 번째 단계 셰이더 모듈은 가장 낮고 가장 기본적인 모듈입니다. 이러한 모듈은 다른 모듈을 참조하지 않지만 다른 많은 모듈에서 참조됩니다. 이러한 모듈에는 주로 다음이 포함됩니다.
-BasePassCommon.ush
-BRDF.ush
CapsuleLight.ush
Common.ush
CommonViewUniformBuffer.ush
정의.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 셰이더 기본 모듈
이 섹션에서는 일부 기본 또는 중요한 셰이더 모듈을 선택하고 해당 인터페이스, 유형 및 정의를 분석하여 모든 사람이 UE의 내장 셰이더 모듈에 대한 일반적인 이해를 가질 수 있도록 하고, UE가 어떤 모듈과 함께 제공되는지, 어떤 내장 인터페이스가 있는지 이해하여 후속 셰이더 코드 분석에 직면할 때 더 명확해질 것입니다.
5.2.3.1 플랫폼.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
// 초기화(Initialize)플랫폼의 .
#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
// 압축해제/언패킹(Unpacking)데이터 .
#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
#endif5.2.3.2 공통.ush
이 모듈에는 주로 다음과 같이 그래픽 API 또는 기능 수준과 관련된 매크로, 유형, 로컬 변수, 정적 변수, 기본 도구 인터페이스 등이 포함됩니다.
// 타입/유형 정의 또는 캡슐화
#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;
};
// 、상수(Constant)정의
#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);
// 텍스처(Texture)샘플링
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);
// 라이팅 계산/산출(Calculate)
MaterialFloat PhongShadingPow(MaterialFloat X, MaterialFloat Y);
// 데이터 변환(Transform/Convert)
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);
// 좌표 、변환(Transform/Convert)
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);
// 드로우/렌더(Draw)인터페이스
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);
(.....)공통 셰이더 모듈은 수많은 기본 유형, 인터페이스, 변수 등을 캡슐화하고 있음을 알 수 있습니다. 위는 인터페이스의 일부만 표시하고 구현 코드는 단순화를 위해 제거되었습니다. 구현된 아동화를 보고 싶다면 UE의 소스코드를 직접 확인해보세요.
5.2.3.3 정의.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
이 모듈은 주로 재료의 모든 셰이딩 모델을 정의하고 소수의 관련 도구 인터페이스를 제공합니다.
// 머티리얼(Material)셰이딩 모델 , 타입/유형 의 라이팅 알고리즘 및 프로세스 .
#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)
// 반사(Reflection)(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);
// 의 반사(Reflection)F0.
float DielectricSpecularToF0(float Specular)
{
return 0.08f * Specular;
}
// 의 F0변환(Transform/Convert)까지 IOR(굴절 ).
float DielectricF0ToIor(float F0)
{
return 2.0f / (1.0f - sqrt(F0)) - 1.0f;
}
// 의 IOR(굴절 )변환(Transform/Convert)까지 F0.
float DielectricIorToF0(float Ior)
{
const float F0Sqrt = (Ior-1)/(Ior+1);
const float F0 = F0Sqrt*F0Sqrt;
return F0;
}
// 계산/산출(Calculate)테이블 의 F0.
float3 ComputeF0(float Specular, float3 BaseColor, float Metallic)
{
return lerp(DielectricSpecularToF0(Specular).xxx, BaseColor, Metallic.xxx);
}UE의 기본 ShadingModelID는 4비트(최대 16비트)만 차지하며, 현재 UE의 내장 셰이딩 모델은 13비트를 차지합니다. 즉, 맞춤형 ShadingModel은 최대 3비트만 가질 수 있습니다.
5.2.3.5 BasePassCommon.ush
이 모듈은 BasePass의 일부 변수, 매크로 정의, 보간 구조 및 도구 클래스 인터페이스를 정의합니다.
// 반투명(Translucent)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
// 반투명(Translucent)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)
(......)
// 보간(Interpolation)구조체 의 .
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의 보간(Interpolation)구조체 .
struct FBasePassInterpolantsVSToPS : FSharedBasePassInterpolants
{
// Note: TEXCOORD8 is unused
#if USE_WORLD_POSITION_EXCLUDING_SHADER_OFFSETS
float3 PixelPositionExcludingWPO : TEXCOORD9;
#endif
};
// VS -> DS(domain shader)의 보간(Interpolation)구조체 .
struct FBasePassInterpolantsVSToDS : FSharedBasePassInterpolants
{
#if USE_WORLD_POSITION_EXCLUDING_SHADER_OFFSETS
float3 WorldPositionExcludingWPO : TEXCOORD9;
#endif
};
#else
#define FBasePassInterpolantsVSToPS FSharedBasePassInterpolants
#endif
// 샘플러(Sampler)캡슐화 .
#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, 저장(Store)을(를) 활용하여 벡터 의 포인트 .
struct BxDFContext
{
float NoV;
float NoL;
float VoL;
float NoH;
float VoH;
float XoV;
float XoL;
float XoH;
float YoV;
float YoL;
float YoH;
};
// 초기화(Initialize)BxDF
void Init( inout BxDFContext Context, half3 N, half3 V, half3 L );
// 초기화(Initialize)BxDF, X, Y개 벡터 ( 및 및 노멀(Normal)의 벡터 ).
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 );
// 디퓨즈(Diffuse).
float3 Diffuse_Lambert( float3 DiffuseColor )
{
return DiffuseColor * (1 / PI);
}
// Burley디퓨즈(Diffuse).
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디퓨즈(Diffuse).
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디퓨즈(Diffuse).
float3 Diffuse_Gotanda( float3 DiffuseColor, float Roughness, float NoV, float NoL, float VoH );
// Blinn노멀(Normal)분포 .
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노멀(Normal)분포 .
float D_Beckmann( float a2, float NoH )
{
float NoH2 = NoH * NoH;
return exp( (NoH2 - 1) / (a2 * NoH2) ) / ( PI * a2 * NoH2 * NoH2 );
}
// GGX노멀(Normal)분포 .
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노멀(Normal)분포 .
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);
}
// 반전 의 노멀(Normal)분포 함수 .
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(BxDFContext 컨텍스트 내부, 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
이 모듈은 주로 버텍스 변환과 관련된 보조 인터페이스를 정의합니다.
// 포인트 변환(Transform)
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
#endif5.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;
};
// 그림자(Shadow)데이터 , 을(를) 활용하여 저장(Store)그림자(Shadow)결과 .
struct FShadowTerms
{
float SurfaceShadow;
float TransmissionShadow;
float TransmissionThickness;
FHairTransmittanceData HairTransmittance;
};
// ---- 라이팅 인터페이스 ----
// 정규화(Normalize).
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호출(Call)의 인터페이스 .
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
이 모듈은 주로 디퍼드 라이팅 셰이딩에 대한 일반적인 인터페이스, 매크로, 변수 및 유형을 정의합니다.
// 컬러 변환(Transform/Convert)
float3 RGBToYCoCg( float3 RGB );
float3 YCoCgToRGB( float3 YCoCg );
// 벡터 압축/패킹(Packing) 및 압축해제/언패킹(Unpacking)(벡터 , , )
float2 UnitVectorToOctahedron( float3 N );
float3 OctahedronToUnitVector( float2 Oct );
float2 UnitVectorToHemiOctahedron( float3 N );
float3 HemiOctahedronToUnitVector( float2 Oct );
// 데이터 압축/패킹(Packing) 및 압축해제/언패킹(Unpacking)
float3 Pack1212To888( float2 x );
float2 Pack888To1212( float3 x );
float Encode71(float Scalar, uint Mask);
float Decode71(float Scalar, out uint Mask);
// 노멀(Normal)
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);
// 검사/감지(Detect)인터페이스 .
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);주목해야 할 것은 BasePass와 LightingPass의 VS와 PS 사이에 사용할 수 있고 포워드 및 디퍼드 렌더링에도 사용할 수 있는 범용 구조인 FGBufferData의 핵심 구조입니다. 가장 큰 데이터 모음입니다. 어떤 경우에는 일부 속성만 유효합니다. 자세한 내용은 ShadingModelsMaterial.ush의 SetGBufferForShadingModel을 참조하세요.
5.2.3.11 DeferredLightingCommon.ush
이 모듈은 지연된 조명과 관련된 일반적인 인터페이스, 매크로, 변수, 유형 등을 정의합니다.
// 개 레이턴시 라이트(Light)데이터 (을(를) 활용하여 렌더링(Render))。
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;
};
// 레이턴시 라이트(Light)데이터 ,의 라이트(Light),을(를) 활용하여 의 의 셰이딩 모델 내에서 ,로써 가속 및 。
struct FSimpleDeferredLightData
{
float3 Position;
float InvRadius;
float3 Color;
float FalloffExponent;
bool bInverseSquared;
};
// 에 따라 라이트(Light) 및 의 데이터 계산/산출(Calculate)。(0테이블 ,1。
float DistanceFromCameraFade(float SceneDepth, FDeferredLightData LightData, float3 WorldPosition, float3 CameraPosition);
// ---- 그림자(Shadow)의 인터페이스 ----
// 그림자(Shadow)레이 캐스팅(Ray Casting) 교차 검사. 만약 검사/감지(Detect)까지 , 이면 반환(Return). 만약 광선(Ray) 내에서 동적 그림자(Shadow), 이면 bOutHitCastDynamicShadow로 true.
float ShadowRayCast(float3 RayOriginTranslatedWorld, float3 RayDirection, float RayLength, int NumSteps, float StepOffset, out bool bOutHitCastContactShadow );
// 계산/산출(Calculate)라이트(Light)의 그림자(Shadow).
void GetShadowTerms(FGBufferData GBuffer, FDeferredLightData LightData, float3 WorldPosition, float3 L, float4 LightAttenuation, float Dither, inout FShadowTerms Shadow);
// 페치/가져오기(Fetch)라이트(Light)의 .
FRect GetRect(float3 ToLight, FDeferredLightData LightData);
FCapsuleLight GetCapsule( float3 ToLight, FDeferredLightData LightData );
// ---- 라이팅 계산/산출(Calculate)인터페이스 ----
// 페치/가져오기(Fetch)라이트(Light)의 , 라이팅 결과 디퓨즈(Diffuse) 및 .
FDeferredLightingSplit GetDynamicLightingSplit(float3 WorldPosition, float3 CameraVector, FGBufferData GBuffer, ...);
// 페치/가져오기(Fetch)라이트(Light)의 .
float4 GetDynamicLighting(float3 WorldPosition, float3 CameraVector, FGBufferData GBuffer, ...);
// 페치/가져오기(Fetch)라이트(Light)의 .
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의 보간(Interpolation).
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
// 로부터 바인딩(Bind)의 포인트 Buffer 내에서 페치/가져오기(Fetch)의 포인트 입력데이터 .
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
// 텍스처(Texture)좌표 .
#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
// 계산/산출(Calculate)셰이딩 .
#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
};
// 위치 및 노멀(Normal)의 포인트 데이터 입력.
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
};
// 캐싱(Cache) 내에서 계산/산출(Calculate)결과 의 포인트 데이터 , 계산/산출(Calculate)회 .
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;
};
// 페치/가져오기(Fetch)인스턴스 데이터 인터페이스 .
#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
// 로부터 보간(Interpolation)구조체 내에서 페치/가져오기(Fetch)머티리얼(Material)파라미터 .
FMaterialPixelParameters GetMaterialPixelParameters(FVertexFactoryInterpolantsVSToPS Interpolants, float4 SvPosition);
half3x3 CalcTangentToWorldNoScale(FVertexFactoryIntermediates Intermediates, half3x3 TangentToLocal);
FMaterialVertexParameters GetMaterialVertexParameters(FVertexFactoryInput Input, ...);
(......)
// 포인트 데이터 계산/산출(Calculate) 및 페치/가져오기(Fetch)인터페이스 .
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의 렌더링 프로세스, 렌더링 상태, 셰이더 로직 등에 대해 자세히 설명합니다.
5.3.1 BasePass 렌더링 프로세스
이전 기사에서는 BasePass의 렌더링 프로세스와 일부 핵심 로직도 다루었습니다. 이 섹션에서는 이를 간략하게 검토합니다. FDeferredShadingSceneRenderer::Render의 BasePass는 PrePass 뒤, LightingPass 앞입니다:
void FDeferredShadingSceneRenderer::Render(FRHICommandListImmediate& RHICmdList)
{
(......)
// 드로우/렌더(Draw)씬 뎁스(SceneDepth).
RenderPrePass(RHICmdList, ...);
(......)
// 렌더링(Render)Base Pass.
RenderBasePass(RHICmdList, ...);
(......)
// 렌더링(Render)라이트(Light).
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)
{
// 드로우/렌더(Draw).
FScopedCommandListWaitForTasks Flusher(CVarRHICmdFlushRenderThreadTasksBasePass.GetValueOnRenderThread() > 0 || CVarRHICmdFlushRenderThreadTasks.GetValueOnRenderThread() > 0, RHICmdList);
// 순회(Traverse/Iterate)view, 개 view렌더링(Render)회 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);
// 실행(Execute)병렬 렌더링(Render).
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)
{
// 병렬 드로우/렌더(Draw)의 데이터 : 커맨드 큐 , , 렌더링(Render)상태 .
FBasePassParallelCommandListSet ParallelSet(View, ParentCmdList,
CVarRHICmdBasePassDeferredContexts.GetValueOnRenderThread() > 0,
CVarRHICmdFlushRenderThreadTasksBasePass.GetValueOnRenderThread() == 0 && CVarRHICmdFlushRenderThreadTasks.GetValueOnRenderThread() == 0,
this,
BasePassDepthStencilAccess,
InDrawRenderState);
// 트리거 병렬 드로우/렌더(Draw).
View.ParallelMeshDrawCommandPasses[EMeshPass::BasePass].DispatchDraw(&ParallelSet, ParentCmdList);
}RenderBasePass는 FScopedCommandListWaitForTasks를 사용하여 그리기 지침이 완료될 때까지 기다립니다. 다음은 후자의 구현 코드입니다.
// Engine\Source\Runtime\RHI\Public\RHICommandList.h
// 즉, 타입/유형 .
namespace EImmediateFlushType
{
enum Type
{
WaitForOutstandingTasksOnly = 0, // 오직 현재(Current)의 .
DispatchToRHIThread, // 까지 RHI스레드(Thread).
WaitForDispatchToRHIThread, // 까지 RHI스레드(Thread).
FlushRHIThread, // RHI스레드(Thread)까지 GPU.
FlushRHIThreadFlushResources, // RHI스레드(Thread)리소스 .
FlushRHIThreadFlushResourcesFlushDeferredDeletes // RHI스레드(Thread)리소스 레이턴시 의 리소스 .
};
};
struct FScopedCommandListWaitForTasks
{
FRHICommandListImmediate& RHICmdList; // 의 RHICmdList.
bool bWaitForTasks; // 는 .
FScopedCommandListWaitForTasks(bool InbWaitForTasks, FRHICommandListImmediate& InRHICmdList)
: RHICmdList(InRHICmdList)
, bWaitForTasks(InbWaitForTasks)
{
}
// 함수 내에서 실행(Execute).
~FScopedCommandListWaitForTasks()
{
if (bWaitForTasks)
{
// 만약 는 의 RHI스레드(Thread), 이면 오직 현재(Current)의 .
if (IsRunningRHIInSeparateThread())
{
RHICmdList.ImmediateFlush(EImmediateFlushType::WaitForOutstandingTasksOnly);
}
// 의 RHI스레드(Thread),RHI스레드(Thread).
else
{
RHICmdList.ImmediateFlush(EImmediateFlushType::FlushRHIThread);
}
}
}
};BasePass 렌더링이 완료될 때까지 기다려야 하는지 여부는 CVarRHICmdFlushRenderThreadTasksBasePass(r.RHICmdFlushRenderThreadTasksBasePass) 또는 CVarRHICmdFlushRenderThreadTasks(r.RHICmdFlushRenderThreadTasks)라는 두 가지 콘솔 명령 중 하나에 의해 활성화됩니다.
5.3.2 BasePass 렌더링 상태
이 섹션에서는 BasePass 렌더링에 사용되는 다양한 렌더링 상태, 셰이더 바인딩 및 드로잉 매개변수에 대해 자세히 설명합니다. 우리는 BasePass가 FBasePassMeshProcessor를 통해 많은 셰이더 바인딩 및 드로잉 매개변수를 수집한다는 것을 알고 있습니다. 프로세서 프로세스에서 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:
// 페치/가져오기(Fetch)Shader바인딩(Bind)。
void GetShaderBindings(const FScene* Scene, ERHIFeatureLevel::Type FeatureLevel, ...) const;
void GetElementShaderBindings(const FShaderMapPointerTable& PointerTable, const FScene* Scene, ...) const;
// 반사(Reflection)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 바인딩 획득, 컴파일 환경 변경, 지정된 순열 및 조합 셰이더만 컴파일 등의 인터페이스를 제공하는 것을 알 수 있다. 그 밖에 반사구, 라이트맵형 등의 속성도 갖고 있다. 다음은 셰이더 바인딩을 얻기 위한 코드 분석입니다.
// Engine\Source\Runtime\Renderer\Private\BasePassRendering.inl
// 페치/가져오기(Fetch)의 VS의 shader바인딩(Bind)。
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
{
// 페치/가져오기(Fetch)FMeshMaterialShader의 shader바인딩(Bind).
FMeshMaterialShader::GetShaderBindings(Scene, FeatureLevel, PrimitiveSceneProxy, MaterialRenderProxy, Material, DrawRenderState, ShaderElementData, ShaderBindings);
// 인스턴스 , 이면 페치/가져오기(Fetch)의 반사(Reflection)Buffer.
if (Scene)
{
FRHIUniformBuffer* ReflectionCaptureUniformBuffer = Scene->UniformBuffers.ReflectionCaptureUniformBuffer.GetReference();
ShaderBindings.Add(ReflectionCaptureBuffer, ReflectionCaptureUniformBuffer);
}
// , 이면 생성(Create)개 기본값(Default)의 반사(Reflection)Buffer.
else
{
ShaderBindings.Add(ReflectionCaptureBuffer, DrawRenderState.GetReflectionCaptureUniformBuffer());
}
// 로부터 라이팅 타입/유형 내에서 페치/가져오기(Fetch)shader바인딩(Bind).
LightMapPolicyType::GetVertexShaderBindings(
PrimitiveSceneProxy,
ShaderElementData.LightMapPolicyElementData,
this,
ShaderBindings);
}
// 페치/가져오기(Fetch)FMeshBatchElement의 VS의 shader바인딩(Bind)。
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
{
// 호출(Call)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"));
(......)
}
// 페치/가져오기(Fetch)shader바인딩(Bind).
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의 픽셀(Pixel)셰이딩 .
template<typename LightMapPolicyType, bool bEnableSkyLight>
class TBasePassPS : public TBasePassPixelShaderBaseType<LightMapPolicyType>
{
public:
// 활성화(Enable)의 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는 셰이더 바인딩 획득, 컴파일 환경 변경, 지정된 순열 및 셰이더 조합 컴파일과 같은 인터페이스를 제공합니다. 또한 반사구, 라이트맵 유형 등의 속성도 가지고 있습니다. 다음은 셰이더 바인딩을 얻기 위한 코드입니다(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의 다른 렌더링 상태를 계속 분석하세요. RenderState와 재질은 이전 기사의 4.3.6장 BasePass에서 분석되었으므로 기본 결과는 여기에 직접 제공됩니다.
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에서 수집한 재질을 사용합니다.
균일버퍼:
FOpaqueBasePassUniformParameters, BasePass의 전용 통합 버퍼입니다.
- 장면->UniformBuffers, 장면 관련 통합 버퍼.
지정되지 않은 기타 렌더링 상태는 기본 상태와 일치합니다.
5.3.3 베이스패스 셰이더
BasePass의 도면 중첩 논리는 이전 기사에서 분석되었습니다. 가장 바깥쪽 레이어부터 내부 레이어까지 장면, 뷰 및 그리드가 있습니다.
foreach(scene in scenes)
{
foreach(view in views)
{
foreach(mesh in meshes)
{
DrawMesh(...); // 회 호출(Call)렌더링(Render)실행(Execute)회 BasePassVertexShader 및 BasePassPixelShader의 코드 .
}
}
}이는 BasePassVertexShader 및 BasePassPixelShader가 실행되는 횟수가 다음과 같다는 것을 의미합니다.
이는 또한 CPU와 GPU 간의 데이터 교환을 줄이고, 렌더링 상태 전환을 줄이고, 캐시 적중률을 향상시키고, 인스턴스화 확률을 높이고, 그리기 호출을 줄일 수 있는 FMeshDrawCommand 정렬의 필요성을 보여줍니다. 그러나 실시간 게임의 경우 대부분의 경우 장면 수와 뷰 수는 1개입니다. 즉, VS 및 PS의 실행 수는 그리드 수에만 관련됩니다.
다음 두 섹션에서는 BasePass의 VS 및 PS 셰이더 로직을 분석합니다.
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();
// 페치/가져오기(Fetch)포인트 의 내에서 데이터 .
FVertexFactoryIntermediates VFIntermediates = GetVertexFactoryIntermediates(Input);
// 페치/가져오기(Fetch)의 위치 ().
float4 WorldPositionExcludingWPO = VertexFactoryGetWorldPosition(Input, VFIntermediates);
float4 WorldPosition = WorldPositionExcludingWPO;
float4 ClipSpacePosition;
// 로컬 의 .
float3x3 TangentToLocal = VertexFactoryGetTangentToLocal(Input, VFIntermediates);
// 페치/가져오기(Fetch)머티리얼(Material)의 포인트 파라미터 .
FMaterialVertexParameters VertexParameters = GetMaterialVertexParameters(Input, VFIntermediates, WorldPosition.xyz, TangentToLocal);
// WorldPosition추가(Add)머티리얼(Material).
{
WorldPosition.xyz += GetMaterialWorldPositionOffset(VertexParameters);
}
(......)
// 계산/산출(Calculate)클리핑/클램핑(Clipping)의 위치 .
{
float4 RasterizedWorldPosition = VertexFactoryGetRasterizedWorldPosition(Input, VFIntermediates, WorldPosition);
ClipSpacePosition = INVARIANT(mul(RasterizedWorldPosition, ResolvedView.TranslatedWorldToClip));
Output.Position = INVARIANT(ClipSpacePosition);
}
(......)
// 글로벌 클리핑/클램핑(Clipping)거리.
#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
// 출력보간(Interpolation)의 데이터 .
Output.FactoryInterpolants = VertexFactoryGetInterpolants(Input, VFIntermediates, VertexParameters);
(......)
// 계산/산출(Calculate)반투명(Translucent)의 컬러 .
#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
// 반투명(Translucent)의 포인트 라이팅 .
#if TRANSLUCENCY_ANY_PERVERTEX_LIGHTING
float3 WorldPositionForVertexLightingTranslated = VertexFactoryGetPositionForVertexLighting(Input, VFIntermediates, WorldPosition.xyz);
float3 WorldPositionForVertexLighting = WorldPositionForVertexLightingTranslated - ResolvedView.PreViewTranslation.xyz;
#endif
// 반투명(Translucent)의 포인트 라이팅 : 포인트 라이팅 (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
// 계산/산출(Calculate)라이팅
#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
// 처리(Process)벨로시티 버퍼(Velocity Buffer).
#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;
// 저장(Save)스크린 스페이스의 위치 .
#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
// 처리(Process) 내에서 데이터 .
FVertexFactoryIntermediates GetVertexFactoryIntermediates(FVertexFactoryInput Input)
{
FVertexFactoryIntermediates Intermediates;
// ID(활성화(Enable)GPU Scene유효(Valid)).
#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
// 및 변환(Transform)모멘트(Moment).
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;
}
// 페치/가져오기(Fetch)머티리얼(Material)의 포인트 파라미터 .
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
// 이전 프레임(Previous Frame)의 데이터 및 노멀(Normal).
Result.PreSkinnedPosition = Intermediates.PreSkinPosition.xyz;
Result.PreSkinnedNormal = TangentToLocal[2]; //TangentBias(Input.TangentZ.xyz);
// 처리(Process)텍스처(Texture)좌표 데이터 .
#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;
}
// 페치/가져오기(Fetch)포인트 보간(Interpolation)데이터 .
FVertexFactoryInterpolantsVSToPS VertexFactoryGetInterpolantsVSToPS(FVertexFactoryInput Input, FVertexFactoryIntermediates Intermediates, FMaterialVertexParameters VertexParameters)
{
FVertexFactoryInterpolantsVSToPS Interpolants;
Interpolants = (FVertexFactoryInterpolantsVSToPS)0;
// 처리(Process)텍스처(Texture)데이터 .
#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
// 라이팅 데이터 : 좌표 ,,,그림자(Shadow)좌표 .
#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"
(......)
// 노멀(Normal)러프니스(Roughness).
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
(......)
// 및 반투명(Translucent)라이팅 .
#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
// 스카이라이트(Skylight).
#if SIMPLE_FORWARD_SHADING || PLATFORM_FORCE_SIMPLE_SKY_DIFFUSE
#define GetEffectiveSkySHDiffuse GetSkySHDiffuseSimple
#else
#define GetEffectiveSkySHDiffuse GetSkySHDiffuse
#endif
void GetSkyLighting(FMaterialPixelParameters MaterialParameters, ...);
// 간접광(Indirect Light).
#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
// 픽셀(Pixel)뎁스 .
void ApplyPixelDepthOffsetForBasePass(inout FMaterialPixelParameters MaterialParameters, ...);
// AO회 .
float3 AOMultiBounce( float3 BaseColor, float AO );
float DotSpecularSG( float Roughness, float3 N, float3 V, FSphericalGaussian LightSG );
// 적용 노멀(Normal).
void ApplyBentNormal( in FMaterialPixelParameters MaterialParameters, in float Roughness, ... );
// BasePass픽셀(Pixel)셰이딩 .
void FPixelShaderInOut_MainPS(
FVertexFactoryInterpolantsVSToPS Interpolants,
FBasePassInterpolantsVSToPS BasePassInterpolants,
in FPixelShaderIn In,
inout FPixelShaderOut Out)
{
// 초기화(Initialize)GBuffer 및 view데이터 .
const uint EyeIndex = 0;
ResolvedView = ResolveView();
float4 OutVelocity = 0;
float4 OutGBufferD = 0;
float4 OutGBufferE = 0;
// 페치/가져오기(Fetch)픽셀(Pixel)의 머티리얼(Material)파라미터 (의 머티리얼(Material)는 머티리얼(Material)의 머티리얼(Material)).
FMaterialPixelParameters MaterialParameters = GetMaterialPixelParameters(Interpolants, In.SvPosition);
FPixelMaterialInputs PixelMaterialInputs;
// 라이팅 텍스처(Texture).
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
// 머티리얼(Material)의 파라미터 .
#if USE_WORLD_POSITION_EXCLUDING_SHADER_OFFSETS
{
float4 ScreenPosition = SvPositionToResolvedScreenPosition(In.SvPosition);
float3 TranslatedWorldPosition = SvPositionToResolvedTranslatedWorld(In.SvPosition);
// 계산/산출(Calculate)머티리얼(Material)의 파라미터 .
CalcMaterialParametersEx(MaterialParameters, PixelMaterialInputs, In.SvPosition, ScreenPosition, In.bIsFrontFace, TranslatedWorldPosition, BasePassInterpolants.PixelPositionExcludingWPO);
}
#else
{
float4 ScreenPosition = SvPositionToResolvedScreenPosition(In.SvPosition);
float3 TranslatedWorldPosition = SvPositionToResolvedTranslatedWorld(In.SvPosition);
// 계산/산출(Calculate)머티리얼(Material)의 파라미터 .
CalcMaterialParametersEx(MaterialParameters, PixelMaterialInputs, In.SvPosition, ScreenPosition, In.bIsFrontFace, TranslatedWorldPosition, TranslatedWorldPosition);
}
#endif
// 픽셀(Pixel)뎁스 .
#if OUTPUT_PIXEL_DEPTH_OFFSET
ApplyPixelDepthOffsetForBasePass(MaterialParameters, PixelMaterialInputs, BasePassInterpolants, Out.Depth);
#endif
// 처리(Process)픽셀(Pixel)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
// 페치/가져오기(Fetch)머티리얼(Material)의 .
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);
// 설정(Set)까지 의 파라미터 까지 GBuffer 내에서 .
SetGBufferForShadingModel(GBuffer, MaterialParameters, Opacity,BaseColor, Metallic, Specular, Roughness, Anisotropy, SubsurfaceColor, SubsurfaceProfile, GBufferDither, ShadingModel);
#if USES_GBUFFER
GBuffer.SelectiveOutputMask = GetSelectiveOutputMask();
GBuffer.Velocity = 0;
#endif
// 벨로시티 버퍼(Velocity Buffer).
#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);
// 노멀(Normal)러프니스(Roughness).
#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
// 디퓨즈(Diffuse)컬러 .
GBuffer.DiffuseColor = BaseColor - BaseColor * Metallic;
// BRDF의 거침(Rough).
#if !FORCE_FULLY_ROUGH
if (View.RenderingReflectionCaptureMask)
#endif
{
EnvBRDFApproxFullyRough(GBuffer.DiffuseColor, GBuffer.SpecularColor);
}
// 노멀(Normal).
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);
}
// 처리(Process)AO.
float DiffOcclusion = MaterialAO;
float SpecOcclusion = MaterialAO;
// 적용 노멀(Normal).
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라이팅 모델 , 이면 회 테이블 산란 컬러 까지 디퓨즈(Diffuse) 내에서 .
#if !MATERIAL_SHADINGMODEL_UNLIT
float3 DiffuseDir = BentNormal;
float3 DiffuseColorForIndirect = GBuffer.DiffuseColor;
// 회 테이블 산란 및 계산/산출(Calculate)셰이딩 모델 .
#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
// 계산/산출(Calculate)계산/산출(Calculate)라이팅 및 스카이라이트(Skylight).
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
// 디퓨즈(Diffuse).
DiffuseColor += (DiffuseIndirectLighting * DiffuseColorForIndirect + SubsurfaceIndirectLighting * SubsurfaceColor) * AOMultiBounce( GBuffer.BaseColor, DiffOcclusion );
// 렌더링(Render) 또는 반투명(Translucent)라이팅 .
#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
// 픽셀(Pixel).
#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
// 반투명(Translucent)의 라이팅 .
#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
// 만약 는 회 테이블 산란 또는 반투명(Translucent)셰이딩 모델 , DiffuseColor상태 , 디퓨즈(Diffuse)까지 컬러 내에서 .
#if !POST_PROCESS_SUBSURFACE && !MATERIAL_SHADINGMODEL_THIN_TRANSLUCENT
Color += DiffuseColor;
#endif
// 발광(Emissive) .
#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
// 저장(Save)GBuffer까지 MRT 내에서 , 저장(Save)의 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
(......)
// 캐싱(Cache) 및 의 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, 는 렌더링(Render), 그러나 로써 데이터 기록/쓰기(Write)까지 MRT[1] 내에서 .
#if GBUFFER_HAS_VELOCITY
Out.MRT[1] = OutVelocity;
#endif
#endif
// 처리(Process)프리 익스포저(Pre-exposure).
#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)
// 의 코드 호출(Call)의 FPixelShaderInOut_MainPS인터페이스 및 GBufferMRT의 출력.
#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로 인코딩합니다.
속도 버퍼, 객체별 섀도우 등과 같은 다음 RT에 GBuffer의 기본이 아닌 데이터를 씁니다.
사전 노출을 처리합니다.
BasePass의 픽셀 셰이더는 동적 조명을 계산하지 않지만 조명 처리를 수행하지 않는다는 의미는 아닙니다. 주로 정적 조명, 주변 조명, 간접 조명, 안개 효과, 표면 아래 산란 및 자체 조명을 처리합니다.
5.3.4 BasePass 요약
이전 섹션의 분석을 통해 BasePass의 VS 및 PS는 동적 조명을 계산하지 않지만 실제로 기하학적 객체와 관련된 많은 처리를 수행한다는 결론을 내릴 수 있습니다. 결과적으로 셰이더 명령의 수는 상당합니다. 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;이를 전설로 나누면 다음과 같다.
그래프 TD FinalOutColor(FinalColor) --> Mul1{} Mul1 --> 라이트어큐뮬레이터(라이트어큐뮬레이터) Mul1 --> View_PreExposure(View.PreExposure) LightAccumulator -> 색상(색상) 색상 --> Mul2{} Mul2 --> Fogging.a(Fogging.a) Mul2 --> 추가1{+} 추가1 --> Fogging.rgb(Fogging.rgb) Add1 --> 확산색상(확산색상) Add1 --> 방출(Emissive) 확산 색상 --> Mul3{} Mul3 --> AOMultiBounce(AOMultiBounce) AOMultiBounce --> GBuffer.BaseColor(GBuffer.BaseColor) AOMultiBounce --> DiffOcclusion(DiffOcclusion) Mul3 --> 추가2{+} 추가2 --> Mul4{} 추가2 --> Mul5{*} Mul4 --> 확산 간접 조명(확산 간접 조명) Mul4 --> DiffuseColorForIndirect(GBuffer.DiffuseColor) Mul5 --> 하위 표면 간접 조명(하위 표면 간접 조명) Mul5 --> 하위 표면 색상(하위 표면 색상)
따라서 기본 셰이딩 모델(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)가 포함됩니다. (아래 사진)

위의 광원에는 모두 변환 및 이동성과 같은 기본 속성이 있습니다.

이동성에는 고정형, 고정형, 이동형의 세 가지 모드가 있습니다.
정적 광원은 라이트맵 라이트맵을 오프라인으로 생성하기 위해 미리 계산된 광원으로만 사용할 수 있으며 해당 속성은 런타임에 변경할 수 없습니다. 성능 비용은 가장 낮지만 조정 가능성은 가장 낮습니다.
고정 광원은 오프라인에서 그림자와 간접 조명을 정적 형상으로 굽기 때문에 해당 그림자와 간접 조명은 런타임 시 변경될 수 없으며 위치도 변경될 수 없습니다. 다른 속성은 변경될 수 있습니다. 적당한 성능 소비 및 적당한 조정 가능성.
무버블 라이트는 런타임에 속성을 변경할 수 있는 완전히 동적인 라이트와 그림자입니다. 성능 소비가 가장 높고 조정 가능성도 가장 높습니다.
- 방향성 조명
장면의 모든 물체를 동일한 방향으로 비추는 태양, 달, 레이저 및 기타 광원을 시뮬레이션합니다.

빛의 세기, 색, 색온도 등의 속성과 Cascade Shadow(CSM), Beam(Light Shaft), Light Function(Light Function) 등의 속성 그룹을 가지고 있습니다.
- 포인트 라이트
전구와 같이 특정 지점에서 모든 방향으로 퍼지는 광원을 시뮬레이션합니다.

기본 속성 외에도 조명 기능 설정도 지원합니다.
- 스포트라이트
스포트라이트는 원뿔 모양으로 빛을 방출하여 손전등과 같은 광원을 시뮬레이션합니다.

내부 원뿔 각도, 외부 원뿔 각도 및 감쇠 반경과 같은 특수 속성이 있습니다. 조명 기능, IES 등도 지원합니다.
- 렉트 라이트
직사각형 조명은 LED, TV, 모니터 등과 같은 직사각형 영역으로 표면 광원을 시뮬레이션합니다.

해당 속성은 소스 너비, 소스 높이, 헛간 문 각도, 헛간 문 길이 등과 같은 특수 속성과 IES 및 조명 기능 속성 그룹을 갖춘 점 광원과 스포트라이트의 혼합입니다.
- 스카이라이트
스카이 라이트는 이름에서 알 수 있듯이 태양이나 흐린 조명 환경이 있는 장면(레벨)을 시뮬레이션합니다. 실외 환경에서 사용할 수 있을 뿐만 아니라 실내 환경에도 주변 조명 효과를 가져올 수 있습니다.

태양과 구름을 이용해 실외 하늘빛을 시뮬레이션합니다.
또한 고정, 고정, 이동의 세 가지 모바일 속성도 있습니다. 그 의미는 다른 유형의 일반 광원과 유사합니다. 스카이라이트에는 소스 유형 속성이 있습니다.

소스 유형은 두 가지 모드를 지원합니다.
- Capture Scene: 장면에서 큐브맵(큐브맵)을 캡처합니다. 이 모드는 이동성에 따라 3가지 동작을 세분화하는 데 사용할 수 있습니다.
정적 스카이라이트의 경우 장면의 조명을 구축할 때 장면 단계가 자동으로 트리거됩니다.
- 고정 또는 이동 가능한 스카이라이트의 경우 구성 요소가 로드될 때 장면이 캡처됩니다. 특별한 경우도 있는데, 실시간 캡처()가 켜진 경우(UE4.25에는 아직 이 기능이 없음) 런타임에 캡처 장면 인터페이스를 호출할 수 있습니다.

- 지정된 큐브맵: 장면에서 캡처하는 대신 기존 큐브맵을 사용할 수 있습니다.
5.4.2 광원 알고리즘
5.4.2.1 BRDF 개요
이 섹션에서는 Unreal Engine 4의 Real Shading과 Shader 소스 코드를 결합하여 UE의 조명 이론, 모델 및 알고리즘을 분석합니다. PBR 조명 이론과 지식이 약한 학생들은 저자의 다른 글을 읽고 PBR의 원리와 구현을 얕은 수준에서 더 깊은 수준으로 학습하는 것이 좋습니다.
1982년에 Cook과 Torrance가 공동으로 Cook-Torrance Specular BRDF를 제안했습니다. 이 모델은 동일한 장면에서 다양한 재료와 다양한 광원의 상대적 밝기를 고려합니다. 반사광의 방향 분포와 입사각에 따라 반사가 변할 때의 색상 변화를 기술하고, 특정 실제 물질로 만들어진 물체에서 반사되는 빛의 분광 에너지 분포를 얻을 수 있으며, 이 분광 에너지 분포를 바탕으로 색상을 정확하게 재현할 수 있습니다. 그 공식은 다음과 같습니다.
2012년에는 Disney Animation Studio의 Brent Burly가 Disney 원칙의 BRDF를 제안했습니다. 소수의 간단하고 이해하기 쉬운 매개변수와 매우 완벽한 아트 워크플로우를 사용하여 이전에 복잡했던 PBR의 매개변수와 제작 프로세스를 크게 단순화합니다. 완전히 물리적으로 정확하지는 않은 예술 중심의 셰이딩 모델입니다.

디즈니 원리 BRDF에 의해 추상화된 다양한 머티리얼 매개변수. 이러한 매개변수는 0과 1 사이의 값으로 매핑됩니다.
디즈니를 원칙으로 하는 BRDF는 이후의 게임 및 영화 산업에서 PBR의 방향과 표준을 확립했습니다. 그 후 주요 상용 엔진은 PBR 렌더링 파이프라인을 Disney 원칙의 BRDF를 기반으로 하는 자체 엔진에 통합했습니다. 다음은 Disney 원칙을 지원하는 주류 상용 엔진의 PBR 일정입니다.
Unreal Engine 4: "Unreal Engine 4의 실제 셰이딩", SIGGRAPH 2013
Unity 5: "Unity의 물리적 기반 셰이딩", GDC 2014
Frostbite: Frostbite를 PBR로 이동, SIGGRAPH 2014
Cry Engine 3.6: "Cry Engine의 물리적 기반 셰이딩", 2015
2013년에는 브라이언 카리스(Brian Karis)가 이끄는 신속 대응 UE 팀이 선두를 맡아 디즈니 원칙의 BRDF를 비즈니스 엔진에 통합한 최초의 선구자가 되었습니다. 통합 과정에서 UE는 절충, 단순화, 개선, 최적화를 거쳐 원래 오프라인 분야에서 사용되던 Disney BRDF를 UE의 실시간 렌더링 시스템에 훌륭하게 통합했습니다.
UE는 Cook-Torrance의 각 하위 항목 선택에 대해 다차원적인 비교를 하였고, 최종적으로 다음과 같은 모델 선택에 이르렀습니다.
첫 번째는 D항(정규분포함수)입니다. Blinn-Phong 모델과 비교할 때 GGX의 정규 분포 함수는 더 긴 꼬리를 가지며 이는 자연적인 물리적 현상에 더 부합하고 매우 효율적입니다. 최종적으로 UE에 의해 선택되었습니다:

GGX 정규 분포 함수의 공식:
UE에 해당하는 셰이더 구현 코드:
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
}두 번째는 Schlick 근사 방법을 사용하는 F 항(Fresnel)입니다.

Schlick의 프레넬 근사 방법의 고전적인 공식은 다음과 같습니다.
UE에 해당하는 셰이더의 대략적인 구현 코드:
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는 위 공식을 완전히 사용하지 않고 효율성을 위해 Pow 연산을 대체하기 위해 Spherical Gaussian 근사 방법을 사용합니다.
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) ) );
}마지막으로 Smith와 Schlick 조인트 마스킹 기능을 사용하는 G 용어(기하학적 마스킹)가 있습니다.

그들의 결합 공식은 다음과 같습니다.
해당 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은 Riemann 합계 방법을 사용하여 조명 적분을 근사화합니다. 조명 기능은 Monte Carlo와 중요도 샘플링을 결합합니다. 공식은 다음과 같습니다.
해당 셰이더 구현 코드:
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;
}추후 디퓨즈 항목이 2개 항목으로 분리됩니다. 이 부분은 다시 설명하지 않겠습니다. 관심 있는 어린이는 기본에서 심층까지 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 항목을 입체각을 기반으로 더 넓은 하이라이트 분포로 수정했습니다. 거칠기를 변경하는 공식은 다음과 같습니다.
이는 또한 새로운 문제로 이어집니다. 부드러운 개체가 더 이상 매끄럽게 보이지 않습니다(아래).

동시에, Area Light 내의 대표점(Representative Point)을 선택하여 전체 영역을 시뮬레이션하여 조명 및 음영에 직접 사용할 수 있습니다. 이는 충분히 작은 각도를 사용하여 반사광을 근사화할 수 있는 분포 범위가 넓은 지점에 적합한 선택입니다.
구체 조명의 경우 방사조도는 점 광원으로 처리되며(구가 반사광의 수평면에 있는 경우) 반사된 광선과 구를 사용하여 가장 가까운 점을 찾습니다.

구와 반사광 사이의 최소 거리를 계산하는 공식은 다음과 같습니다.
여기서
그러면 착색된 점의 에너지 적분은 다음 공식에 따라 계산될 수 있습니다.
여기서

GGX의 정규화 인자는
그 결과 매우 멋진 구형 조명 효과가 나타납니다.

Tube Light, Capsule Light의 알고리즘은 아래와 같습니다. 음영점에서 관형 광원의 양쪽 끝을 가리키는 벡터
UE는 이를 위해 많은 단순화와 근사치를 만들었습니다. 그 중 가장 중요한 것은 구형 광원과 유사하여 가장 작은 각도를 근사화하고 이를 최단 거리로 대체하는 것입니다.
그리고 이방성 GGX의 정규화 인자는
광원의 원점만 변경하고 에너지 보존 항만 적용하면 되므로 이러한 작업은 누적될 수 있습니다. 따라서 관형 광원에 대한 대략적인 컨볼루션 효과는 매우 좋습니다.

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)
{
(......)
// 의 라이트(Light).
FSortedLightSetSceneInfo SortedLightSet;
{
// 및 정렬 라이트(Light).
GatherAndSortLights(SortedLightSet);
// 계산/산출(Calculate)라이트(Light)(할당(Allocate)라이트(Light)).
ComputeLightGrid(RHICmdList, bComputeLightGrid, SortedLightSet);
}
(......)
}FSortedLightSetSceneInfo는 광원의 매우 중요한 기본 개념이며, 후속 광원 처리에는 이것이 포함됩니다. 이에 대한 정의와 기타 광원 관련 개념을 살펴보겠습니다.
// Engine\Source\Runtime\Engine\Public\PrimitiveSceneProxy.h
// 동적 라이트(Light)의 데이터 .
class FSimpleLightEntry
{
public:
FVector Color;
float Radius;
float Exponent;
float VolumetricScatteringIntensity;
bool bAffectTranslucency;
};
// 동적 라이트(Light)의 및 뷰(View)의 데이터 .
class FSimpleLightPerViewEntry
{
public:
FVector Position;
};
// 라이트(Light)인스턴스 의 view의 데이터 .
// , Instace는 Instance.
class FSimpleLightInstacePerViewIndexData
{
public:
uint32 PerViewIndex : 31; // 31
uint32 bHasPerViewData; // 1
};
// 동적 라이트(Light)리스트 .
class FSimpleLightArray
{
public:
// 동적 라이트(Light)리스트 , 및 뷰(View).
TArray<FSimpleLightEntry, TMemStackAllocator<>> InstanceData;
// 동적 라이트(Light)리스트 , 및 뷰(View).
TArray<FSimpleLightPerViewEntry, TMemStackAllocator<>> PerViewData;
// 라이트(Light)인스턴스 의 view의 데이터 리스트 .
TArray<FSimpleLightInstacePerViewIndexData, TMemStackAllocator<>> InstancePerViewDataIndices;
public:
// 페치/가져오기(Fetch)라이트(Light)의 뷰(View)의 데이터 .
inline const FSimpleLightPerViewEntry& GetViewDependentData(int32 LightIndex, int32 ViewIndex, int32 NumViews) const
{
if (InstanceData.Num() == PerViewData.Num())
{
return PerViewData[LightIndex];
}
else
{
// 계산/산출(Calculate)뷰(View).
FSimpleLightInstacePerViewIndexData PerViewIndex = InstancePerViewDataIndices[LightIndex];
const int32 PerViewDataIndex = PerViewIndex.PerViewIndex + ( PerViewIndex.bHasPerViewData ? ViewIndex : 0 );
return PerViewData[PerViewDataIndex];
}
}
};
// Engine\Source\Runtime\Renderer\Private\LightSceneInfo.h
// 을(를) 활용하여 정렬 의 라이트(Light)의 정보 .
struct FSortedLightSceneInfo
{
union
{
// FMeshDrawCommand의 정렬 , 데이터 의 라이트(Light)의 정렬 .
struct
{
// 라이트(Light)타입/유형 .
uint32 LightType : LightType_NumBits;
// 는 텍스처(Texture)구성(Configuration)
uint32 bTextureProfile : 1;
// 는 라이팅 함수 .
uint32 bLightFunction : 1;
// 는 계산/산출(Calculate)그림자(Shadow).
uint32 bShadowed : 1;
// 는 을(를) 활용하여 채널/패스(Pass).
uint32 bUsesLightingChannels : 1;
// 는 라이트(Light).
uint32 bIsNotSimpleLight : 1;
// 는 지원 레이턴시 타일드(Tiled)렌더링(Render).
uint32 bTiledDeferredNotSupported : 1;
// 는 지원 레이턴시 렌더링(Render).
uint32 bClusteredDeferredNotSupported : 1;
} Fields;
// 의 정렬 .
int32 Packed;
} SortKey;
// 의 라이트(Light)정보 .
const FLightSceneInfo* LightSceneInfo;
// 라이트(Light).
int32 SimpleLightIndex;
// 라이트(Light)함수 .
explicit FSortedLightSceneInfo(const FLightSceneInfo* InLightSceneInfo)
: LightSceneInfo(InLightSceneInfo),
SimpleLightIndex(-1)
{
SortKey.Packed = 0;
SortKey.Fields.bIsNotSimpleLight = 1;
}
// 라이트(Light)함수 .
explicit FSortedLightSceneInfo(int32 InSimpleLightIndex)
: LightSceneInfo(nullptr),
SimpleLightIndex(InSimpleLightIndex)
{
SortKey.Packed = 0;
SortKey.Fields.bIsNotSimpleLight = 0;
}
};
struct FSortedLightSetSceneInfo
{
// 라이트(Light).
int SimpleLightsEnd;
// 타일드(Tiled)라이팅 .
int TiledSupportedEnd;
// 라이팅 .
int ClusteredSupportedEnd;
// 개 그림자(Shadow)의 라이트(Light).
int AttenuationLightStart;
// 라이트(Light)리스트 .
FSimpleLightArray SimpleLights;
// 의 라이트(Light)리스트 .
TArray<FSortedLightSceneInfo, SceneRenderingAllocator> SortedLights;
};주목해야 할 것은 FSortedLightSceneInfo의 광원 정렬 키 값입니다. 우선순위가 가장 높은 속성은 클러스터링, 차단 및 단순 광원과 관련되며, 그 다음으로는 광원 채널, 그림자, 조명 기능 및 조명 구성이 있습니다. 가장 낮은 우선순위는 광원 유형입니다.
이 정렬의 기본은 다음과 같습니다. 클러스터링 및 차단은 고급 광원 자르기와 같은 작업을 통해 음영 소비 및 명령 수를 크게 줄일 수 있으며 가장 높은 위치에 배치됩니다. 간단한 광원의 조명 논리는 훨씬 간단하고 명령의 수도 훨씬 적습니다. 모든 단순 광원은 클러스터 및 블록으로 렌더링될 수 있으므로 클러스터링 및 차단을 따릅니다. 나머지는 조명 채널이 켜져 있는지 여부에 따라 단순하지 않은 조명의 특성입니다. 또한 많은 논리에 직접적인 영향을 미칩니다. 라이트 채널을 켜면 렌더링 효율에 큰 영향을 주기 때문에 단순하지 않은 광원 중에서 가장 먼저 배치됩니다. 그림자가 켜져 있는지 여부도 렌더링 성능에 큰 영향을 미칩니다. 그림자가 켜져 있으면 그림자 맵과 감쇠 텍스처를 그리기 위해 여러 패스가 추가되고 렌더링 텍스처 전환이 증가하므로 순위가 합리적입니다. 다음의 핵심값도 같은 방법으로 추론할 수 있다.
5.4.3.2 조명 수집 및 정렬
이 섹션에서는 광원을 수집하고 정렬하기 위한 GatherAndSortLights 인터페이스를 분석합니다.
// Engine\Source\Runtime\Renderer\Private\LightRendering.cpp
void FDeferredShadingSceneRenderer::GatherAndSortLights(FSortedLightSetSceneInfo& OutSortedLights)
{
if (bAllowSimpleLights)
{
// 라이트(Light), .
GatherSimpleLights(ViewFamily, Views, OutSortedLights.SimpleLights);
}
FSimpleLightArray &SimpleLights = OutSortedLights.SimpleLights;
TArray<FSortedLightSceneInfo, SceneRenderingAllocator> &SortedLights = OutSortedLights.SortedLights;
// SortedLights라이트(Light) 및 라이트(Light), 정렬 .
SortedLights.Empty(Scene->Lights.Num() + SimpleLights.InstanceData.Num());
bool bDynamicShadows = ViewFamily.EngineShowFlags.DynamicShadows && GetShadowQuality() > 0;
// 라이트(Light)리스트 .
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)
{
// 라이트(Light)는 개 vie.
for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
{
if (LightSceneInfo->ShouldRenderLight(Views[ViewIndex]))
{
// 생성(Create)FSortedLightSceneInfo인스턴스 .
FSortedLightSceneInfo* SortedLightInfo = new(SortedLights) FSortedLightSceneInfo(LightSceneInfo);
// 검사/감지(Detect)그림자(Shadow) 또는 라이팅 함수 .
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();
// 라이트(Light).
SortedLightInfo->SortKey.Fields.bIsNotSimpleLight = 1;
// 지원 타일드(Tiled) 또는 의 조건 : 을(를) 활용하여 라이트(Light)의 , 또는 모멘트(Moment).
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;
// 는 지원 타일드(Tiled)렌더링(Render).
SortedLightInfo->SortKey.Fields.bTiledDeferredNotSupported = !(bTiledOrClusteredDeferredSupported && LightSceneInfo->Proxy->IsTiledDeferredLightingSupported());
// 는 지원 렌더링(Render).
SortedLightInfo->SortKey.Fields.bClusteredDeferredNotSupported = !bTiledOrClusteredDeferredSupported;
break;
}
}
}
}
// 추가(Add)라이트(Light).
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;
// 라이트(Light).
SortedLightInfo->SortKey.Fields.bIsNotSimpleLight = 0;
// 라이트(Light)로써 타일드(Tiled) 또는 렌더링(Render).
SortedLightInfo->SortKey.Fields.bTiledDeferredNotSupported = 0;
SortedLightInfo->SortKey.Fields.bClusteredDeferredNotSupported = 0;
}
// 라이트(Light)정렬 , 그림자(Shadow)라이팅 함수 의 라이트(Light), 로써 렌더링(Render)텍스처(Texture).
struct FCompareFSortedLightSceneInfo
{
FORCEINLINE bool operator()( const FSortedLightSceneInfo& A, const FSortedLightSceneInfo& B ) const
{
return A.SortKey.Packed < B.SortKey.Packed;
}
};
SortedLights.Sort( FCompareFSortedLightSceneInfo() );
// 초기화(Initialize).
OutSortedLights.SimpleLightsEnd = SortedLights.Num();
OutSortedLights.TiledSupportedEnd = SortedLights.Num();
OutSortedLights.ClusteredSupportedEnd = SortedLights.Num();
OutSortedLights.AttenuationLightStart = SortedLights.Num();
// 순회(Traverse/Iterate)렌더링(Render)의 라이트(Light), 타일드(Tiled) 및 그림자(Shadow)라이트(Light)의 범위 .
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())
{
// 라이트(Light)의 .
OutSortedLights.SimpleLightsEnd = LightIndex;
}
if (SortedLightInfo.SortKey.Fields.bTiledDeferredNotSupported && OutSortedLights.TiledSupportedEnd == SortedLights.Num())
{
// 타일드(Tiled)라이트(Light)의 .
OutSortedLights.TiledSupportedEnd = LightIndex;
}
if (SortedLightInfo.SortKey.Fields.bClusteredDeferredNotSupported && OutSortedLights.ClusteredSupportedEnd == SortedLights.Num())
{
// 라이트(Light)의 .
OutSortedLights.ClusteredSupportedEnd = LightIndex;
}
if (bDrawShadows || bDrawLightFunction || bLightingChannels)
{
// 개 그림자(Shadow)의 라이트(Light).
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)
(......)
{
// 는 지원 타일드(Tiled)렌더링(Render).
bTiledDeferredLightingSupported = (SourceLength == 0.0f);
}
}즉, 길이가 0인 점광원만 블록 렌더링을 지원하며, 다른 유형 및 조건의 광원은 지원하지 않습니다.
광원 길이가 0보다 큰 점 광원은 구형 조명 모델이 되며, 이는 더 이상 단순한 조명 모델에 적합하지 않으며 블록으로 렌더링할 수 없습니다.
GatherSimpleLights의 기능은 장면에 보이는 기본 요소로부터 간단한 광원을 수집하는 것입니다.
void FSceneRenderer::GatherSimpleLights(const FSceneViewFamily& ViewFamily, const TArray<FViewInfo>& Views, FSimpleLightArray& SimpleLights)
{
TArray<const FPrimitiveSceneInfo*, SceneRenderingAllocator> PrimitivesWithSimpleLights;
// 로부터 라이트(Light)의 뷰(View) 내에서 .
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];
// ※ 주의: 추가(Add)을(를) 활용하여 의 는 AddUnique, 로써 개 라이트(Light)리스트 내에서 .
PrimitivesWithSimpleLights.AddUnique(PrimitiveSceneInfo);
}
}
// 로부터 내에서 라이트(Light).
for (int32 PrimitiveIndex = 0; PrimitiveIndex < PrimitivesWithSimpleLights.Num(); PrimitiveIndex++)
{
const FPrimitiveSceneInfo* Primitive = PrimitivesWithSimpleLights[PrimitiveIndex];
Primitive->Proxy->GatherSimpleLights(ViewFamily, SimpleLights);
}
}단순 광원을 수집하는 경우 각 기본 장면이 나타내는 GatherSimpleLights 인터페이스가 호출됩니다. 현재 이 인터페이스를 구현하는 주요 기본 장면 대표는 다음과 같습니다.
-FNiagaraSceneProxy
-FNiagaraRendererLights
FDynamicSpriteEmitterData
FParticleSystemSceneProxy
모두 캐스케이드 파티클, 나이아가라 파티클과 관련된 에이전트이기 때문에 파티클 이펙트 모듈에서는 단순 광원만 사용하고 있음을 유추할 수 있습니다.
5.4.3.3 ComputeLightGrid
ComputeLightGrid의 기능은 로컬 광원과 반사 프로브를 프러스텀 공간의 3D 그리드에 클립하고 각 뷰와 관련된 광원 목록과 그리드를 구축하는 것입니다.
// Engine\Source\Runtime\Renderer\Private\LightGridInjection.cpp
void FDeferredShadingSceneRenderer::ComputeLightGrid(FRHICommandListImmediate& RHICmdList, bool bNeedLightGrid, FSortedLightSetSceneInfo &SortedLightSet)
{
// 라이트(Light) 또는 지원 SM5 및 로써 , 반환(Return).
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을(를) 활용하여 렌더링(Render).
bool bAnyViewUsesForwardLighting = false;
for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
{
const FViewInfo& View = Views[ViewIndex];
bAnyViewUsesForwardLighting |= View.bTranslucentSurfaceLighting || ShouldRenderVolumetricFog() || View.bHasSingleLayerWaterMaterial;
}
// 는 클리핑/클램핑(Clipping)까지 의 라이트(Light).
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;
// 클리핑/클램핑(Clipping)라이트(Light).
if (bCullLightsToGrid)
{
SimpleLightsEnd = SortedLightSet.SimpleLightsEnd;
// 1. 라이트(Light).
if (SimpleLightsEnd > 0)
{
const FSimpleLightArray &SimpleLights = SortedLightSet.SimpleLights;
const FFloat16 SimpleLightSourceLength16f = FFloat16(0);
FLightingChannels SimpleLightLightingChannels;
// 라이트(Light)적용 채널/패스(Pass).
SimpleLightLightingChannels.bChannel0 = SimpleLightLightingChannels.bChannel1 = SimpleLightLightingChannels.bChannel2 = true;
const uint32 SimpleLightLightingChannelMask = GetLightingChannelMaskForStruct(SimpleLightLightingChannels);
// 을(를) 활용하여 의 라이트(Light)리스트 , 의 범위 .
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);
// 라이트(Light)그림자(Shadow).
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. 추가(Add)타입/유형 의 라이트(Light), 지원 렌더링(Render)의 .
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;
}
// 정적 그림자(Shadow)을(를) 활용하여 ShadowMapChannel, 동적 그림자(Shadow)라이트(Light)감쇠(Falloff/Attenuation)텍스처(Texture)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;
// 처리(Process)로컬 라이트(Light).
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);
}
// 라이트(Light).
else if (SortedLightInfo.SortKey.Fields.LightType == LightType_Directional && ViewFamily.EngineShowFlags.DirectionalLights)
{
(......)
}
}
}
}
const int32 NumLocalLightsFinal = ForwardLocalLightData.Num();
if (ForwardLocalLightData.Num() == 0)
{
ForwardLocalLightData.AddZeroed();
}
// 갱신(Update)라이트(Light)의 데이터 까지 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;
// 생성(Create)라이트(Light)데이터 의 Uniform Buffer.
View.ForwardLightingResources->ForwardLightDataUniformBuffer = TUniformBufferRef<FForwardLightData>::CreateUniformBufferImmediate(ForwardLightData, UniformBuffer_SingleFrame);
// 을(를) 활용하여 RDG 및 Compute Shader클리핑/클램핑(Clipping)로컬 라이트(Light) 및 반사(Reflection).
// 스레드 그룹(Thread Group).
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"));
// 생성(Create)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);
// 라이트(Light)shader.
TShaderMapRef<FLightGridInjectionCS> ComputeShader(View.ShaderMap, PermutationVector);
// 처리(Process)라이트(Light).
if (GLightLinkedListCulling != 0)
{
// UAV.
AddClearUAVPass(GraphBuilder, PassParameters->RWStartOffsetGrid, 0xFFFFFFFF);
AddClearUAVPass(GraphBuilder, PassParameters->RWNextCulledLightLink, 0);
AddClearUAVPass(GraphBuilder, GraphBuilder.CreateUAV(NextCulledLightDataBuffer, PF_R32_UINT), 0);
// 추가(Add)라이트(Light)Pass.
FComputeShaderUtils::AddPass(GraphBuilder, RDG_EVENT_NAME("LightGridInject:LinkedList"), ComputeShader, PassParameters, NumGroups);
{
// 라이트(Light)압축/패킹(Packing)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);
// 추가(Add)라이트(Light)압축/패킹(Packing)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는 광원 그리드를 처리하는 데 사용됩니다. 사용되는 셰이더는 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);
}
};해당 셰이더 코드:
// 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까지 의 뷰(View)뎁스 .
float ComputeCellNearViewDepthFromZSlice(uint ZSlice)
{
// ※ 주의: 의 뎁스 는 스레드(Thread)의 , 는 , 의 , 뎁스 범위 .
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;
}
// 에 따라 계산/산출(Calculate)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;
}
// 및 교차(Intersection).
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의 . 생성(Create)개 의 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);
}
// 라이트(Light).
[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;
// 계산/산출(Calculate)바운딩 박스(AABB).
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;
// 순회(Traverse/Iterate)라이트(Light), 및 교차(Intersection)의 라이트(Light)기록/쓰기(Write)까지 리스트 내에서 .
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;
// 라이트(Light) 및 의 AABB교차(Intersection).
bPassSpotlightTest = !IsAabbOutsideInfiniteAcuteConeApprox(ViewSpaceLightPosition, ViewSpaceLightDirection, TanConeAngle, ViewTileCenter, ViewTileExtent);
}
}
// 만약 교차(Intersection), 기록/쓰기(Write)라이트(Light)정보 .
if (bPassSpotlightTest)
#endif // REFINE_SPOTLIGHT_BOUNDS
{
#if USE_LINKED_CULL_LIST
uint NextLink;
InterlockedAdd(RWNextCulledLightLink[0], 1U, NextLink);
// 저장(Save)라이트(Light).
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;
// 저장(Save)라이트(Light).
if (CulledLightIndex < ForwardLightData.MaxCulledLightsPerCell)
{
RWCulledLightDataGrid[GridIndex * ForwardLightData.MaxCulledLightsPerCell + CulledLightIndex] = LocalLightIndex;
}
#endif
}
}
}
// 처리(Process)반사(Reflection).
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;
// 압축/패킹(Packing)의 역방향(Inverse)리스트 .
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];
}
}
// 라이트(Light)압축/패킹(Packing).
[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;
// 압축/패킹(Packing)라이트(Light)
CompactReverseLinkedList(GridIndex, ForwardLightData.NumLocalLights);
// 압축/패킹(Packing)반사(Reflection).
CompactReverseLinkedList(ForwardLightData.NumGridCells + GridIndex, ForwardLightData.NumReflectionCaptures);
}
}그리드에 광원을 저장하는 것은 이전 기사에서 언급한 Volume Tiled Forward Rendering과 다소 유사합니다.

볼륨 라이트 그리드 데이터 구조 다이어그램.
5.5 라이팅패스
이 섹션에서는 주로 LightingPass의 렌더링 프로세스, 렌더링 상태, 셰이더 로직 등에 대해 자세히 설명합니다.
5.5.1 LightingPass 렌더링 프로세스
이전 기사에서는 LightingPass의 렌더링 프로세스와 일부 핵심 로직도 다루었습니다. 이 섹션에서는 이를 간략하게 검토합니다. FDeferredShadingSceneRenderer::Render의 LightingPass가 BasePass 뒤에 있습니다.
void FDeferredShadingSceneRenderer::Render(FRHICommandListImmediate& RHICmdList)
{
(......)
// 렌더링(Render)Base Pass.
RenderBasePass(RHICmdList, ...);
(......)
// 렌더링(Render)라이트(Light).
RenderLights(RHICmdList, ...);
(......)
}RenderLights 및 RenderLight의 렌더링 논리를 다시 간략하게 검토해 보겠습니다.
// Engine\Source\Runtime\Renderer\Private\LightRendering.cpp
// 렌더링(Render)라이트(Light).
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;
// 그림자(Shadow)의 라이트(Light).
const int32 AttenuationLightStart = SortedLightSet.AttenuationLightStart;
const int32 SimpleLightsEnd = SortedLightSet.SimpleLightsEnd;
// 라이팅
{
SCOPED_DRAW_EVENT(RHICmdList, DirectLighting);
FSceneRenderTargets& SceneContext = FSceneRenderTargets::Get(RHICmdList);
(......)
// 그림자(Shadow)라이팅
if(ViewFamily.EngineShowFlags.DirectLighting)
{
SCOPED_DRAW_EVENT(RHICmdList, NonShadowedLights);
// 그림자(Shadow)의 라이트(Light).
int32 StandardDeferredStart = SortedLightSet.SimpleLightsEnd;
bool bRenderSimpleLightsStandardDeferred = SortedLightSet.SimpleLights.InstanceData.Num() > 0;
// 레이턴시 라이팅 .
if (ShouldUseClusteredDeferredShading() && AreClusteredLightsInLightGrid())
{
StandardDeferredStart = SortedLightSet.ClusteredSupportedEnd;
bRenderSimpleLightsStandardDeferred = false;
// 추가(Add)레이턴시 렌더링(Render)Pass.
AddClusteredDeferredShadingPass(RHICmdList, SortedLightSet);
}
// 타일드(Tiled)레이턴시 라이팅 .
else if (CanUseTiledDeferred())
{
(......)
if (ShouldUseTiledDeferred(SortedLightSet.TiledSupportedEnd) && !bAnyViewIsStereo)
{
StandardDeferredStart = SortedLightSet.TiledSupportedEnd;
bRenderSimpleLightsStandardDeferred = false;
// 렌더링(Render)타일드(Tiled)레이턴시 라이팅 .
RenderTiledDeferredLighting(RHICmdList, SortedLights, SortedLightSet.SimpleLightsEnd, SortedLightSet.TiledSupportedEnd, SimpleLights);
}
}
// 라이팅 .
if (bRenderSimpleLightsStandardDeferred)
{
SceneContext.BeginRenderingSceneColor(RHICmdList, ESimpleRenderTargetMode::EExistingColorAndDepth, FExclusiveDepthStencil::DepthRead_StencilWrite);
// 렌더링(Render)라이팅 .
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;
// 렌더링(Render)그림자(Shadow)라이팅 .
RenderLight(RHICmdList, LightSceneInfo, nullptr, nullptr, false, false);
}
SceneContext.FinishRenderingSceneColor(RHICmdList);
}
(......)
}
(......)
// 그림자(Shadow)의 라이팅
{
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;
// 렌더링(Render)그림자(Shadow)의 라이트(Light) 및 라이팅 함수 라이트(Light).
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);
(......)
// 처리(Process)그림자(Shadow).
else // (OcclusionType == FOcclusionType::Shadowmap)
{
for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
{
const FViewInfo& View = Views[ViewIndex];
View.HeightfieldLightingViewInfo.ClearShadowing(View, RHICmdList, LightSceneInfo);
}
// 그림자(Shadow).
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);
}
// 처리(Process)라이팅 함수 (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);
}
}
(......)
// 렌더링(Render)레이턴시 라이팅 .
{
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)
{
// 렌더링(Render)그림자(Shadow)의 라이트(Light).
RenderLight(RHICmdList, &LightSceneInfo, LightShadowMaskTexture, InHairVisibilityViews, false, true);
}
SceneContext.FinishRenderingSceneColor(RHICmdList);
(......)
}
}
}
}
}
// 렌더링(Render)개 라이트(Light).
void FDeferredShadingSceneRenderer::RenderLight(FRHICommandList& RHICmdList, const FLightSceneInfo* LightSceneInfo, IPooledRenderTarget* ScreenShadowMaskTexture, const FHairStrandsVisibilityViews* InHairVisibilityViews, bool bRenderOverlap, bool bIssueDrawEvent)
{
FGraphicsPipelineStateInitializer GraphicsPSOInit;
RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit);
// 설정(Set)블렌딩(Blending)상태 로 , 로써 라이트(Light)의 라이팅 까지 텍스처(Texture) 내에서 .
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();
// 순회(Traverse/Iterate)view, 라이트(Light)개 view드로우/렌더(Draw)회 .
for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
{
FViewInfo& View = Views[ViewIndex];
// 라이트(Light)뷰(View) 내에서 유효(Valid).
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);
(......)
// 드로우/렌더(Draw).
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();
// 설정(Set)레이턴시 라이트(Light)의 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);
// 에 의해 는 , 스크린 스페이스의 , 을(를) 활용하여 풀스크린(Full Screen)범위 의 모멘트(Moment)드로우/렌더(Draw).
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 // 로컬 라이트(Light)
{
// 는 활성화(Enable)뎁스 바운딩 박스(AABB)(DBT).
GraphicsPSOInit.bDepthBounds = GSupportsDepthBoundsTest && GAllowDepthBoundsTest != 0;
TShaderMapRef<TDeferredLightVS<true> > VertexShader(View.ShaderMap);
SetBoundingGeometryRasterizerAndDepthState(GraphicsPSOInit, View, LightBounds);
// 설정(Set)레이턴시 라이트(Light)의 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);
// 뎁스 바운딩 박스(AABB)(DBT)오직 그림자(Shadow)의 라이트(Light) 내에서 유효(Valid), 로써 유효(Valid)컬링 뎁스 범위 의 픽셀(Pixel).
if (GraphicsPSOInit.bDepthBounds)
{
// UE4을(를) 활용하여 역방향(Inverse)의 뎁스 (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;
}
// 설정(Set)뎁스 바운딩 박스(AABB)파라미터 .
RHICmdList.SetDepthBounds(FarDepth, NearDepth);
}
// 포인트 라이트(Light) 또는 을(를) 활용하여 드로우/렌더(Draw).
if( LightSceneInfo->Proxy->GetLightType() == LightType_Point ||
LightSceneInfo->Proxy->GetLightType() == LightType_Rect )
{
StencilingGeometry::DrawSphere(RHICmdList);
}
// 을(를) 활용하여 드로우/렌더(Draw).
else if (LightSceneInfo->Proxy->GetLightType() == LightType_Spot)
{
StencilingGeometry::DrawCone(RHICmdList);
}
}
}
}5.5.2 LightingPass 렌더링 상태
이 섹션에서는 LightingPass 렌더링에 사용되는 다양한 렌더링 상태, 셰이더 바인딩 및 드로잉 매개변수에 대해 설명합니다.
단일 광원을 렌더링할 때 먼저 혼합 상태를 살펴보십시오.
GraphicsPSOInit.BlendState = TStaticBlendState<CW_RGBA, BO_Add, BF_One, BF_One, BO_Add, BF_One, BF_One>::GetRHI();이는 광원의 색상과 알파가 중첩된 상태라는 뜻이며, 공식은 다음과 같습니다.
이는 물리학의 상식에도 부합한다. 단일 표면 객체가 여러 광원에 의해 조명되는 경우 색상과 강도가 모두 중첩되어야 합니다.
다른 렌더링 상태는 다음과 같습니다(병렬 조명을 예로 사용).
렌더링 상태 가치 설명
기본 유형 PT_TriangleList 기본 유형은 삼각형입니다.
b깊이 경계 거짓 다양한 유형의 광원의 깊이 경계 값이 다릅니다.
래스터라이저 상태 StaticRasterizerState<FM_Solid, CM_None> 뒷면 또는 앞면 자르기가 꺼진 솔리드 렌더링.
깊이스텐실상태 TStaticDepthStencilState<false, CF_Always> 깊이 쓰기를 끄면 깊이 비교 기능이 항상 테스트를 통과합니다.
버텍스셰이더 TDeferredLightVS 비이미시브을 위한 버텍스 셰이더.
픽셀셰이더 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);
}
// 설정(Set)파라미터 .
void SetParameters(FRHICommandList& RHICmdList, const FViewInfo& View, const FLightSceneInfo* LightSceneInfo)
{
// 설정(Set)뷰(View)Uniform Buffer.
FGlobalShader::SetParameters<FViewUniformShaderParameters>(RHICmdList, RHICmdList.GetBoundVertexShader(), View.ViewUniformBuffer);
StencilingGeometryParameters.Set(RHICmdList, this, View, LightSceneInfo);
}
// 설정(Set)라이트(Light)파라미터 .
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
// 라이트(Light)의 픽셀(Pixel)셰이딩 .
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)
{
// 바인딩(Bind)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:
// 설정(Set)라이트(Light)파라미터 .
void SetParameters(
FRHICommandList& RHICmdList,
const FSceneView& View,
const FLightSceneInfo* LightSceneInfo,
IPooledRenderTarget* ScreenShadowMaskTexture,
FRenderLightParams* RenderLightParams)
{
FRHIPixelShader* ShaderRHI = RHICmdList.GetBoundPixelShader();
// 설정(Set)라이트(Light)파라미터 .
SetParametersBase(RHICmdList, ShaderRHI, View, ScreenShadowMaskTexture, LightSceneInfo->Proxy->GetIESTextureResource(), RenderLightParams);
// 설정(Set)레이턴시 라이트(Light)의 파라미터 .
SetDeferredLightParameters(RHICmdList, ShaderRHI, GetUniformBufferParameter<FDeferredLightUniformStruct>(), LightSceneInfo, View);
}
// 설정(Set)라이트(Light)파라미터 .
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);
// 라이트(Light)감쇠(Falloff/Attenuation).
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텍스처(Texture).
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
);
}
// 채널/패스(Pass)텍스처(Texture).
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 셰이더
장면, 광원, 뷰 및 셰이더 호출 간의 중첩 관계는 이전 기사에서 지적되었습니다.
foreach(scene in scenes)
{
foreach(light in lights)
{
foreach(view in views)
{
RenderLight(); // 회 호출(Call)렌더링(Render)라이트(Light)실행(Execute)DeferredLightVertexShaders 및 DeferredLightPixelShaders의 코드 .
}
}
}DeferredLightVertexShader 및 DeferredLightPixelShader가 실행되는 횟수는 다음과 같습니다.
이는 또한 CPU와 GPU 간의 데이터 교환을 줄이고, 렌더링 상태 전환을 줄이고, 캐시 적중률을 향상시키고, 인스턴스화 확률을 높이고, Draw Call을 줄일 수 있는 광원과 그림자 정렬의 필요성을 보여줍니다. 그러나 실시간 게임의 경우 대부분의 경우 장면 수와 뷰 수는 1개입니다. 즉, VS 및 PS의 실행 수는 광원 수에만 관련됩니다.
다음 두 섹션에서는 LightingPass의 VS 및 PS 셰이더 로직을 분석합니다.
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
// 을(를) 활용하여 풀스크린(Full Screen)렌더링(Render)의 VS.
void DirectionalVertexMain(
in float2 InPosition : ATTRIBUTE0,
in float2 InUV : ATTRIBUTE1,
out float2 OutTexCoord : TEXCOORD0,
out float3 OutScreenVector : TEXCOORD1,
out float4 OutPosition : SV_POSITION
)
{
// 드로우/렌더(Draw)모멘트(Moment).
DrawRectangle(float4(InPosition.xy, 0, 1), InUV, OutPosition, OutTexCoord);
// 출력위치 변환(Transform/Convert)벡터 .
OutScreenVector = mul(float4(OutPosition.xy, 1, 0), View.ScreenToTranslatedWorld).xyz;
}
#endif
#if FEATURE_LEVEL >= FEATURE_LEVEL_SM4 && SHADER_RADIAL_LIGHT == 1
// 을(를) 활용하여 드로우/렌더(Draw)포인트 라이트(Light) 또는 의 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)의 공식 개 의 .
// 유효(Valid),의 엣지(Edge), 로부터 포인트 .
const float SliceRadius = DistanceDownConeDirection * TanConeAngle * InvCosRadiansPerSide;
// 의 로컬 생성(Create)개 위치 ,XY개 ,Z.
const float3 LocalPosition = float3(ZRadius * SliceIndex / (float)(NumSlices - 1), SliceRadius * sin(CurrentAngle), SliceRadius * cos(CurrentAngle));
// 변환(Transform/Convert)까지 적용 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);
// 유효(Valid),의 엣지(Edge), 로부터 포인트 .
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 입력의 ATTRIBUTE 값은 모두 0이고, 오른쪽 상단의 VS 출력은 실제로는 정상적인 값입니다.
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;
};
// 페치/가져오기(Fetch)파라미터 .
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;
}
// 생성(Create)설정(Set)레이턴시 라이팅 데이터 구조체 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;
}
// 채널/패스(Pass)텍스처(Texture).
Texture2D<uint> LightingChannelsTexture;
// 페치/가져오기(Fetch)채널/패스(Pass).
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
// 레이턴시 픽셀(Pixel)셰이딩 .
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;
// 계산/산출(Calculate)라이트(Light)의 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
// 페치/가져오기(Fetch)스크린 스페이스의 데이터 , GBuffer 및 AO.
FScreenSpaceData ScreenSpaceData = GetScreenSpaceData(InputParams.ScreenUV);
// 오직 ShadingModelID로 0의 픽셀(Pixel)는 레이턴시 셰이딩 .
BRANCH if( ScreenSpaceData.GBuffer.ShadingModelID > 0
// 검사/감지(Detect)채널/패스(Pass)는 .
#if USE_LIGHTING_CHANNELS
&& (GetLightingChannelMask(InputParams.ScreenUV) & DeferredLightUniforms.LightingChannelMask)
#endif
)
{
const float OpaqueVisibility = 1.0f;
// 계산/산출(Calculate)씬 뎁스(SceneDepth).
const float SceneDepth = CalcSceneDepth(InputParams.ScreenUV);
// 페치/가져오기(Fetch)데이터 .
const FDerivedParams DerivedParams = GetDerivedParams(InputParams, SceneDepth);
// 설정(Set)레이턴시 라이트(Light)데이터 .
FDeferredLightData LightData = SetupLightDataForStandardDeferred();
// 페치/가져오기(Fetch)지터링 .
float Dither = InterleavedGradientNoise(InputParams.PixelPos, View.StateFrameIndexMod8 );
// 로부터 라이트(Light)의 텍스처(Texture)페치/가져오기(Fetch)모멘트(Moment)텍스처(Texture).
FRectTexture RectTexture = InitRectTexture(DeferredLightUniforms.SourceTexture);
float SurfaceShadow = 1.0f;
// 계산/산출(Calculate)동적 라이팅 .
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);
// 계산/산출(Calculate)라이트(Light)구성(Configuration)의 감쇠(Falloff/Attenuation)계수.
const float Attenuation = ComputeLightProfileMultiplier(DerivedParams.WorldPosition, DeferredLightUniforms.Position, -DeferredLightUniforms.Direction, DeferredLightUniforms.Tangent);
// 계산/산출(Calculate)컬러 .
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
// 페치/가져오기(Fetch)스크린 스페이스의 데이터 .
FScreenSpaceData GetScreenSpaceData(float2 UV, bool bGetNormalizedNormal = true)
{
FScreenSpaceData Out;
// 페치/가져오기(Fetch)GBuffer데이터 .
Out.GBuffer = GetGBufferData(UV, bGetNormalizedNormal);
// 페치/가져오기(Fetch)스크린 스페이스AO.
float4 ScreenSpaceAO = Texture2DSampleLevel(SceneTexturesStruct.ScreenSpaceAOTexture, SceneTexturesStruct.ScreenSpaceAOTextureSampler, UV, 0);
// ※ 주의: AO오직 r채널/패스(Pass).
Out.AmbientOcclusion = ScreenSpaceAO.r;
return Out;
}
// 페치/가져오기(Fetch)스크린 스페이스UV의 GBuffer데이터 .
FGBufferData GetGBufferData(float2 UV, bool bGetNormalizedNormal = true)
{
(......)
// 로부터 GBuffer텍스처(Texture) 내에서 샘플 수.
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);
// 텍스처(Texture)까지 의 구조체 .
return DecodeGBufferData(GBufferA, GBufferB, GBufferC, GBufferD, GBufferE, GBufferF, GBufferVelocity, CustomNativeDepth, CustomStencil, SceneDepth, bGetNormalizedNormal, CheckerFromSceneColorUV(UV));
}
// 텍스처(Texture)까지 의 구조체 .
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;
// 노멀(Normal).
GBuffer.WorldNormal = DecodeNormal( InGBufferA.xyz );
if(bGetNormalizedNormal)
{
GBuffer.WorldNormal = normalize(GBuffer.WorldNormal);
}
// , , , 러프니스(Roughness).
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 텍스처에서 데이터를 샘플링한 다음 2차 처리를 수행하고 이를 FGBufferData에 저장한 다음 FGBufferData의 인스턴스를 FScreenSpaceData에 저장하는 것입니다. 이를 통해 후속 조명 계산에 직접 액세스할 수 있으며, 여러 텍스처 샘플링으로 인해 GPU와 비디오 메모리 사이에 IO 병목 현상이 발생하고 GPU 캐시 적중률이 감소하는 것을 방지할 수 있습니다.
첫 번째는 복잡한 조명 계산 논리에 들어가는 매우 복잡한 GetDynamicLighting입니다.
// Engine\Shaders\Private\DeferredLightingCommon.ush
// 계산/산출(Calculate)동적 라이팅 .
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;
}
// 계산/산출(Calculate)동적 라이팅 , 디퓨즈(Diffuse) 및 .
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;
// 페치/가져오기(Fetch)라이트(Light)의 감쇠(Falloff/Attenuation).
if (LightData.bRadialLight)
{
LightMask = GetLocalLightAttenuation( WorldPosition, LightData, ToLight, L );
}
LightAccumulator.EstimatedCost += 0.3f; // running the PixelShader at all has a cost
BRANCH
// 오직 계산/산출(Calculate)유효(Valid)의 테이블 .
if( LightMask > 0 )
{
// 처리(Process)테이블 그림자(Shadow).
FShadowTerms Shadow;
Shadow.SurfaceShadow = AmbientOcclusion;
Shadow.TransmissionShadow = 1;
Shadow.TransmissionThickness = 1;
Shadow.HairTransmittance.Transmittance = 1;
Shadow.HairTransmittance.OpaqueVisibility = 1;
// 계산/산출(Calculate)테이블 그림자(Shadow)데이터 .
GetShadowTerms(GBuffer, LightData, WorldPosition, L, LightAttenuation, Dither, Shadow);
SurfaceShadow = Shadow.SurfaceShadow;
LightAccumulator.EstimatedCost += 0.3f; // add the cost of getting the shadow terms
BRANCH
// 만약 그림자(Shadow)의 라이팅 및 투과 및 >0계산/산출(Calculate)라이팅 .
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 ) // 모멘트(Moment)
{
FRect Rect = GetRect( ToLight, LightData );
// 모멘트(Moment)라이팅 .
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
// 처리(Process)감쇠(Falloff/Attenuation), N 및 L의 포인트 .
if( Capsule.Length > 0 ) // 만약 는 유효(Valid), 이면 오직 (공식 5.4.2.2)
{
LineIrradiance( N, Capsule.LightPos[0], Capsule.LightPos[1], Capsule.DistBiasSqr, LineCosSubtended, Falloff, NoL );
}
else // 라이트(Light)개 포인트
{
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, 조정(Adjust)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;
// 조정(Adjust)유효(Valid)의 라이트(Light).
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:
// 기본값(Default)라이팅
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:
// 회 테이블 산란 구성(Configuration)
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
// 기본값(Default)라이팅 모델 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;
// 을(를) 활용하여 계산/산출(Calculate)의 디퓨즈(Diffuse).
Lighting.Diffuse = AreaLight.FalloffColor * (Falloff * NoL) * Diffuse_Lambert( GBuffer.DiffuseColor );
// 을(를) 활용하여 모멘트(Moment)의 GGX의 LTC계산/산출(Calculate)의 반사(Reflection).
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 논문에서 제안되었습니다. 핵심 아이디어는 먼저 탄젠트, 탄젠트 및 광원 방향을 늘리고 조정하는 것입니다.

최대화된

먼저 Karis 근사 방법을 사용하여 첫 번째 항을 추정한 다음 Newton 반복 방법을 사용하여 두 번째 항을 찾은 다음 이를 사용하여

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 );
}
}
}표면의 다양한 벡터의 내적을 조정한 후 Lambert를 사용하여 디퓨즈를 계산하고 직사각형 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 논문에서 나온 것입니다. 핵심 아이디어는 선형 사전 변환을 통해 임의 모양의 표면 광원의 BRDF의 러프니스, 이방성 및 경사 특성을 근사화하는 것입니다.

또한 효율성은 구, 반구 및 고정된 코사인 조명 모델의 효율성과 비슷하며 Feng의 조명 모델보다 빠릅니다.

그런 다음 가장 시간이 많이 걸리는 LTC 매트릭스를 사전 통합하고 텍스처로 스케일링합니다. 각각 LCMatTexture 및 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 요약
다음은 조명 계산 픽셀 셰이더의 주요 프로세스와 단계를 요약한 것입니다.
그래프 TD DeferredLightPixelMain(DeferredLightPixelMain) --> SetupLightDataForStandardDeferred(SetupLightDataForStandardDeferred) SetupLightDataForStandardDeferred --> GetDynamicLighting(GetDynamicLighting) GetDynamicLighting --> GetDynamicLightingSplit(GetDynamicLightingSplit) GetDynamicLightingSplit --> GetLocalLightAttenuation(GetLocalLightAttenuation) GetLocalLightAttenuation --> GetShadowTerms(GetShadowTerms) GetShadowTerms --> IntegrateBxDF_CapsuleLight(IntegrateBxDF_CapsuleLight) IntegrateBxDF_CapsuleLight --> LineIrradiance(LineIrradiance) LineIrradiance --> SphereHorizonCosWrap(SphereHorizonCosWrap) SphereHorizonCosWrap --> ClosestPointLineToRay(ClosestPointLineToRay) ClosestPointLineToRay --> IntegrateBxDF_AreaLight(IntegrateBxDF_AreaLight) IntegrateBxDF_AreaLight --> ShadingModelID{ShadingModelID} ShadingModelID -->|SUBSURFACE| 서브서피스BxDF(하위서피스BxDF) ShadingModelID -->|CLEAR_COAT| 클리어코트BxDF(클리어코트BxDF) ShadingModelID -->|HAIR| 헤어BxDF(헤어BxDF) ShadingModelID -->|SINGLELAYERWATER| DefaultLitBxDF(DefaultLitBxDF) ShadingModelID -->|DEFAULT_LIT| DefaultLitBxDF(DefaultLitBxDF) ShadingModelID -->|THIN_TRANSLUCENT| DefaultLitBxDF(DefaultLitBxDF) ShadingModelID -->|천| 천BxDF(천BxDF) ShadingModelID -->|눈| 아이BxDF(EyeBxDF) ShadingModelID -->|...| 기타ShadingModel(...)
DefaultLitBxDF --> SphereMaxNoH(SphereMaxNoH)
SphereMaxNoH --> Diffuse_Lambert(Diffuse_Lambert)
Diffuse_Lambert --> RectGGXApproxLTC(RectGGXApproxLTC)
RectGGXApproxLTC --> SpecularGGX(반사GGX)
SpecularGGX --> LightAccumulator_AddSplit(LightAccumulator_AddSplit)
LightAccumulator_AddSplit --> LightAccumulator_GetResultSplit(LightAccumulator_GetResultSplit)
위의 주요 단계의 구체적인 논리는 여기서 반복되지 않습니다. 이전 코드 분석을 참고할 수 있습니다.
5.6 UE의 그림자
UE에는 다양한 유형의 섀도우가 있고 구현이 복잡하며 많은 최적화 기술이 관련됩니다. 이 장에서는 UE의 섀도우 렌더링 메커니즘을 소개하고 설명하는 데 많은 시간을 할애할 것입니다.
5.6.1 섀도우 개요
UE에는 다양한 광원 유형이 있는 반면 UE의 그림자는 아래 설명과 같이 더욱 다양합니다.
- 정적 그림자
정적 그림자는 정적 광원에 의해 정적 개체에 캐스팅되고 정적 수신기에 캐스팅됩니다. 따라서 정적 개체에서만 생성할 수 있습니다. 동적 개체는 일반적으로 정적 그림자를 생성할 수 없습니다.

왼쪽의 동적 캐릭터는 정적 광원 아래에서 빛나거나 그림자를 드리우지 않는 반면, 오른쪽의 정적 캐릭터는 빛을 발합니다.
- 계단식 그림자 맵
방향성 계단식 그림자 맵(Directional Light Cascading Shadow Maps) 또는 전체 장면 그림자(Whole Scene Shadows)라고도 알려진 계단식 그림자는 원거리 광원 뷰 공간에서 깊이 맵 정확도가 부족하고 다양한 시각적 결함이 있는 문제를 해결하기 위해 평행광으로 인해 전체 장면에 영향을 미치는 다단계 그림자 맵입니다.

계단식 그림자의 개략도. 광원 보기 공간에서는 빨간색이 가장 가깝고 깊이 맵의 해상도가 가장 작습니다. 노란색은 가장 멀고 깊이 맵의 해상도가 가장 높습니다.

방향성 조명의 계단식 그림자 맵 속성 그룹은 계단식 그림자의 세부 매개변수를 설정할 수 있습니다.
또한 방향성 고정 조명은 정적 그림자를 지원하는 동시에 계단식 그림자 맵을 통해 전체 장면의 그림자를 지원한다는 점에서 특별합니다. 애니메이션 식물이 많은 레벨에 매우 유용합니다. 동적 개체의 그림자를 지원할 수 있고 특정 거리에서 점차적으로 정적 그림자로 전환되기 때문입니다.
전환 영역은 방향성 조명 캐스케이드 그림자 속성 그룹의 Dynamic Shadow Distance StationaryLight 값을 조정하여 조정할 수 있습니다.
- 고정 조명 그림자
동적 객체는 디스턴스 필드 섀도우 맵에서 월드의 정적 섀도우로 통합되어야 합니다. 이렇게 하려면 개체별 셰이딩을 사용해야 합니다. 각각의 이동 가능한 개체는 고정 광원에서 두 개의 동적 그림자를 생성합니다. 하나는 동적 개체에 투영된 정적 세계를 처리하기 위한 그림자이고, 다른 하나는 세계에 투영된 동적 개체를 처리하기 위한 그림자입니다.
이 설정을 사용하면 고정 조명의 유일한 그림자 비용은 영향을 받는 동적 개체에서 발생합니다. 이는 동적 개체의 수에 따라 비용이 크거나 작을 수 있음을 의미합니다. 장면에 동적 개체가 충분하면 이동 가능한 광원을 사용하는 것이 더 효율적입니다.

- 객체별 그림자
이동 가능한 구성 요소에 사용되는 객체별 음영 처리는 객체의 경계 상자에 그림자 맵을 적용하므로 경계 상자는 정확해야 합니다. 스켈레탈 메시의 경우 이는 물리 자산이 있어야 함을 의미합니다. 입자 시스템의 경우 고정 경계 상자는 모든 입자를 수용할 수 있을 만큼 충분히 커야 합니다.

메시의 조명 속성 그룹에서 동적 삽입 그림자(Dynamic Inset Shadow)는 개체별 그림자를 켤 수 있으며, 이는 높은 품질과 높은 정밀도가 필요한 개체에 매우 유용합니다.
- 동적 그림자
이동식 광원은 모든 개체에 완전히 동적인 그림자(및 조명)를 투사합니다. 이 조명에 대한 데이터는 라이트맵에 구워지지 않으므로 모든 것에 동적 그림자를 자유롭게 드리울 수 있습니다. 스태틱 메시, 스켈레탈 메시, 파티클 이펙트 등은 전적으로 움직이는 라이트에서 동적 그림자를 드리우고 받습니다.
일반적으로 이동식 동적 그림자 투사 조명은 가장 성능 집약적입니다.
- 그림자 미리보기
고정 또는 정적 라이트가 편집 중이고 제때에 구성되지 않은 경우 UE는 구성되지 않은 그림자 대신 미리 보기 그림자를 사용합니다. 미리보기 그림자에는 미리보기라는 단어가 표시됩니다.

- 캡슐 섀도우
UE는 물체 자체 대신 물체의 물리적 자산(Physics Asset)에 의해 생성된 캡슐을 사용하여 그림자를 투사하는 것을 지원합니다. 부드러운 그림자를 생성할 수 있으며 물리적 자산이 포함된 스킨드 메시(스켈레탈 메시)에 주로 사용됩니다.

상단: 캡슐 섀도우; 하단: 해당 물리적 자산 표현.
- 컨택트 섀도우
접촉 그림자는 광원을 기반으로 하는 화면 공간 기반 보상 그림자입니다. 원리는 그림자 계산 단계에 있습니다. 광원의 접촉 그림자가 켜지면 추가 가시성 판단 광선이 투사되고, 장면 깊이 버퍼의 Ray Marching이 실행되어 픽셀이 가려졌는지(그림자에) 있는지 확인함으로써 보다 정확한 그림자 정보를 얻을 수 있습니다.

왼쪽: 접촉 그림자 끄기; 오른쪽: 접촉 그림자를 켭니다.
- 디스턴스 필드 섀도우
디스턴스 필드 섀도우(Distance Field Shadow)라는 이름은 미리 생성된 디스턴스 필드 정보를 사용하여 투사되는 그림자라는 의미입니다.
씬 디스턴스 필드가 필요한 기능을 사용하기 전에 프로젝트 설정에서 디스턴스 필드 섀도우, 디스턴스 필드 충돌, 디스턴스 필드 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
// 그림자(Shadow)정보 .
class FProjectedShadowInfo : public FRefCountedObject
{
public:
typedef TArray<const FPrimitiveSceneInfo*,SceneRenderingAllocator> PrimitiveArrayType;
// 렌더링(Render)그림자(Shadow)을(를) 활용하여 의 view.
FViewInfo* ShadowDepthView;
// 그림자(Shadow)필수: 렌더링(Render)의 view, Null테이블 view의 그림자(Shadow).
FViewInfo* DependentView;
// 그림자(Shadow)Uniform Buffer.
TUniformBufferRef<FShadowDepthPassUniformParameters> ShadowDepthPassUniformBuffer;
TUniformBufferRef<FMobileShadowDepthPassUniformParameters> MobileShadowDepthPassUniformBuffer;
// 그림자(Shadow)렌더링(Render)텍스처(Texture)(뎁스 또는 컬러 ).
FShadowMapRenderTargets RenderTargets;
// FVisibleLightInfo::AllProjectedShadows의 .
int32 ShadowId;
// 캐싱(Cache).
EShadowDepthCacheMode CacheMode;
// 변환(Transform)그림자(Shadow)모멘트(Moment)의 위치 .
FVector PreShadowTranslation;
// 그림자(Shadow)의 뷰(View)모멘트(Moment), 을(를) 활용하여 DependentView의 뷰(View)모멘트(Moment).
FMatrix ShadowViewMatrix;
// 및 모멘트(Moment), 을(를) 활용하여 렌더링(Render)그림자(Shadow)뎁스 버퍼(Depth Buffer)의 모멘트(Moment).
FMatrix SubjectAndReceiverMatrix;
FMatrix ReceiverMatrix;
FMatrix InvReceiverMatrix;
float InvMaxSubjectDepth;
// 뎁스 확장 , 의 . 을(를) 활용하여 변환(Transform/Convert)그림자(Shadow)뎁스 까지 .
float MaxSubjectZ;
float MinSubjectZ;
float MinPreSubjectZ;
// 의 그림자(Shadow)의 .
FConvexVolume CasterFrustum;
FConvexVolume ReceiverFrustum;
// 그림자(Shadow)의 바운딩 박스(AABB).
FSphere ShadowBounds;
// 단계 그림자(Shadow)설정(Set).
FShadowCascadeSettings CascadeSettings;
// 해상도/크기 , (atlas) 내에서 필터링 그림자(Shadow)의 .
uint32 BorderSize;
// 그림자(Shadow)뎁스 버퍼(Depth Buffer)( 또는 atlas) 내에서 의 위치 (), 의 그림자(Shadow): X + BorderSize, Y + BorderSize.
uint32 X;
uint32 Y;
// 그림자(Shadow)의 해상도 , . 할당(Allocate)의 그림자(Shadow)해상도 는 : ResolutionX + 2 * BorderSize, ResolutionY + 2 * BorderSize.
uint32 ResolutionX;
uint32 ResolutionY;
// , 개 view의 또는 의 최댓값(Max).
float MaxScreenPercent;
// 개 view의 .
TArray<float, TInlineAllocator<2> > FadeAlphas;
// 그림자(Shadow)는 뎁스 버퍼(Depth Buffer)할당(Allocate), 는 , 이면 X 및 Y초기화(Initialize).
uint32 bAllocated : 1;
// 그림자(Shadow)는 렌더링(Render).
uint32 bRendered : 1;
// 그림자(Shadow)는 preshadow캐싱(Cache) 내에서 할당(Allocate), 는 , 이면 X 및 Y는 preshadow캐싱(Cache)뎁스 buffer의 .
uint32 bAllocatedInPreshadowCache : 1;
// 그림자(Shadow)는 preshadow캐싱(Cache) 내에서 의 뎁스 갱신(Update).
uint32 bDepthsCached : 1;
uint32 bDirectionalLight : 1;
// 는 개 채널/패스(Pass) 내에서 렌더링(Render)cubemap의 의 포인트 라이트(Light)그림자(Shadow).
uint32 bOnePassPointLightShadow : 1;
// 는 개 는 의 그림자(Shadow).
uint32 bWholeSceneShadow : 1;
// 는 RSM(ReflectiveShadowmap).
uint32 bReflectiveShadowmap : 1;
// 는 반투명(Translucent)그림자(Shadow).
uint32 bTranslucentShadow : 1;
// 는 그림자(Shadow).
uint32 bCapsuleShadow : 1;
// 는 그림자(Shadow), 그림자(Shadow)는 처리(Process)정적 까지 동적 의 그림자(Shadow).
uint32 bPreShadow : 1;
// 는 오직 그림자(Shadow), 는 , 이면 투영/프로젝션 까지 의 , 품질 그림자(Shadow)(을(를) 활용하여 ).
uint32 bSelfShadowOnly : 1;
// 는 반투명(Translucent)그림자(Shadow).
uint32 bPerObjectOpaqueShadow : 1;
// 는 활성화(Enable).
uint32 bTransmission : 1;
// 을(를) 활용하여 포인트 라이트(Light)렌더링(Render)cubemap6개 의 그림자(Shadow)을(를) 활용하여 의 뷰(View)투영/프로젝션 모멘트(Moment).
TArray<FMatrix> OnePassShadowViewProjectionMatrices;
// 을(를) 활용하여 포인트 라이트(Light)렌더링(Render)cubemap6개 의 그림자(Shadow)을(를) 활용하여 의 뷰(View)모멘트(Moment).
TArray<FMatrix> OnePassShadowViewMatrices;
/** Frustums for each cubemap face, used for object culling one pass point light shadows. */
TArray<FConvexVolume> OnePassShadowFrustums;
(......)
// 그림자(Shadow)의 파라미터 , 단계 의 그림자(Shadow).
float PerObjectShadowFadeStart;
float InvPerObjectShadowFadeLength;
public:
// 설정(Set)그림자(Shadow).
bool SetupPerObjectProjection(FLightSceneInfo* InLightSceneInfo, ...);
// 설정(Set)()그림자(Shadow).
void SetupWholeSceneProjection(FLightSceneInfo* InLightSceneInfo, ...);
// 렌더링(Render)반투명(Translucent)의 그림자(Shadow)뎁스 .
void RenderDepth(FRHICommandListImmediate& RHICmdList, ...);
// 렌더링(Render)반투명(Translucent)의 그림자(Shadow)뎁스 .
void RenderTranslucencyDepths(FRHICommandList& RHICmdList, ...);
// 로 의 view렌더링(Render)까지 의 그림자(Shadow).
void RenderProjection(FRHICommandListImmediate& RHICmdList, ...) const;
// 렌더링(Render)채널/패스(Pass)포인트 라이트(Light)그림자(Shadow).
void RenderOnePassPointLightProjection(FRHICommandListImmediate& RHICmdList, ...) const;
void RenderFrustumWireframe(FPrimitiveDrawInterface* PDI) const;
// 렌더링(Render)상태 인터페이스 .
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;
// 추가(Add)그림자(Shadow)의 .
void AddSubjectPrimitive(FPrimitiveSceneInfo* PrimitiveSceneInfo, TArray<FViewInfo>* ViewArray, ...);
// 추가(Add)그림자(Shadow).
void AddReceiverPrimitive(FPrimitiveSceneInfo* PrimitiveSceneInfo);
// 로 그림자(Shadow)의 동적 .
void GatherDynamicMeshElements(FSceneRenderer& Renderer, ...);
// DynamicMeshElement변환(Transform/Convert)FMeshDrawCommand.
void SetupMeshDrawCommandsForShadowDepth(FSceneRenderer& Renderer, ...);
void SetupMeshDrawCommandsForProjectionStenciling(FSceneRenderer& Renderer);
// 로부터 메모리 내에서 생성(Create)개 의 viewShadowDepthView 내에서 캐싱(Cache), 을(를) 활용하여 로써 렌더링(Render)그림자(Shadow)뎁스 .
void SetupShadowDepthView(FRHICommandListImmediate& RHICmdList, FSceneRenderer* SceneRenderer);
// 설정(Set) 및 갱신(Update)Uniform Buffer.
void SetupShadowUniformBuffers(FRHICommandListImmediate& RHICmdList, FScene* Scene, ...);
// 캐싱(Cache)의 그림자(Shadow)EReadable상태 .
void TransitionCachedShadowmap(FRHICommandListImmediate& RHICmdList, FScene* Scene);
// 계산/산출(Calculate) 및 갱신(Update)ShaderDepthBias 및 ShaderSlopeDepthBias.
void UpdateShaderDepthBias();
// 계산/산출(Calculate)PCF비교 파라미터 .
float ComputeTransitionSize() const;
// 데이터 페치/가져오기(Fetch) 및 인터페이스 .
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;
// 그림자(Shadow)리스트 .
PrimitiveArrayType DynamicSubjectPrimitives;
// , 오직 preshadow유효(Valid).
PrimitiveArrayType ReceiverPrimitives;
// 반투명(Translucent)그림자(Shadow)리스트 .
PrimitiveArrayType SubjectTranslucentPrimitives;
// 그림자(Shadow)의 의 .
TArray<FMeshBatchAndRelevance,SceneRenderingAllocator> DynamicSubjectMeshElements;
TArray<FMeshBatchAndRelevance,SceneRenderingAllocator> DynamicSubjectTranslucentMeshElements;
TArray<const FStaticMeshBatch*, SceneRenderingAllocator> SubjectMeshCommandBuildRequests;
// DynamicSubjectMeshElements개수 .
int32 NumDynamicSubjectMeshElements;
// SubjectMeshCommandBuildRequests개수 .
int32 NumSubjectMeshCommandBuildRequestElements;
// 드로우/렌더(Draw)그림자(Shadow)의 드로우/렌더(Draw)/렌더링(Render)상태 .
FMeshCommandOneFrameArray ShadowDepthPassVisibleCommands;
FParallelMeshDrawCommandPass ShadowDepthPass;
TArray<FShadowMeshDrawCommandPass, TInlineAllocator<2>> ProjectionStencilingPasses;
FDynamicMeshDrawCommandStorage DynamicMeshDrawCommandStorage;
FGraphicsMinimalPipelineStateSet GraphicsMinimalPipelineStateSet;
bool NeedsShaderInitialisation;
// 그림자(Shadow)렌더링(Render)의 . UpdateShaderDepthBias()설정(Set), GetShaderDepthBias()페치/가져오기(Fetch), -1테이블 초기화(Initialize).
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
// 뷰(View)[]의 라이트(Light)정보 , 는 그림자(Shadow)의 정보 .
class FVisibleLightInfo
{
public:
// 메모리 힙 (mem stack)할당(Allocate) 및 의 그림자(Shadow)정보 .
TArray<FProjectedShadowInfo*,SceneRenderingAllocator> MemStackProjectedShadows;
// 의 그림자(Shadow)정보 , 에 의해 그림자(Shadow)설정(Set)스테이지 출력, 는 의 렌더링(Render).
TArray<FProjectedShadowInfo*,SceneRenderingAllocator> AllProjectedShadows;
// 의 그림자(Shadow)정보 , 을(를) 활용하여 을(를) 활용하여 의 , /그림자(Shadow)/RSM.
TArray<FProjectedShadowInfo*,SceneRenderingAllocator> ShadowsToProject;
TArray<FProjectedShadowInfo*,SceneRenderingAllocator> CapsuleShadowsToProject;
TArray<FProjectedShadowInfo*,SceneRenderingAllocator> RSMsToProject;
// 의 그림자(Shadow). 의 메모리 힙 내에서 할당(Allocate) 및 , 따라서 을(를) 활용하여 TRefCountPtr을(를) 활용하여 .
TArray<TRefCountPtr<FProjectedShadowInfo>,SceneRenderingAllocator> ProjectedPreShadows;
// 차폐/오클루전 의 그림자(Shadow), 를 위해 차폐/오클루전 컬링 따라서 .
TArray<FProjectedShadowInfo*,SceneRenderingAllocator> OccludedPerObjectShadows;
};
// 뷰(View)의 라이트(Light)정보 .
class FVisibleLightViewInfo
{
public:
// 라이트(Light)까지 의 .
TArray<FPrimitiveSceneInfo*,SceneRenderingAllocator> VisibleDynamicLitPrimitives;
// FVisibleLightInfo::AllProjectedShadows의 그림자(Shadow)매핑 테이블 .
FSceneBitArray ProjectedShadowVisibilityMap;
// FVisibleLightInfo::AllProjectedShadows의 그림자(Shadow)ViewRelevance.
TArray<FPrimitiveViewRelevance,SceneRenderingAllocator> ProjectedShadowViewRelevanceMap;
// 는 프러스텀/프러스텀 . (/스카이라이트(Skylight)는 true)
uint32 bInViewFrustum : 1;
(......)
};
// Engine\Source\Runtime\Renderer\Private\ScenePrivate.h
class FSceneViewState
{
public:
// 그림자(Shadow)의 . 을(를) 활용하여 비교 개 그림자(Shadow)인스턴스 는 .
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:
// 그림자(Shadow)의 id.
FPrimitiveComponentId PrimitiveId;
// 그림자(Shadow)의 라이트(Light).
const ULightComponent* Light;
// 그림자(Shadow)그림자(Shadow) 내에서 의 .
int32 ShadowSplitIndex;
// 는 반투명(Translucent)그림자(Shadow).
bool bTranslucentShadow;
};
};5.6.3 섀도우 초기화
이 섹션에서는 그림자 초기화를 분석하는 데 많은 시간을 할애합니다. 중복 코드 분석을 읽고 싶지 않다면 5.6.3.12 섀도우 초기화 요약으로 바로 이동할 수 있습니다.
섀도우 초기화는 InitViews 단계에서 이루어지며 호출 스택 다이어그램은 다음과 같습니다.
void FDeferredShadingSceneRenderer::Render(FRHICommandListImmediate& RHICmdList)
{
bool FDeferredShadingSceneRenderer::InitViews(RHICmdList, ...)
{
void FDeferredShadingSceneRenderer::InitViewsPossiblyAfterPrepass(FRHICommandListImmediate& RHICmdList, ...)
{
// 초기화(Initialize)동적 그림자(Shadow).
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)
{
// 초기화(Initialize)플래그 및 개수 .
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;
// 계산/산출(Calculate)그림자(Shadow).
TArray<FProjectedShadowInfo*,SceneRenderingAllocator> PreShadows;
// 뷰(View)의 그림자(Shadow).
TArray<FProjectedShadowInfo*,SceneRenderingAllocator> ViewDependentWholeSceneShadows;
// 뷰(View)의 클리핑/클램핑(Clipping)의 그림자(Shadow).
TArray<FProjectedShadowInfo*,SceneRenderingAllocator> ViewDependentWholeSceneShadowsThatNeedCulling;
{
// 순회(Traverse/Iterate)라이트(Light), 타입/유형 의 라이트(Light)추가(Add)타입/유형 의 렌더링(Render)의 그림자(Shadow)리스트 내에서 .
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그림자(Shadow) 및 , 만약 그림자(Shadow)타입/유형 , 이면 무시/스킵(Ignore).
const FLightOcclusionType OcclusionType = GetLightOcclusionType(LightSceneInfoCompact);
if (OcclusionType != FLightOcclusionType::Shadowmap)
continue;
// 만약 라이트(Light)활성화(Enable)그림자(Shadow) 또는 그림자(Shadow)품질 , 이면 무시/스킵(Ignore)그림자(Shadow).
if ((LightSceneInfoCompact.bCastStaticShadow || LightSceneInfoCompact.bCastDynamicShadow) && GetShadowQuality() > 0)
{
// 검사/감지(Detect)라이트(Light)는 개 view, 만약 , 이면 무시/스킵(Ignore).
bool bIsVisibleInAnyView = false;
for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
{
bIsVisibleInAnyView = LightSceneInfo->ShouldRenderLight(Views[ViewIndex]);
if (bIsVisibleInAnyView)
{
break;
}
}
// 클리핑/클램핑(Clipping)조건 , 처리(Process)라이트(Light)의 그림자(Shadow).
if (bIsVisibleInAnyView)
{
// 초기화(Initialize)그림자(Shadow)의 플래그 및 변수.
static const auto AllowStaticLightingVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.AllowStaticLighting"));
const bool bAllowStaticLighting = (!AllowStaticLightingVar || AllowStaticLightingVar->GetValueOnRenderThread() != 0);
// 는 포인트 라이트(Light)그림자(Shadow). ※ 주의: 모멘트(Moment)포인트 라이트(Light)처리(Process).
const bool bPointLightShadow = LightSceneInfoCompact.LightType == LightType_Point || LightSceneInfoCompact.LightType == LightType_Rect;
// 계산/산출(Calculate)그림자(Shadow)의 라이트(Light)오직 생성(Create)그림자(Shadow)(whole scene shadow).
const bool bShouldCreateShadowForMovableLight =
LightSceneInfoCompact.bCastDynamicShadow
&& (!LightSceneInfo->Proxy->HasStaticShadowing() || !bAllowStaticLighting);
const bool bCreateShadowForMovableLight =
bShouldCreateShadowForMovableLight
&& (!bPointLightShadow || bProjectEnablePointLightShadows);
// 계산/산출(Calculate)그림자(Shadow)의 의 라이트(Light)생성(Create)그림자(Shadow).
const bool bShouldCreateShadowToPreviewStaticLight =
LightSceneInfo->Proxy->HasStaticShadowing()
&& LightSceneInfoCompact.bCastStaticShadow
&& !LightSceneInfo->IsPrecomputedLightingValid();
const bool bCreateShadowToPreviewStaticLight =
bShouldCreateShadowToPreviewStaticLight
&& (!bPointLightShadow || bProjectEnablePointLightShadows);
// 정적 그림자(Shadow)그러나 에 의해 오버랩 유효(Valid)그림자(Shadow)의 라이트(Light)생성(Create)그림자(Shadow).
const bool bShouldCreateShadowForOverflowStaticShadowing =
LightSceneInfo->Proxy->HasStaticShadowing()
&& !LightSceneInfo->Proxy->HasStaticLighting()
&& LightSceneInfoCompact.bCastStaticShadow
&& LightSceneInfo->IsPrecomputedLightingValid()
&& LightSceneInfo->Proxy->GetShadowMapChannel() == INDEX_NONE;
const bool bCreateShadowForOverflowStaticShadowing =
bShouldCreateShadowForOverflowStaticShadowing
&& (!bPointLightShadow || bProjectEnablePointLightShadows);
// 추가(Add)포인트 라이트(Light)의 그림자(Shadow).
const bool bPointLightWholeSceneShadow = (bShouldCreateShadowForMovableLight || bShouldCreateShadowForOverflowStaticShadowing || bShouldCreateShadowToPreviewStaticLight) && bPointLightShadow;
if (bPointLightWholeSceneShadow)
{
UsedWholeScenePointLightNames.Add(LightSceneInfoCompact.LightSceneInfo->Proxy->GetComponentName());
}
// 생성(Create)라이트(Light)의 그림자(Shadow).
if (bCreateShadowForMovableLight || bCreateShadowToPreviewStaticLight || bCreateShadowForOverflowStaticShadowing)
{
CreateWholeSceneProjectedShadow(LightSceneInfo, NumPointShadowCachesUpdatedThisFrame, NumSpotShadowCachesUpdatedThisFrame);
}
// 및 의 라이트(Light)생성(Create)CSM(단계 그림자(Shadow)), 또는 는 의 정적 라이트(Light).
if ((!LightSceneInfo->Proxy->HasStaticLighting() && LightSceneInfoCompact.bCastDynamicShadow) || bCreateShadowToPreviewStaticLight)
{
// 추가(Add)뷰(View)의 그림자(Shadow).
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);
}
// 처리(Process)그림자(Shadow), 의 는 라이트(Light) 및 의 . PerObject그림자(Shadow)、반투명(Translucent)그림자(Shadow)、그림자(Shadow).
if( !bMobile || (LightSceneInfo->Proxy->CastsModulatedShadows() && !LightSceneInfo->Proxy->UseCSMForDynamicObjects()))
{
Scene->FlushAsyncLightPrimitiveInteractionCreation();
// 처리(Process)동적 의 그림자(Shadow).
for (FLightPrimitiveInteraction* Interaction = LightSceneInfo->GetDynamicInteractionOftenMovingPrimitiveList(false); Interaction; Interaction = Interaction->GetNextPrimitive())
{
SetupInteractionShadows(RHICmdList, Interaction, VisibleLightInfo, bStaticSceneOnly, ViewDependentWholeSceneShadows, PreShadows);
}
// 처리(Process)정적 의 그림자(Shadow).
for (FLightPrimitiveInteraction* Interaction = LightSceneInfo->GetDynamicInteractionStaticPrimitiveList(false); Interaction; Interaction = Interaction->GetNextPrimitive())
{
SetupInteractionShadows(RHICmdList, Interaction, VisibleLightInfo, bStaticSceneOnly, ViewDependentWholeSceneShadows, PreShadows);
}
}
}
}
}
}
// 계산/산출(Calculate)그림자(Shadow)의 .
InitProjectedShadowVisibility(RHICmdList);
}
// 의 계산/산출(Calculate)그림자(Shadow), 추가(Add)의 까지 캐싱(Cache) 내에서 .
UpdatePreshadowCache(FSceneRenderTargets::Get(RHICmdList));
// 리스트 , 로써 드로우/렌더(Draw)타입/유형 의 그림자(Shadow).
GatherShadowPrimitives(PreShadows, ViewDependentWholeSceneShadowsThatNeedCulling, bStaticSceneOnly);
// 할당(Allocate)그림자(Shadow)뎁스 렌더링(Render)텍스처(Texture).
AllocateShadowDepthTargets(RHICmdList);
// 그림자(Shadow)의 동적 , 의 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];
// 로 라이트(Light)생성(Create)그림자(Shadow)의 초기화(Initialize).
TArray<FWholeSceneProjectedShadowInitializer, TInlineAllocator<6> > ProjectedShadowInitializers;
if (LightSceneInfo->Proxy->GetWholeSceneProjectedShadowInitializer(ViewFamily, ProjectedShadowInitializers))
{
FSceneRenderTargets& SceneContext_ConstantsOnly = FSceneRenderTargets::Get_FrameConstantsOnly();
// 그림자(Shadow)해상도 상수(Constant).
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());
// 계산/산출(Calculate)뷰(View)의 그림자(Shadow)의 해상도 , 을(를) 활용하여 의 해상도 .
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);
// 계산/산출(Calculate)해상도 스케일 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:
// 처리(Process).
checkf(false, TEXT("Unexpected LightType %d appears in CreateWholeSceneProjectedShadow %s"),
(int32)LightSceneInfo->Proxy->GetLightType(),
*LightSceneInfo->Proxy->GetComponentName().ToString());
}
// 적용 ShadowResolutionScale계산/산출(Calculate)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;
}
// 임계값(Threshold)생성(Create)그림자(Shadow).
if (MaxFadeAlpha > 1.0f / 256.0f)
{
Scene->FlushAsyncLightPrimitiveInteractionCreation();
// 순회(Traverse/Iterate)라이트(Light)의 그림자(Shadow)초기화(Initialize), 에 따라 단계 개수 생성(Create) 및 설정(Set)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);
// 계산/산출(Calculate)그림자(Shadow)의 캐싱(Cache), 그림자(Shadow)의 해상도/크기 및 개수 데이터 .
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;
}
// 생성(Create)NumShadowMaps개 그림자(Shadow), 개 그림자(Shadow)의 데이터 FProjectedShadowInfo인스턴스 내에서 .
for (int32 CacheModeIndex = 0; CacheModeIndex < NumShadowMaps; CacheModeIndex++)
{
// 생성(Create)FProjectedShadowInfo인스턴스 .
FProjectedShadowInfo* ProjectedShadowInfo = new(FMemStack::Get(), 1, 16) FProjectedShadowInfo;
// 설정(Set)ProjectedShadowInfo의 파라미터 (프러스텀/프러스텀 , 변환(Transform)모멘트(Moment), 뎁스 , 뎁스 ).
ProjectedShadowInfo->SetupWholeSceneProjection(
LightSceneInfo,
NULL,
ProjectedShadowInitializer,
SizeX,
SizeY,
ShadowBorder,
false // no RSM
);
ProjectedShadowInfo->CacheMode = CacheMode[CacheModeIndex];
ProjectedShadowInfo->FadeAlphas = FadeAlphas;
// 추가(Add)라이트(Light)의 그림자(Shadow)리스트 .
VisibleLightInfo.MemStackProjectedShadows.Add(ProjectedShadowInfo);
// 채널/패스(Pass)포인트 라이트(Light)그림자(Shadow).
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++)
{
// 개 생성(Create)뷰(View)투영/프로젝션 모멘트(Moment).
const FMatrix WorldToLightMatrix = FLookAtMatrix(LightPosition, LightPosition + CubeDirections[FaceIndex], UpVectors[FaceIndex]) * ScaleMatrix;
ProjectedShadowInfo->OnePassShadowViewMatrices.Add(WorldToLightMatrix);
const FMatrix ShadowViewProjectionMatrix = WorldToLightMatrix * FaceProjection;
ProjectedShadowInfo->OnePassShadowViewProjectionMatrices.Add(ShadowViewProjectionMatrix);
// 생성(Create), 을(를) 활용하여 의 클리핑/클램핑(Clipping).
GetViewFrustumBounds(ProjectedShadowInfo->OnePassShadowFrustums[FaceIndex], ShadowViewProjectionMatrix, false);
// 유효(Valid)의 .
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);
}
}
// 초기화(Initialize).
ProjectedShadowInfo->CasterFrustum.Init();
}
// 거리그림자(Shadow), 실행(Execute)CPU클리핑/클램핑(Clipping).
if (!ProjectedShadowInfo->bRayTracedDistanceField)
{
// 라이트(Light)뷰(View)의 , 로써 클리핑/클램핑(Clipping)그림자(Shadow).
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))
{
// 라이트(Light)의 추가(Add)까지 의 리스트 (subject primitive list).
for (FLightPrimitiveInteraction* Interaction = LightSceneInfo->GetDynamicInteractionOftenMovingPrimitiveList(false);
Interaction;
Interaction = Interaction->GetNextPrimitive())
{
// 그림자(Shadow)그림자(Shadow)(InsetShader) 및 라이트(Light)교차(Intersection)의 추가(Add)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)
{
// 그림자(Shadow)의 추가(Add)까지 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;
// 캐싱(Cache)는 오직 정적 .
if (CacheMode[CacheModeIndex] == SDCM_StaticPrimitivesOnly)
{
const bool bHasStaticPrimitives = ProjectedShadowInfo->HasSubjectPrims();
// 정적 그림자(Shadow)렌더링(Render).
bRenderShadow = bHasStaticPrimitives;
FCachedShadowMapData& CachedShadowMapData = Scene->CachedShadowMaps.FindChecked(ProjectedShadowInfo->GetLightSceneInfo().Id);
CachedShadowMapData.bCachedShadowMapHasPrimitives = bHasStaticPrimitives;
}
// 렌더링(Render)그림자(Shadow)이면 추가(Add)까지 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)
{
// 순회(Traverse/Iterate)view, 개 view생성(Create)view의 그림자(Shadow).
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;
(......)
// 만약 는 뷰(View), 처리(Process)그림자(Shadow).
if (IStereoRendering::IsAPrimaryView(View))
{
const bool bExtraDistanceFieldCascade = LightSceneInfo.Proxy->ShouldCreateRayTracedCascade(View.GetFeatureLevel(), LightSceneInfo.IsPrecomputedLightingValid(), View.MaxShadowCascades);
// 페치/가져오기(Fetch)뷰(View)의 투영/프로젝션 개수 .
const int32 ProjectionCount = LightSceneInfo.Proxy->GetNumViewDependentWholeSceneShadows(View, LightSceneInfo.IsPrecomputedLightingValid()) + (bExtraDistanceFieldCascade?1:0);
FSceneRenderTargets& SceneContext_ConstantsOnly = FSceneRenderTargets::Get_FrameConstantsOnly();
// 에 따라 투영/프로젝션 개수 , 생성(Create)그림자(Shadow)초기화(Initialize) 및 의 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));
// 생성(Create)FProjectedShadowInfo인스턴스 .
FProjectedShadowInfo* ProjectedShadowInfo = new(FMemStack::Get(), 1, 16) FProjectedShadowInfo;
// 그림자(Shadow).
uint32 ShadowBorder = NeedsUnatlasedCSMDepthsWorkaround(FeatureLevel) ? 0 : SHADOW_BORDER;
// 설정(Set)투영/프로젝션 .
ProjectedShadowInfo->SetupWholeSceneProjection(
&LightSceneInfo,
&View,
ProjectedShadowInitializer,
ShadowBufferResolution.X - ShadowBorder * 2,
ShadowBufferResolution.Y - ShadowBorder * 2,
ShadowBorder,
false // no RSM
);
ProjectedShadowInfo->FadeAlphas = FadeAlphas;
// ProjectedShadowInfo추가(Add)까지 라이트(Light)정보 의 리스트 내에서 .
FVisibleLightInfo& LightViewInfo = VisibleLightInfos[LightSceneInfo.Id];
VisibleLightInfo.MemStackProjectedShadows.Add(ProjectedShadowInfo);
VisibleLightInfo.AllProjectedShadows.Add(ProjectedShadowInfo);
ShadowInfos.Add(ProjectedShadowInfo);
// 추가(Add)까지 클리핑/클램핑(Clipping)그림자(Shadow)리스트 .
if (!ProjectedShadowInfo->bRayTracedDistanceField)
{
ShadowInfosThatNeedCulling.Add(ProjectedShadowInfo);
}
}
}
// 처리(Process)RSM(Refletive Shadow Map), 을(를) 활용하여 LPV라이팅 .
FSceneViewState* ViewState = (FSceneViewState*)View.State;
if (ViewState)
{
(......)
}
}
}
}5.6.3.4 SetupInteractionShadows
그런 다음 계속해서 SetupInteractionShadow를 분석합니다.
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;
// 처리(Process)라이트(Light), 만약 포인트 , bShadowHandledByParent로 true.
if (PrimitiveSceneInfo->LightingAttachmentRoot.IsValid())
{
FAttachmentGroupSceneInfo& AttachmentGroup = Scene->AttachmentGroups.FindChecked(PrimitiveSceneInfo->LightingAttachmentRoot);
bShadowHandledByParent = AttachmentGroup.ParentSceneInfo && AttachmentGroup.ParentSceneInfo->Proxy->LightAttachmentsAsGroup();
}
// bShadowHandledByParent의 그림자(Shadow)포인트 처리(Process).
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))
{
// 생성(Create)그림자(Shadow).
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];
// 검사/감지(Detect)그림자(Shadow)는 개 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];
// 페치/가져오기(Fetch)의 캐싱(Cache)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;
// 반투명(Translucent)그림자(Shadow), 회 는 : id, 라이트(Light), 그림자(Shadow)의 , 는 반투명(Translucent)그림자(Shadow).
const FSceneViewState::FProjectedShadowKey OpaqueKey(PrimitiveSceneInfo->PrimitiveComponentId, LightSceneInfo->Proxy->GetLightComponent(), INDEX_NONE, false);
// 검사/감지(Detect)반투명(Translucent)그림자(Shadow) 또는 그림자(Shadow)는 차폐/오클루전 .
const bool bOpaqueShadowIsOccluded =
!bCreateOpaqueObjectShadow ||
(
!View.bIgnoreExistingQueries && View.State &&
((FSceneViewState*)View.State)->IsShadowOccluded(RHICmdList, OpaqueKey, NumBufferedFrames)
);
// 반투명(Translucent)그림자(Shadow)정렬 .
const FSceneViewState::FProjectedShadowKey TranslucentKey(PrimitiveSceneInfo->PrimitiveComponentId, LightSceneInfo->Proxy->GetLightComponent(), INDEX_NONE, true);
// 검사/감지(Detect)반투명(Translucent)그림자(Shadow) 또는 그림자(Shadow)는 차폐/오클루전 .
const bool bTranslucentShadowIsOccluded =
!bCreateTranslucentObjectShadow ||
(
!View.bIgnoreExistingQueries && View.State &&
((FSceneViewState*)View.State)->IsShadowOccluded(RHICmdList, TranslucentKey, NumBufferedFrames)
);
// 무시/스킵(Ignore)Pass렌더링(Render)의 .
if (PrimitiveSceneInfo->Proxy->ShouldRenderInMainPass())
{
const bool bSubjectIsVisibleInThisView = View.PrimitiveVisibilityMap[PrimitiveSceneInfo->GetIndex()];
bSubjectIsVisible |= bSubjectIsVisibleInThisView;
}
// 그림자(Shadow)만약 및 뷰(View)차폐/오클루전 , 이면 로 .
bOpaqueShadowIsVisibleThisFrame |= (bPrimitiveIsShadowRelevant && !bOpaqueShadowIsOccluded);
bTranslucentShadowIsVisibleThisFrame |= (bPrimitiveIsShadowRelevant && !bTranslucentShadowIsOccluded);
bShadowIsPotentiallyVisibleNextFrame |= bPrimitiveIsShadowRelevant;
bOpaque |= ViewRelevance.bOpaque;
bTranslucentRelevance |= ViewRelevance.HasTranslucency();
} // for
// 만약 다음 프레임(Next Frame), 이면 반환(Return), 로써 의 그림자(Shadow)생성(Create) 및 설정(Set).
if (!bOpaqueShadowIsVisibleThisFrame && !bTranslucentShadowIsVisibleThisFrame && !bShadowIsPotentiallyVisibleNextFrame)
{
return;
}
// 그림자(Shadow).
TArray<FPrimitiveSceneInfo*, SceneRenderingAllocator> ShadowGroupPrimitives;
PrimitiveSceneInfo->GatherLightingAttachmentGroupPrimitives(ShadowGroupPrimitives);
#if ENABLE_NAN_DIAGNOSTIC
// 유효(Valid)의 그림자(Shadow), 반환(Return).
if (ShadowGroupPrimitives.Num() == 0)
{
return;
}
#endif
// 계산/산출(Calculate)의 그림자(Shadow)의 바운딩 박스(AABB).
FBoxSphereBounds OriginalBounds = ShadowGroupPrimitives[0]->Proxy->GetBounds();
// 무효/유효하지 않음(Invalid)의 바운딩 박스(AABB).
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);
// 그림자(Shadow)상수(Constant).
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());
// 그림자(Shadow)해상도 .
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;
// 는 렌더링(Render)그림자(Shadow)(그림자(Shadow)캐싱(Cache)).
const bool bRenderPreShadow =
CVarAllowPreshadows.GetValueOnRenderThread()
&& LightSceneInfo->Proxy->HasStaticShadowing()
&& bSubjectIsVisible
&& (!PrimitiveSceneInfo->Proxy->HasStaticLighting() || !Interaction->IsShadowMapped())
&& !(PrimitiveSceneInfo->Proxy->UseSingleSampleShadowFromStationaryLights() && LightSceneInfo->Proxy->GetLightType() == LightType_Directional);
// 만약 렌더링(Render)그림자(Shadow), 이면 바운딩 박스(AABB), 로써 캐싱(Cache)을(를) 활용하여 .
if (bRenderPreShadow && ShouldUseCachePreshadows())
{
float PreshadowExpandFraction = FMath::Max(CVarPreshadowExpandFraction.GetValueOnRenderThread(), 0.0f);
Bounds.SphereRadius += (Bounds.BoxExtent * PreshadowExpandFraction).Size();
Bounds.BoxExtent *= PreshadowExpandFraction + 1.0f;
}
// 그림자(Shadow)초기화(Initialize).
FPerObjectProjectedShadowInitializer ShadowInitializer;
if ((MaxResolutionFadeAlpha > 1.0f / 256.0f || (bRenderPreShadow && MaxResolutionPreShadowFadeAlpha > 1.0f / 256.0f))
&& LightSceneInfo->Proxy->GetPerObjectProjectedShadowInitializer(Bounds, ShadowInitializer))
{
const float MaxFadeAlpha = MaxResolutionFadeAlpha;
// 의 그림자(Shadow)생성(Create)그림자(Shadow)인스턴스 .
if (CVarAllowPerObjectShadows.GetValueOnRenderThread() && MaxFadeAlpha > 1.0f / 256.0f)
{
const int32 SizeX = MaxDesiredResolution >= MaxShadowResolution ? MaxShadowResolution : (1 << (FMath::CeilLogTwo(MaxDesiredResolution) - 1));
if (bOpaque && bCreateOpaqueObjectShadow && (bOpaqueShadowIsVisibleThisFrame || bShadowIsPotentiallyVisibleNextFrame))
{
// 생성(Create)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추가(Add)까지 의 리스트 .
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);
}
}
}
// 반투명(Translucent)그림자(Shadow).
if (bTranslucentRelevance
&& Scene->GetFeatureLevel() >= ERHIFeatureLevel::SM5
&& bCreateTranslucentObjectShadow
&& (bTranslucentShadowIsVisibleThisFrame || bShadowIsPotentiallyVisibleNextFrame))
{
(......)
}
}
const float MaxPreFadeAlpha = MaxResolutionPreShadowFadeAlpha;
// 처리(Process)유효(Valid)의 그림자(Shadow).
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();
// 검사/감지(Detect)는 그림자(Shadow).
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의 바운딩 박스(AABB)거리, 이면 테이블 그림자(Shadow).
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;
}
}
// 오직 그림자(Shadow)그림자(Shadow)생성(Create)반투명(Translucent)preshadow.
if (bIsOutsideWholeSceneShadow)
{
// 로부터 캐싱(Cache) 내에서 을(를) 활용하여 preshadow.
TRefCountPtr<FProjectedShadowInfo> ProjectedPreShadowInfo = GetCachedPreshadow(Interaction, ShadowInitializer, OriginalBounds, PreshadowSizeX);
bool bOk = true;
// 생성(Create) 및 설정(Set)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
);
}
// 설정(Set)유효(Valid)의 ProjectedPreShadowInfo의 데이터 , 추가(Add)까지 VisibleLightInfo리스트 내에서 .
if (bOk)
{
ProjectedPreShadowInfo->FadeAlphas = ResolutionPreShadowFadeAlphas;
VisibleLightInfo.AllProjectedShadows.Add(ProjectedPreShadowInfo);
VisibleLightInfo.ProjectedPreShadows.Add(ProjectedPreShadowInfo);
// 만약 preshadow뎁스 버퍼(Depth Buffer), 이면 OutPreShadows리스트 내에서 . OutPreShadows을(를) 활용하여 렌더링(Render)그림자(Shadow)의 정보 .
if (!ProjectedPreShadowInfo->bDepthsCached && ProjectedPreShadowInfo->CasterFrustum.PermutedPlanes.Num())
{
OutPreShadows.Add(ProjectedPreShadowInfo);
}
// 의 그림자(Shadow)의 리스트 .
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);
// 순회(Traverse/Iterate)의 라이트(Light), 초기화(Initialize)뷰(View)의 ProjectedShadowVisibilityMaps, 의 그림자(Shadow).
for(TSparseArray<FLightSceneInfoCompact>::TConstIterator LightIt(Scene->Lights);LightIt;++LightIt)
{
FVisibleLightInfo& VisibleLightInfo = VisibleLightInfos[LightIt.GetIndex()];
// 할당(Allocate)뷰(View)의 그림자(Shadow) 및 .
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());
}
// 순회(Traverse/Iterate)라이트(Light)의 그림자(Shadow)인스턴스 .
for( int32 ShadowIndex=0; ShadowIndex<VisibleLightInfo.AllProjectedShadows.Num(); ShadowIndex++ )
{
FProjectedShadowInfo& ProjectedShadowInfo = *VisibleLightInfo.AllProjectedShadows[ShadowIndex];
// 저장(Save)그림자(Shadow).
ProjectedShadowInfo.ShadowId = ShadowIndex;
for(int32 ViewIndex = 0;ViewIndex < Views.Num();ViewIndex++)
{
FViewInfo& View = Views[ViewIndex];
// 처리(Process)뷰(View)의 그림자(Shadow).
if (ProjectedShadowInfo.DependentView && ProjectedShadowInfo.DependentView != &View)
{
(......)
}
FVisibleLightViewInfo& VisibleLightViewInfo = View.VisibleLightInfos[LightIt.GetIndex()];
// 라이트(Light)프러스텀/프러스텀 .
if(VisibleLightViewInfo.bInViewFrustum)
{
// 계산/산출(Calculate)의 뷰(View)데이터 .
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;
// 판정/확인 그림자(Shadow)는 차폐/오클루전 .
bool bShadowIsOccluded = false;
if (!View.bIgnoreExistingQueries && View.State)
{
// Check if the shadow is occluded.
bShadowIsOccluded =
((FSceneViewState*)View.State)->IsShadowOccluded(
RHICmdList,
FSceneViewState::FProjectedShadowKey(ProjectedShadowInfo),
NumBufferedFrames
);
}
// 만약 조건 , 이면 설정(Set)플래그
if(bPrimitiveIsShadowRelevant && !bShadowIsOccluded)
{
VisibleLightViewInfo.ProjectedShadowVisibilityMap[ShadowIndex] = true;
}
// 만약 그림자(Shadow)는 RSM그림자(Shadow), 이면 드로우/렌더(Draw)그림자(Shadow).
if(bPrimitiveIsShadowRelevant && !bShadowIsOccluded && !ProjectedShadowInfo.bReflectiveShadowmap)
{
bool bDrawPreshadowFrustum = CVarDrawPreshadowFrustum.GetValueOnRenderThread() != 0;
// 드로우/렌더(Draw)preshadow.
if ((ViewFamily.EngineShowFlags.ShadowFrustums)
&& ((bDrawPreshadowFrustum && ProjectedShadowInfo.bPreShadow) || (!bDrawPreshadowFrustum && !ProjectedShadowInfo.bPreShadow)))
{
// 드로우/렌더(Draw)그림자(Shadow)의 PDI.
FViewElementPDI ShadowFrustumPDI(&Views[ViewIndex], nullptr, &Views[ViewIndex].DynamicPrimitiveShaderData);
// 그림자(Shadow)드로우/렌더(Draw).
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);
// 그림자(Shadow)투영/프로젝션 바운딩 박스(AABB).
DrawFrustumWireframe(&ShadowFrustumPDI, ProjectedShadowInfo.SubjectAndReceiverMatrix.Inverse() * FTranslationMatrix(-ProjectedShadowInfo.PreShadowTranslation), Color, 0);
}
else // 그림자(Shadow), 호출(Call)ProjectedShadowInfo의 드로우/렌더(Draw)인터페이스 .
{
ProjectedShadowInfo.RenderFrustumWireframe(&ShadowFrustumPDI);
}
}
}
}
}
}
}
(......)
}위의 DrawFrustumWireframe 및 RenderFrustumWireframe 호출은 즉시 그리는 것이 아니라 FViewElementPDI를 통해 FBatchedElements를 생성한 후 뷰에 저장하여 나중에 섀도우 렌더링 단계에서 실제로 드로잉을 수행할 수 있도록 합니다.
5.6.3.7 UpdatePreshadowCache
초기화 단계에서 UpdatePreshadowCache를 계속 분석해 보겠습니다.
void FSceneRenderer::UpdatePreshadowCache(FSceneRenderTargets& SceneContext)
{
if (ShouldUseCachePreshadows() && !Views[0].bIsSceneCapture)
{
// 초기화(Initialize)텍스처(Texture).
if (Scene->PreshadowCacheLayout.GetSizeX() == 0)
{
const FIntPoint PreshadowCacheBufferSize = SceneContext.GetPreShadowCacheTextureResolution();
Scene->PreshadowCacheLayout = FTextureLayout(1, 1, PreshadowCacheBufferSize.X, PreshadowCacheBufferSize.Y, false, ETextureLayoutAspectRatio::None, false);
}
// 순회(Traverse/Iterate)캐싱(Cache)의 그림자(Shadow), 렌더링(Render)의 인스턴스 .
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;
// 로써 캐싱(Cache)의 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렌더링(Render)뎁스 의 .
UncachedPreShadows.Sort(FComparePreshadows());
for (int32 ShadowIndex = 0; ShadowIndex < UncachedPreShadows.Num(); ShadowIndex++)
{
TRefCountPtr<FProjectedShadowInfo> CurrentShadow = UncachedPreShadows[ShadowIndex];
// 로부터 텍스처(Texture) 내에서 preshadow까지 , 까지 , 이면 설정(Set)데이터 .
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)
{
// 그림자(Shadow) 또는 뷰(View)그림자(Shadow).
if (PreShadows.Num() || ViewDependentWholeSceneShadows.Num())
{
TArray<FGatherShadowPrimitivesPacket*,SceneRenderingAllocator> Packets;
// 순회(Traverse/Iterate)클리핑/클램핑(Clipping)그림자(Shadow).
if (GUseOctreeForShadowCulling)
{
Packets.Reserve(100);
// 탐색/조회(Find/Lookup) 및 의 그림자(Shadow)교차(Intersection)의 .
for(FScenePrimitiveOctree::TConstIterator<SceneRenderingAllocator> PrimitiveOctreeIt(Scene->PrimitiveOctree); PrimitiveOctreeIt.HasPendingNodes(); PrimitiveOctreeIt.Advance())
{
const FScenePrimitiveOctree::FNode& PrimitiveOctreeNode = PrimitiveOctreeIt.GetCurrentNode();
const FOctreeNodeContext& PrimitiveOctreeNodeContext = PrimitiveOctreeIt.GetCurrentContext();
{
// 탐색/조회(Find/Lookup)포인트 의 포인트 .
FOREACH_OCTREE_CHILD_NODE(ChildRef)
{
if(PrimitiveOctreeNode.HasChild(ChildRef))
{
// 포인트 는 개 그림자(Shadow).
const FOctreeNodeContext ChildContext = PrimitiveOctreeNodeContext.GetChildContext(ChildRef);
bool bIsInFrustum = false;
if(!bIsInFrustum)
{
// 순회(Traverse/Iterate)의 preshadow, 판정/확인 포인트 는 및 preshadow교차(Intersection).
for(int32 ShadowIndex = 0, Num = PreShadows.Num(); ShadowIndex < Num; ShadowIndex++)
{
FProjectedShadowInfo* ProjectedShadowInfo = PreShadows[ShadowIndex];
// 검사/감지(Detect)는 그림자(Shadow).
if(ProjectedShadowInfo->CasterFrustum.IntersectBox(ChildContext.Bounds.Center + ProjectedShadowInfo->PreShadowTranslation, ChildContext.Bounds.Extent))
{
bIsInFrustum = true;
break;
}
}
}
// 만약 , 이면 포인트 의 바운딩 박스(AABB) 및 뷰(View)의 그림자(Shadow)실행(Execute)교차(Intersection)검사/감지(Detect).
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;
}
}
}
// 및 개 그림자(Shadow)교차(Intersection), 이면 추가(Add)의 포인트 힙 (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 // 순회(Traverse/Iterate).
{
const int32 PacketSize = CVarParallelGatherNumPrimitivesPerPacket.GetValueOnRenderThread();
const int32 NumPackets = FMath::DivideAndRoundUp(Scene->Primitives.Num(), PacketSize);
Packets.Reserve(NumPackets);
// , 선형(Linear)순회(Traverse/Iterate), 추가(Add)개수 의 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);
}
}
// 호출(Call)병렬 For필터링 및 그림자(Shadow)교차(Intersection)의
ParallelFor(Packets.Num(),
[&Packets](int32 Index)
{
Packets[Index]->AnyThreadTask();
},
!(FApp::ShouldUseThreadingForPerformance() && CVarParallelGatherShadowPrimitives.GetValueOnRenderThread() > 0) );
// 해제(Release)리소스 .
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) // 만약 포인트
{
// 을(를) 활용하여 필터링 및 그림자(Shadow)의 .
for (FScenePrimitiveOctree::ElementConstIt NodePrimitiveIt(Node->GetElementIt()); NodePrimitiveIt; ++NodePrimitiveIt)
{
if (NodePrimitiveIt->PrimitiveFlagsCompact.bCastDynamicShadow)
{
FilterPrimitiveForShadows(NodePrimitiveIt->Bounds, NodePrimitiveIt->PrimitiveFlagsCompact, NodePrimitiveIt->PrimitiveSceneInfo, NodePrimitiveIt->Proxy);
}
}
}
else
{
// 을(를) 활용하여 정보 의 범위 순회(Traverse/Iterate), 개 필터링 및 그림자(Shadow)의 .
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)
{
// 검사/감지(Detect)는 개 preshadow。
if (PreShadows.Num() && PrimitiveFlagsCompact.bCastStaticShadow && PrimitiveFlagsCompact.bStaticLighting)
{
for (int32 ShadowIndex = 0, Num = PreShadows.Num(); ShadowIndex < Num; ShadowIndex++)
{
FProjectedShadowInfo* RESTRICT ProjectedShadowInfo = PreShadows[ShadowIndex];
// 바운딩 박스(AABB) 및 그림자(Shadow)교차(Intersection).
bool bInFrustum = ProjectedShadowInfo->CasterFrustum.IntersectBox(PrimitiveBounds.Origin, ProjectedShadowInfo->PreShadowTranslation, PrimitiveBounds.BoxExtent);
// 투영/프로젝션 의 추가(Add)PreShadowSubjectPrimitives 내에서 .
if (bInFrustum && ProjectedShadowInfo->GetLightSceneInfoCompact().AffectsPrimitive(PrimitiveBounds, PrimitiveProxy))
{
PreShadowSubjectPrimitives[ShadowIndex].Add(PrimitiveSceneInfo);
}
}
}
// 및 그림자(Shadow)교차(Intersection).
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;
// 투영/프로젝션 의 바운딩 박스(AABB)까지 라이트(Light)벡터 .
const float ProjectedDistanceFromShadowOriginAlongLightDir = PrimitiveToShadowCenter | LightDirection;
const float PrimitiveDistanceFromCylinderAxisSq = (-LightDirection * ProjectedDistanceFromShadowOriginAlongLightDir + PrimitiveToShadowCenter).SizeSquared();
const float CombinedRadiusSq = FMath::Square(ProjectedShadowInfo->ShadowBounds.W + PrimitiveBounds.SphereRadius);
// 검사/감지(Detect)는 그림자(Shadow)의 [].
if (PrimitiveDistanceFromCylinderAxisSq < CombinedRadiusSq
&& !(ProjectedDistanceFromShadowOriginAlongLightDir < 0 && PrimitiveToShadowCenter.SizeSquared() > CombinedRadiusSq)
&& ProjectedShadowInfo->CascadeSettings.ShadowBoundsAccurate.IntersectBox(PrimitiveBounds.Origin, PrimitiveBounds.BoxExtent))
{
// 로 RSM실행(Execute)거리클리핑/클램핑(Clipping).
const float MinScreenRadiusForShadowCaster = ProjectedShadowInfo->bReflectiveShadowmap ? GMinScreenRadiusForShadowCasterRSM : GMinScreenRadiusForShadowCaster;
// 스크린 스페이스해상도/크기 클리핑/클램핑(Clipping)
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;
}
// 는 계산/산출(Calculate)그림자(Shadow)(InsetShadow).
bool bCastsInsetShadows = PrimitiveProxy->CastsInsetShadow();
// 처리(Process)의 라이트(Light).
if (PrimitiveSceneInfo->LightingAttachmentRoot.IsValid())
{
FAttachmentGroupSceneInfo& AttachmentGroup = PrimitiveSceneInfo->Scene->AttachmentGroups.FindChecked(PrimitiveSceneInfo->LightingAttachmentRoot);
bCastsInsetShadows = AttachmentGroup.ParentSceneInfo && AttachmentGroup.ParentSceneInfo->Proxy->CastsInsetShadow();
}
// 검사/감지(Detect)의 조건 판정/확인 , 조건 추가(Add)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인스턴스 의 추가(Add)그림자(Shadow)인스턴스 내에서 .
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);
}
}
// 개 그림자(Shadow)인스턴스 의 추가(Add)그림자(Shadow)인스턴스 내에서 .
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 ShadowDepthTargets 할당
초기화 단계에서 AllocateShadowDepthTargets를 계속 분석합니다.
void FSceneRenderer::AllocateShadowDepthTargets(FRHICommandListImmediate& RHICmdList)
{
FSceneRenderTargets& SceneContext = FSceneRenderTargets::Get(RHICmdList);
// 그림자(Shadow)할당(Allocate)정렬 .
// 의 2d그림자(Shadow)로써 라이트(Light).
TArray<FProjectedShadowInfo*, SceneRenderingAllocator> Shadows;
// 의 2d그림자(Shadow).
TArray<FProjectedShadowInfo*, SceneRenderingAllocator> CachedSpotlightShadows;
TArray<FProjectedShadowInfo*, SceneRenderingAllocator> TranslucentShadows;
// 의 2d그림자(Shadow)
TArray<FProjectedShadowInfo*, SceneRenderingAllocator> CachedPreShadows;
TArray<FProjectedShadowInfo*, SceneRenderingAllocator> RSMShadows;
// 포인트 라이트(Light)의 cubemap, .
TArray<FProjectedShadowInfo*, SceneRenderingAllocator> WholeScenePointShadows;
// 순회(Traverse/Iterate)라이트(Light)
for (TSparseArray<FLightSceneInfoCompact>::TConstIterator LightIt(Scene->Lights); LightIt; ++LightIt)
{
const FLightSceneInfoCompact& LightSceneInfoCompact = *LightIt;
FLightSceneInfo* LightSceneInfo = LightSceneInfoCompact.LightSceneInfo;
FVisibleLightInfo& VisibleLightInfo = VisibleLightInfos[LightSceneInfo->Id];
// 개 라이트(Light)의 단계 그림자(Shadow)개 텍스처(Texture) 내에서 .
TArray<FProjectedShadowInfo*, SceneRenderingAllocator> WholeSceneDirectionalShadows;
// 순회(Traverse/Iterate)라이트(Light)의 그림자(Shadow)인스턴스 .
for (int32 ShadowIndex = 0; ShadowIndex < VisibleLightInfo.AllProjectedShadows.Num(); ShadowIndex++)
{
FProjectedShadowInfo* ProjectedShadowInfo = VisibleLightInfo.AllProjectedShadows[ShadowIndex];
// 검사/감지(Detect)그림자(Shadow)는 개 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];
}
// 검사/감지(Detect)그림자(Shadow)캐싱(Cache)로 의 조건 .
if (ProjectedShadowInfo->CacheMode == SDCM_MovablePrimitivesOnly && !ProjectedShadowInfo->HasSubjectPrims())
{
FCachedShadowMapData& CachedShadowMapData = Scene->CachedShadowMaps.FindChecked(ProjectedShadowInfo->GetLightSceneInfo().Id);
if (!CachedShadowMapData.bCachedShadowMapHasPrimitives)
{
bShadowIsVisible = false;
}
}
// 모바일 플랫폼(Mobile)렌더링(Render)오직 지원 반투명(Translucent)그림자(Shadow) 또는 CSM.
if (FeatureLevel < ERHIFeatureLevel::SM5
&& (!ProjectedShadowInfo->bPerObjectOpaqueShadow && !(ProjectedShadowInfo->bDirectionalLight && ProjectedShadowInfo->bWholeSceneShadow)))
{
bShadowIsVisible = false;
}
// 렌더링(Render) 내에서 , 동적 그림자(Shadow)까지 라이트(Light)감쇠(Falloff/Attenuation)텍스처(Texture)(light attenuation texture)의 채널/패스(Pass) 내에서 .
if (IsForwardShadingEnabled(ShaderPlatform)
&& ProjectedShadowInfo->GetLightSceneInfo().GetDynamicShadowMapChannel() == -1)
{
bShadowIsVisible = false;
}
// 그림자(Shadow), 이면 에 따라 타입/유형 추가(Add)까지 의 그림자(Shadow)인스턴스 리스트 내에서 .
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);
}
}
}
}
// 정렬 단계 그림자(Shadow), 단계 의 블렌딩(Blending)는 필수: 의 .
VisibleLightInfo.ShadowsToProject.Sort(FCompareFProjectedShadowInfoBySplitIndex());
VisibleLightInfo.RSMsToProject.Sort(FCompareFProjectedShadowInfoBySplitIndex());
// 할당(Allocate)CSM뎁스 렌더링(Render)텍스처(Texture).
AllocateCSMDepthTargets(RHICmdList, WholeSceneDirectionalShadows);
}
// 처리(Process)캐싱(Cache)의 PreShadow.
if (CachedPreShadows.Num() > 0)
{
// 생성(Create)의 PreShadow캐싱(Cache)뎁스 텍스처(Depth Texture).
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();
// 설정(Set)그림자(Shadow)뎁스 뷰(View), 렌더링(Render) 내에서 로 그림자(Shadow)까지 개 을(를) 활용하여 의 뷰(View).
ProjectedShadowInfo->SetupShadowDepthView(RHICmdList, this);
SortedShadowsForShadowDepthPass.PreshadowCache.Shadows.Add(ProjectedShadowInfo);
}
}
// 할당(Allocate)그림자(Shadow)의 렌더링(Render)텍스처(Texture), 포인트 라이트(Light)cubemap,RSM,캐싱(Cache)의 ,,반투명(Translucent)그림자(Shadow).(CSM할당(Allocate))
AllocateOnePassPointLightDepthTargets(RHICmdList, WholeScenePointShadows);
AllocateRSMDepthTargets(RHICmdList, RSMShadows);
AllocateCachedSpotlightShadowDepthTargets(RHICmdList, CachedSpotlightShadows);
AllocatePerObjectShadowDepthTargets(RHICmdList, Shadows);
AllocateTranslucentShadowDepthTargets(RHICmdList, TranslucentShadows);
// 갱신(Update)반투명(Translucent)그림자(Shadow)의 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);
}
}
}
// 을(를) 활용하여 의 그림자(Shadow)캐싱(Cache).
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);
// 순회(Traverse/Iterate)그림자(Shadow).
for (int32 AtlasIndex = 0; AtlasIndex < SortedShadowsForShadowDepthPass.ShadowMapAtlases.Num(); AtlasIndex++)
{
FSortedShadowMapAtlas& Atlas = SortedShadowsForShadowDepthPass.ShadowMapAtlases[AtlasIndex];
// 순회(Traverse/Iterate)그림자(Shadow)의 그림자(Shadow)인스턴스 , 그림자(Shadow)의 .
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);
}
}
// 순회(Traverse/Iterate)RSM그림자(Shadow), 개 의 그림자(Shadow)인스턴스 의 .
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);
}
}
// 순회(Traverse/Iterate)포인트 라이트(Light)그림자(Shadow), 개 그림자(Shadow)의 그림자(Shadow)인스턴스 의 .
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);
}
}
// 순회(Traverse/Iterate)PreShadow캐싱(Cache)의 그림자(Shadow), 그림자(Shadow)인스턴스 의 .
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);
}
// 순회(Traverse/Iterate)반투명(Translucent)그림자(Shadow), 개 의 그림자(Shadow)인스턴스 의 .
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의 주요 프로세스를 다음과 같이 간략하고 간결하게 요약합니다.
뷰, 장면 광원 및 콘솔 변수를 기반으로 그림자 관련 마커를 초기화합니다.
장면의 모든 광원 소스(Scene->Lights)를 탐색하고 다음 작업을 수행합니다.
광원이 그림자를 활성화하지 않거나 그림자 품질이 너무 낮거나 광원이 모든 뷰에서 표시되지 않는 경우 이는 무시되고 그림자 투사가 수행되지 않습니다.
점 광원 파노라마 그림자인 경우 장면의 UsedWholeScenePointLightNames 목록에 광원 구성 요소 이름을 추가합니다.
파노라마 그림자 생성 조건이 충족되면 CreateWholeSceneProjectedShadow를 호출합니다.
그림자 데이터를 초기화하고 그림자에 필요한 해상도, 전환 인자(FadeAlpha) 등을 계산합니다.
전환 인자가 너무 작은 경우(1/256 미만) 직접 반환합니다.
광원이 투사하는 그림자 수를 순회하며 해상도 계산, 위치(SizeX, SizeY) 계산, 생성할 그림자 맵 수 등을 매번 수행합니다.
섀도우 맵 수에 따라 동일한 수의 FProjectedShadowInfo 섀도우 인스턴스를 만듭니다. 각 섀도우 인스턴스에 대해 다음을 수행합니다.
파노라마 투영 매개변수(프러스텀 경계 보기, 변환 행렬, 깊이, 깊이 오프셋 등), 전환 계수, 캐시 모드 등을 설정합니다.
VisibleLightInfo.MemStackProjectedShadows 목록을 추가합니다. 단일 채널 포인트 라이트 그림자인 경우 그림자 인스턴스의 큐브맵 데이터를 채우고(투영 행렬 보기, 경계 상자, 원거리 및 근거리 클리핑 평면 등) 원뿔을 초기화합니다.
레이 트레이싱이 아닌 거리 필드 그림자에 대해 CPU 측 클리핑을 수행합니다. 광원 뷰의 볼록 몸체를 구성한 다음 광원의 움직이는 기본 목록을 순회하고 IntersectsConvexHulls를 호출하여 광원 경계 상자와 기본 경계 상자 간의 교차 테스트를 수행합니다. 교차하는 프리미티브만 섀도우 인스턴스의 기본 프리미티브 목록에 추가됩니다. 광원의 정적 기본 요소에 대해 유사한 작업을 수행합니다.
마지막으로 VisibleLightInfo.AllProjectedShadows 목록에 렌더링해야 하는 그림자 인스턴스를 추가합니다.
두 가지 유형의 조명(움직이는 조명과 고정 조명, 아직 빌드되지 않은 정적 조명)에 대해 CSM(Cascading Shadows)을 만듭니다. AddViewDependentWholeSceneShadowsForView를 호출하여 뷰와 연결된 CSM을 만듭니다.
각 보기에 대해 모든 보기를 탐색합니다.
기본 보기가 아닌 경우 다음 단계를 건너뜁니다.
뷰와 관련된 파노라마 투영 수를 가져오고, 동일한 수의 FProjectedShadowInfo 그림자 인스턴스를 생성하고, 각 그림자 인스턴스에 대한 파노라마 투영 매개변수를 설정한 다음, 마지막으로 그림자 인스턴스 목록과 잘라낼 목록에 추가합니다.
대화형 그림자(PerObject 그림자, 투명 그림자, 자체 그림자 등을 포함하여 광원과 기본 요소 사이의 영향 참조)를 처리하고, 광원의 동적 및 정적 기본 요소를 통과하고, 각 기본 요소에 대해 SetupInteractionShadows를 호출하여 대화형 그림자를 설정합니다.
광원 첨부 루트 구성 요소를 처리하고 관련 플래그를 설정합니다. 추가 루트 구성 요소가 있는 경우 다음 단계를 건너뜁니다.
- 섀도우 인스턴스를 생성해야 하는 경우 CreatePerObjectProjectedShadow를 호출하여 객체별 섀도우를 생성합니다.
모든 뷰를 탐색하고 그림자 관련 마커를 수집합니다.
이 프레임이 표시되지 않고 다음 프레임도 잠재적으로 표시되지 않는 경우 후속 그림자 생성 및 설정을 건너뛰고 직접 반환합니다.
유효한 섀도우 그룹 프리미티브가 없습니다. 직접 반환하세요.
그림자 계산을 위한 다양한 데이터(그림자 프러스텀, 해상도, 가시성 마커, 아틀라스 위치 등).
전환 인자(FadeAlpha)가 특정 임계값(1.0/256.0)보다 작으면 직접 반환합니다.
그림자 생성 조건을 만족하고 불투명 그림자인 경우 그림자 인스턴스를 생성 및 설정한 후 VisibleLightInfo.MemStackProjectedShadows 목록에 추가합니다. 이 프레임이 표시되면 그림자 인스턴스의 기본 기본 목록에 추가됩니다. 다음 프레임이 잠재적으로 표시되면 VisibleLightInfo.OccludedPerObjectShadows의 인스턴스에 추가됩니다.
그림자 생성 조건이 만족되어 반투명 그림자라면 위 단계와 유사한 작업을 수행합니다.
InitProjectedShadowVisibility를 호출하여 그림자 가시성 결정을 수행합니다.
각 광원에 대해 장면의 모든 광원을 반복합니다.
뷰 및 관련 컨테이너 내에서 투사 그림자 가시성을 할당합니다.
- 각 그림자 인스턴스에 대해 가시 광원의 모든 그림자 인스턴스를 탐색합니다.
그림자 인덱스를 저장합니다.
- 각 보기에 대해 모든 보기를 탐색합니다.
뷰 프러스텀 내에 있지 않은 조명을 건너뛰고 뷰 관련 그림자를 처리합니다.
메인 프리미티브의 뷰 관련 데이터를 계산하여 그림자 차단 여부를 판단하고 그림자 가시성 플래그를 설정합니다.
그림자가 표시되고 RSM이 아닌 경우 FViewElementPDI를 사용하여 그림자 원뿔을 그립니다.
UpdatePreshadowCache를 호출하여 이전에 미리 계산된 섀도우를 정리하고 캐시에 새 섀도우를 추가해 보세요.
텍스처 레이아웃을 초기화합니다.
캐시된 모든 사전 그림자를 반복하여 이 프레임에서 렌더링되지 않는 인스턴스를 제거합니다.
캐시할 수 있는 PreShadow 목록을 수집합니다.
PreShadow를 큰 것부터 작은 것까지 정렬합니다(PreShadow가 클수록 깊이를 렌더링할 때 더 많은 객체를 갖게 됩니다).
캐시되지 않은 모든 PreShadow를 탐색하고 텍스처 레이아웃에서 PreShadow를 위한 공간을 찾으십시오. 발견되면 관련 데이터를 설정하고 Scene->CachedPreshadows 목록에 추가합니다.
GatherShadowPrimitives를 호출하여 다양한 유형의 그림자를 처리하기 위한 프리미티브 목록을 수집합니다.
PreShadow 및 뷰 관련 파노라마 그림자(ViewDependentWholeSceneShadows)가 없으면 직접 반환합니다.
- 옥트리 순회가 허용되는 경우(GUseOctreeForShadowCulling에 의해 결정됨) 각 하위 노드에 대해 옥트리 순회 Scene->PrimitiveOctree를 사용하십시오.
하위 노드가 하나 이상의 섀도우(PreShadow 및 뷰 관련 파노라마 섀도우 포함) 내에 있는지 확인하고, 그렇다면 노드 컨테이너로 푸시합니다.
프리미티브 노드의 요소가 0보다 큰 경우 FMemStack에서 FGatherShadowPrimitivesPacket 인스턴스를 생성하고 노드의 관련 데이터를 저장한 후 FGatherShadowPrimitivesPacket 인스턴스 목록에 추가합니다.
옥트리가 아닌 순회 모드인 경우 프리미티브를 선형으로 순회하고 FGatherShadowPrimitivesPacket을 생성하여 목록에 추가합니다.
그림자와 평행하게 교차하지 않는 프리미티브를 필터링하고 그림자와 교차하는 프리미티브를 수집하려면 ParallelFor를 사용합니다.
수집의 마지막 단계에서는 섀도우 인스턴스의 SubjectPrimitive 목록에 섀도우의 영향을 받는 프리미티브를 추가하고 이전에 적용된 리소스를 정리합니다.
AllocateShadowDepthTargets를 호출하여 그림자 맵에 필요한 렌더링 텍스처를 할당합니다.
할당된 할당자를 사용하여 다양한 유형의 섀도우 목록을 초기화합니다.
- 각 광원에 대해 모든 광원을 탐색합니다.
각 그림자 인스턴스에 대해 광원의 모든 그림자 인스턴스를 반복합니다.
하나 이상의 뷰에 그림자가 표시되는지 확인합니다.
섀도우 캐시 모드가 이동 가능한 프리미티브인 경우 조건부 가시성을 감지합니다.
기타 특별한 가시성 판단.
그림자가 표시되면 유형별로 다른 그림자 인스턴스 목록에 추가합니다.
캐스케이드 사이의 블렌딩에는 순서가 필요하므로 캐스케이드 그림자를 주문하십시오.
AllocateCSMDepthTargets를 호출하여 CSM 깊이 렌더링 텍스처를 할당합니다.
PreShadow를 처리합니다.
포인트 라이트 큐브맵, RSM, 캐시된 스포트라이트, 객체별 및 투명 그림자 렌더링 텍스처를 순서대로 할당합니다.
투명 그림자 맵의 균일 버퍼를 업데이트합니다.
전혀 사용하지 않는 섀도우 캐시를 제거하세요.
GatherShadowDynamicMeshElements를 호출하여 그림자 동적 메시 요소를 수집합니다.
모든 그림자 맵 아틀라스(ShadowMapAtlases)를 반복하고 각 아틀라스 내의 모든 그림자 인스턴스의 그리드 요소를 수집합니다.
모든 RSM 섀도우 맵 아틀라스(RSMAtlases)를 탐색하고 각 아틀라스 내의 모든 섀도우 인스턴스의 그리드 요소를 수집합니다.
모든 포인트 라이트 큐브 그림자 맵(ShadowMapCubemaps)을 탐색하고 각 큐브 그림자 맵 내의 모든 그림자 인스턴스의 메쉬 요소를 수집합니다.
모든 PreShadow 캐시 섀도우 맵(PreshadowCache)을 탐색하고 섀도우 인스턴스의 그리드 요소를 수집합니다.
모든 투명 개체 그림자 맵 아틀라스(TranslucencyShadowMapAtlases)를 탐색하고 각 아틀라스에 있는 모든 그림자 인스턴스의 그리드 요소를 수집합니다.
이것으로 섀도우 초기화 요약을 마칩니다. 그림자 처리는 매우 복잡하고 많은 논리 및 최적화 기술이 필요하다는 것을 알 수 있습니다. 다음은 섀도우 초기화 단계와 관련된 최적화 기술에 대한 대략적인 요약입니다.
간단한 모양의 객체(뷰, 광원, 그림자, 기본 요소)를 사용하여 교차 테스트를 수행하고 교차하지 않는 그림자 요소를 제거합니다.
다양한 태그(객체, 뷰, 콘솔, 전역 변수 등)와 파생 태그를 사용하여 비준수 그림자 요소를 제거합니다.
중간 데이터(전환 요소, 화면 크기, 깊이 값 등)를 사용하여 비준수 그림자 요소를 제거합니다.
특수 데이터 구조(텍스처 레이아웃, 섀도우 아틀라스, 옥트리, 연속 선형 배열) 및 순회 방법(병렬, 옥트리, 선형)을 통해 실행 결과가 향상됩니다.
캐싱(PreShadowCache, 잠재 가시성 등)을 최대한 활용하여 렌더링 효과를 줄입니다.
그림자 유형을 정렬하여 렌더링 상태 전환을 줄이고, CPU 및 GPU 상호 작용 데이터를 줄이고, 캐시 적중률을 향상시킵니다.
다양한 세분성에서의 폐색 선별.
5.6.4 그림자 렌더링
위의 그림자 초기화 단계를 분석한 후, 다음으로 그림자의 렌더링 단계를 분석합니다. 그림자의 렌더링 패스는 PrePass 이후와 BasePass 이전입니다.
void FDeferredShadingSceneRenderer::Render(FRHICommandListImmediate& RHICmdList)
{
(......)
RenderPrePass(RHICmdList, ...);
(......)
// 그림자(Shadow)렌더링(Render)Pass.
RenderShadowDepthMaps(RHICmdList);
(......)
RenderBasePass(RHICmdList, ...);
(......)
}5.6.4.1 RenderShadowDepthMaps
RenderShadowDepthMaps 분석 소스 코드로 직접 이동해 보겠습니다.
// Engine\Source\Runtime\Renderer\Private\ShadowDepthRendering.cpp
void FSceneRenderer::RenderShadowDepthMaps(FRHICommandListImmediate& RHICmdList)
{
(......)
// 렌더링(Render)그림자(Shadow).
FSceneRenderer::RenderShadowDepthMapAtlases(RHICmdList);
// 렌더링(Render)포인트 라이트(Light)그림자(Shadow).
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];
// 는 로써 병렬 드로우/렌더(Draw)
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);
// 설정(Set)Uniform Buffer.
ProjectedShadowInfo->SetupShadowUniformBuffers(RHICmdList, Scene);
// 그림자(Shadow)렌더링(Render)Pass.
auto BeginShadowRenderPass = [this, &RenderTarget, &SceneContext](FRHICommandList& InRHICmdList, bool bPerformClear)
{
FRHITexture* DepthTarget = RenderTarget.TargetableTexture;
ERenderTargetLoadAction DepthLoadAction = bPerformClear ? ERenderTargetLoadAction::EClear : ERenderTargetLoadAction::ELoad;
// 렌더링(Render)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);
}
// 변환(Transform/Convert)렌더링(Render)텍스처(Texture)상태 로 .
InRHICmdList.TransitionResource(EResourceTransitionAccess::EWritable, DepthTarget);
InRHICmdList.BeginRenderPass(RPInfo, TEXT("ShadowDepthCubeMaps"));
};
// 는 그림자(Shadow).
{
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();
}
// 렌더링(Render)그림자(Shadow).
ProjectedShadowInfo->RenderDepth(RHICmdList, this, BeginShadowRenderPass, bDoParallelDispatch);
if (!bDoParallelDispatch)
{
RHICmdList.EndRenderPass();
}
// 변환(Transform/Convert)렌더링(Render)텍스처(Texture)상태 로 .
RHICmdList.TransitionResource(EResourceTransitionAccess::EReadable, RenderTarget.TargetableTexture);
}
// Preshadow캐싱(Cache).
if (SortedShadowsForShadowDepthPass.PreshadowCache.Shadows.Num() > 0)
{
FSceneRenderTargetItem& RenderTarget = SortedShadowsForShadowDepthPass.PreshadowCache.RenderTargets.DepthTarget->GetRenderTargetItem();
// 순회(Traverse/Iterate)PreshadowCache의 그림자(Shadow)인스턴스 .
for (int32 ShadowIndex = 0; ShadowIndex < SortedShadowsForShadowDepthPass.PreshadowCache.Shadows.Num(); ShadowIndex++)
{
FProjectedShadowInfo* ProjectedShadowInfo = SortedShadowsForShadowDepthPass.PreshadowCache.Shadows[ShadowIndex];
// 캐싱(Cache)의 드로우/렌더(Draw).
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();
}
// 드로우/렌더(Draw).
ProjectedShadowInfo->RenderDepth(RHICmdList, this, BeginShadowRenderPass, bDoParallelDispatch);
if (!bDoParallelDispatch)
{
RHICmdList.EndRenderPass();
}
// 드로우/렌더(Draw), 플래그 캐싱(Cache).
ProjectedShadowInfo->bDepthsCached = true;
}
}
RHICmdList.TransitionResource(EResourceTransitionAccess::EReadable, RenderTarget.TargetableTexture);
}
// 반투명(Translucent)그림자(Shadow).
for (int32 AtlasIndex = 0; AtlasIndex < SortedShadowsForShadowDepthPass.TranslucencyShadowMapAtlases.Num(); AtlasIndex++)
{
const FSortedShadowMapAtlas& ShadowMapAtlas = SortedShadowsForShadowDepthPass.TranslucencyShadowMapAtlases[AtlasIndex];
FIntPoint TargetSize = ShadowMapAtlas.RenderTargets.ColorTargets[0]->GetDesc().Extent;
// 반투명(Translucent)그림자(Shadow)을(를) 활용하여 까지 렌더링(Render)텍스처(Texture).
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"));
{
// 순회(Traverse/Iterate)반투명(Translucent)그림자(Shadow)의 그림자(Shadow)인스턴스 , 실행(Execute)반투명(Translucent)그림자(Shadow)뎁스 드로우/렌더(Draw).
for (int32 ShadowIndex = 0; ShadowIndex < ShadowMapAtlas.Shadows.Num(); ShadowIndex++)
{
FProjectedShadowInfo* ProjectedShadowInfo = ShadowMapAtlas.Shadows[ShadowIndex];
// 렌더링(Render)반투명(Translucent)그림자(Shadow)뎁스 .
ProjectedShadowInfo->RenderTranslucencyDepths(RHICmdList, this);
}
}
RHICmdList.EndRenderPass();
RHICmdList.TransitionResource(EResourceTransitionAccess::EReadable, ColorTarget0.TargetableTexture);
RHICmdList.TransitionResource(EResourceTransitionAccess::EReadable, ColorTarget1.TargetableTexture);
}
// 설정(Set)LPV의 RSM uniform Buffer, 로써 로써 병렬 드로우/렌더(Draw).
{
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();
}
}
}
}
// 렌더링(Render)RSM(Reflective Shadow Map, 반사(Reflection)그림자(Shadow)).
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();
// 그림자(Shadow)의 Uniform Buffer갱신(Update).
if (bIsWholeSceneDirectionalShadow)
{
// 을(를) 활용하여 캐싱(Cache)의 Uniform Buffer갱신(Update)그림자(Shadow)뷰(View)의 Uniform Buffer.
ShadowDepthView->ViewUniformBuffer.UpdateUniformBufferImmediate(*ShadowDepthView->CachedViewUniformShaderParameters);
// 만약 뷰(View), 순회(Traverse/Iterate)의 뷰(View)UB확장 , 실행(Execute)렌더링(Render).
if (DependentView)
{
extern TSet<IPersistentViewUniformBufferExtension*> PersistentViewUniformBufferExtensions;
for (IPersistentViewUniformBufferExtension* Extension : PersistentViewUniformBufferExtensions)
{
Extension->BeginRenderView(DependentView);
}
}
}
// 모바일 플랫폼(Mobile)플랫폼의 드로우/렌더(Draw)의 Uniform Buffer갱신(Update).
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;
}
// 설정(Set)Pass의 렌더링(Render)상태 .
FMeshPassProcessorRenderState DrawRenderState(*ShadowDepthView, PassUniformBuffer);
SetStateForShadowDepth(bReflectiveShadowmap, bOnePassPointLightShadow, DrawRenderState);
SetStateForView(RHICmdList);
if (CacheMode == SDCM_MovablePrimitivesOnly)
{
if (bDoParallelDispatch)
{
BeginShadowRenderPass(RHICmdList, false);
}
// 렌더링(Render)의 뎁스 정적 의 뎁스 .
CopyCachedShadowMap(RHICmdList, DrawRenderState, SceneRenderer, *ShadowDepthView);
if (bDoParallelDispatch)
{
RHICmdList.EndRenderPass();
}
}
// 병렬 드로우/렌더(Draw)
if (bDoParallelDispatch)
{
bool bFlush = CVarRHICmdFlushRenderThreadTasksShadowPass.GetValueOnRenderThread() > 0
|| CVarRHICmdFlushRenderThreadTasks.GetValueOnRenderThread() > 0;
FScopedCommandListWaitForTasks Flusher(bFlush);
// 렌더링(Render).
{
FShadowParallelCommandListSet ParallelCommandListSet(*ShadowDepthView, SceneRenderer, RHICmdList, CVarRHICmdShadowDeferredContexts.GetValueOnRenderThread() > 0, !bFlush, DrawRenderState, *this, BeginShadowRenderPass);
ShadowDepthPass.DispatchDraw(&ParallelCommandListSet, RHICmdList);
}
}
// 병렬 드로우/렌더(Draw)
else
{
ShadowDepthPass.DispatchDraw(nullptr, RHICmdList);
}
}이를 통해 깊이 렌더링의 깊이 패스는 일부 상태 처리가 약간 다르다는 점을 제외하면 장면의 깊이 패스와 유사하다는 것을 알 수 있습니다.
5.6.4.3 FProjectedShadowInfo::RenderTranslucencyDepths
// Engine\Source\Runtime\Renderer\Private\TranslucentLighting.cpp
void FProjectedShadowInfo::RenderTranslucencyDepths(FRHICommandList& RHICmdList, FSceneRenderer* SceneRenderer)
{
// 설정(Set)반투명(Translucent)뎁스 Pass의 Uniform Buffer.
FTranslucencyDepthPassUniformParameters TranslucencyDepthPassParameters;
SetupTranslucencyDepthPassUniformBuffer(this, RHICmdList, *ShadowDepthView, TranslucencyDepthPassParameters);
TUniformBufferRef<FTranslucencyDepthPassUniformParameters> PassUniformBuffer = TUniformBufferRef<FTranslucencyDepthPassUniformParameters>::CreateUniformBufferImmediate(TranslucencyDepthPassParameters, UniformBuffer_SingleFrame, EUniformBufferValidation::None);
// 설정(Set)MeshPassProcessor의 렌더링(Render)상태 .
FMeshPassProcessorRenderState DrawRenderState(*ShadowDepthView, PassUniformBuffer);
{
// 그림자(Shadow) 및 .
RHICmdList.SetViewport(
X, Y, 0.0f,
// ※ 주의: 뷰포트(Viewport)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,
// ※ 주의: 뷰포트(Viewport)BorderSize(는 의 2*BorderSize).
(X + BorderSize + ResolutionX),
(Y + BorderSize + ResolutionY), 1.0f);
DrawRenderState.SetDepthStencilState(TStaticDepthStencilState<false, CF_Always>::GetRHI());
// 블렌딩(Blending)상태 로 .
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);
// 선언 반투명(Translucent)뎁스 Pass처리(Process).
FTranslucencyDepthPassMeshProcessor TranslucencyDepthPassMeshProcessor(
SceneRenderer->Scene,
ShadowDepthView,
DrawRenderState,
this,
&TranslucencyDepthContext);
// 순회(Traverse/Iterate)동적 반투명(Translucent)
for (int32 MeshBatchIndex = 0; MeshBatchIndex < DynamicSubjectTranslucentMeshElements.Num(); MeshBatchIndex++)
{
const FMeshBatchAndRelevance& MeshAndRelevance = DynamicSubjectTranslucentMeshElements[MeshBatchIndex];
const uint64 BatchElementMask = ~0ull;
// 반투명(Translucent)MeshBatch추가(Add)까지 Processor 내에서 , 로써 변환(Transform/Convert)MeshDrawCommand.
TranslucencyDepthPassMeshProcessor.AddMeshBatch(*MeshAndRelevance.Mesh, BatchElementMask, MeshAndRelevance.PrimitiveSceneProxy);
}
// 순회(Traverse/Iterate)정적 반투명(Translucent).
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);
}
}
}
// 유효(Valid)의 드로우/렌더(Draw)드로우/렌더(Draw).
if (VisibleMeshDrawCommands.Num() > 0)
{
const bool bDynamicInstancing = IsDynamicInstancingEnabled(ShadowDepthView->FeatureLevel);
FRHIVertexBuffer* PrimitiveIdVertexBuffer = nullptr;
// 뷰(View)의 데이터 까지 드로우/렌더(Draw).
ApplyViewOverridesToMeshDrawCommands(*ShadowDepthView, VisibleMeshDrawCommands, DynamicMeshDrawCommandStorage, GraphicsMinimalPipelineStateSet, NeedsShaderInitialisation);
// 정렬 드로우/렌더(Draw).
SortAndMergeDynamicPassMeshDrawCommands(SceneRenderer->FeatureLevel, VisibleMeshDrawCommands, DynamicMeshDrawCommandStorage, PrimitiveIdVertexBuffer, 1);
// 드로우/렌더(Draw).
SubmitMeshDrawCommands(VisibleMeshDrawCommands, GraphicsMinimalPipelineStateSet, PrimitiveIdVertexBuffer, 0, bDynamicInstancing, 1, RHICmdList);
}
}
}반투명 객체의 그림자를 렌더링할 때는 불투명도와 달리 렌더링 단계에서 FTranslucencyDepthPassMeshProcessor를 통해 FMeshDrawCommand로 변환하는 것을 볼 수 있습니다. 유효한 지침이 있는 경우에만 지침이 정렬되고 실제로 도면이 제출됩니다.
5.6.4.4 RenderShadowDepthMapAtlases
RenderShadowDepthMaps의 시작은 RenderShadowDepthMapAtlases를 호출하여 CSM 그림자 아틀라스를 그리는 것입니다. 다음은 코드 분석입니다.
void FSceneRenderer::RenderShadowDepthMapAtlases(FRHICommandListImmediate& RHICmdList)
{
FSceneRenderTargets& SceneContext = FSceneRenderTargets::Get(RHICmdList);
// 는 을(를) 활용하여 병렬 드로우/렌더(Draw).
bool bCanUseParallelDispatch = RHICmdList.IsImmediate() &&
GRHICommandList.UseParallelAlgorithms() && CVarParallelShadows.GetValueOnRenderThread();
// 순회(Traverse/Iterate)그림자(Shadow).
for (int32 AtlasIndex = 0; AtlasIndex < SortedShadowsForShadowDepthPass.ShadowMapAtlases.Num(); AtlasIndex++)
{
const FSortedShadowMapAtlas& ShadowMapAtlas = SortedShadowsForShadowDepthPass.ShadowMapAtlases[AtlasIndex];
// 렌더링(Render)텍스처(Texture).
FSceneRenderTargetItem& RenderTarget = ShadowMapAtlas.RenderTargets.DepthTarget->GetRenderTargetItem();
FIntPoint AtlasSize = ShadowMapAtlas.RenderTargets.DepthTarget->GetDesc().Extent;
// 그림자(Shadow)렌더링(Render)Pass.
auto BeginShadowRenderPass = [this, &RenderTarget, &SceneContext](FRHICommandList& InRHICmdList, bool bPerformClear)
{
ERenderTargetLoadAction DepthLoadAction = bPerformClear ? ERenderTargetLoadAction::EClear : ERenderTargetLoadAction::ELoad;
// 렌더링(Render)Pass정보 .
FRHIRenderPassInfo RPInfo(RenderTarget.TargetableTexture, MakeDepthStencilTargetActions(MakeRenderTargetActions(DepthLoadAction, ERenderTargetStoreAction::EStore), ERenderTargetActions::Load_Store), nullptr, FExclusiveDepthStencil::DepthWrite_StencilWrite);
// 만약 지원 컬러 의 뎁스 렌더링(Render)텍스처(Texture), 이면 할당(Allocate)렌더링(Render)텍스처(Texture), 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;
// 렌더링(Render)Pass, 로써 렌더링(Render)Pass.
for (int32 ShadowIndex = 0; ShadowIndex < ShadowMapAtlas.Shadows.Num(); ShadowIndex++)
{
FProjectedShadowInfo* ProjectedShadowInfo = ShadowMapAtlas.Shadows[ShadowIndex];
const bool bDoParallelDispatch = bCanUseParallelDispatch &&
(ProjectedShadowInfo->IsWholeSceneDirectionalShadow() || CVarParallelShadowsNonWholeScene.GetValueOnRenderThread());
// 에 따라 는 병렬 추가(Add)까지 의 렌더링(Render)Pass리스트 .
if (bDoParallelDispatch)
{
// 병렬 리스트 .
ParallelShadowPasses.Add(ProjectedShadowInfo);
}
else
{
// 연속 리스트 .
SerialShadowPasses.Add(ProjectedShadowInfo);
}
}
FLightSceneProxy* CurrentLightForDrawEvent = NULL;
// 병렬 렌더링(Render).
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);
// 호출(Call)그림자(Shadow)인스턴스 의 렌더링(Render)뎁스 인터페이스 .
ProjectedShadowInfo->RenderDepth(RHICmdList, this, BeginShadowRenderPass, true);
}
}
CurrentLightForDrawEvent = nullptr;
// 병렬 드로우/렌더(Draw).
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);
}
// 호출(Call)그림자(Shadow)인스턴스 의 렌더링(Render)뎁스 인터페이스 .
ProjectedShadowInfo->RenderDepth(RHICmdList, this, BeginShadowRenderPass, false);
if(!bForceSingleRenderPass)
{
RHICmdList.EndRenderPass();
}
}
if(bForceSingleRenderPass)
{
RHICmdList.EndRenderPass();
}
}
// 변환(Transform/Convert)렌더링(Render)텍스처(Texture)로 로드/읽기(Read).
RHICmdList.TransitionResource(EResourceTransitionAccess::EReadable, RenderTarget.TargetableTexture);
}
}5.6.5 섀도우 애플리케이션
이전 두 섹션에서는 그림자의 초기화 및 렌더링 로직을 자세히 분석했는데, 렌더링된 깊이 맵이 조명에 어떻게 적용되나요? 이 섹션에서는 해당 기간의 기술적 세부 사항을 공개합니다.
5.6.5.1 렌더라이트
그림자 맵을 적용하려면 여전히 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];
(......)
// 처리(Process)그림자(Shadow)의 라이팅 .
{
SCOPED_DRAW_EVENT(RHICmdList, ShadowedLights);
(......)
bool bDirectLighting = ViewFamily.EngineShowFlags.DirectLighting;
bool bShadowMaskReadable = false;
TRefCountPtr<IPooledRenderTarget> ScreenShadowMaskTexture;
TRefCountPtr<IPooledRenderTarget> ScreenShadowMaskSubPixelTexture;
// 순회(Traverse/Iterate)라이트(Light),드로우/렌더(Draw)그림자(Shadow) 및 라이팅 함수 의 라이트(Light).
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;
(......)
// 할당(Allocate)그림자(Shadow)텍스처(Texture).
if ((bDrawShadows || bDrawLightFunction || bDrawPreviewIndicator) && !ScreenShadowMaskTexture.IsValid())
{
SceneContext.AllocateScreenShadowMask(RHICmdList, ScreenShadowMaskTexture);
bShadowMaskReadable = false;
(......)
}
// 드로우/렌더(Draw)그림자(Shadow).
if (bDrawShadows)
{
INC_DWORD_STAT(STAT_NumShadowedLights);
const FLightOcclusionType OcclusionType = GetLightOcclusionType(*LightSceneInfo.Proxy);
(......)
// 그림자(Shadow)타입/유형 .
else // (OcclusionType == FOcclusionType::Shadowmap)
{
(......)
// 그림자(Shadow).
auto ClearShadowMask = [&](TRefCountPtr<IPooledRenderTarget>& InScreenShadowMaskTexture)
{
const bool bClearLightScreenExtentsOnly = CVarAllowClearLightSceneExtentsOnly.GetValueOnRenderThread() && SortedLightInfo.SortKey.Fields.LightType != LightType_Directional;
bool bClearToWhite = !bClearLightScreenExtentsOnly;
// 그림자(Shadow)렌더링(Render)채널/패스(Pass)정보 .
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;
}
// 변환(Transform/Convert)렌더링(Render)Pass의 렌더링(Render).
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);
// 그림자(Shadow)텍스처(Texture)상태 는 .
DrawClearQuad(RHICmdList, true, FLinearColor(1, 1, 1, 1), false, 0, false, 0);
}
else
{
LightSceneInfo.Proxy->GetScissorRect(ScissorRect, View, View.ViewRect);
}
}
}
RHICmdList.EndRenderPass();
};
// 의 그림자(Shadow).
ClearShadowMask(ScreenShadowMaskTexture);
// 의 그림자(Shadow)픽셀(Pixel)텍스처(Texture).
if (ScreenShadowMaskSubPixelTexture)
{
ClearShadowMask(ScreenShadowMaskSubPixelTexture);
}
// 렌더링(Render)그림자(Shadow).
RenderShadowProjections(RHICmdList, &LightSceneInfo, ScreenShadowMaskTexture, ScreenShadowMaskSubPixelTexture, HairDatas, bInjectedTranslucentVolume);
}
// 플래그 렌더링(Render)그림자(Shadow)텍스처(Texture).
bUsedShadowMaskTexture = true;
}
(......)
// 을(를) 활용하여 그림자(Shadow)텍스처(Texture).
if (bUsedShadowMaskTexture)
{
// 그림자(Shadow)텍스처(Texture).
RHICmdList.CopyToResolveTarget(ScreenShadowMaskTexture->GetRenderTargetItem().TargetableTexture, ScreenShadowMaskTexture->GetRenderTargetItem().ShaderResourceTexture, FResolveParams(FResolveRect()));
if (ScreenShadowMaskSubPixelTexture)
{
RHICmdList.CopyToResolveTarget(ScreenShadowMaskSubPixelTexture->GetRenderTargetItem().TargetableTexture, ScreenShadowMaskSubPixelTexture->GetRenderTargetItem().ShaderResourceTexture, FResolveParams(FResolveRect()));
}
// 변환(Transform/Convert)ScreenShadowMaskTexture로 .
if (!bShadowMaskReadable)
{
RHICmdList.TransitionResource(EResourceTransitionAccess::EReadable, ScreenShadowMaskTexture->GetRenderTargetItem().ShaderResourceTexture);
if (ScreenShadowMaskSubPixelTexture)
{
RHICmdList.TransitionResource(EResourceTransitionAccess::EReadable, ScreenShadowMaskSubPixelTexture->GetRenderTargetItem().ShaderResourceTexture);
}
bShadowMaskReadable = true;
}
}
(......)
else
{
// 계산/산출(Calculate)레이턴시 라이팅 , 그림자(Shadow).
SCOPED_DRAW_EVENT(RHICmdList, StandardDeferredLighting);
SceneContext.BeginRenderingSceneColor(RHICmdList, ESimpleRenderTargetMode::EExistingColorAndDepth, FExclusiveDepthStencil::DepthRead_StencilWrite, true);
// 라이팅 개 생성(Create), 그러나 오직 라이트(Light)기록/쓰기(Write)유효(Valid)데이터 , 을(를) 활용하여 . 을(를) 활용하여 bUsedShadowMaskTexture판정/확인 는 을(를) 활용하여 ScreenShadowMaskTexture의 판정/확인 .
IPooledRenderTarget* LightShadowMaskTexture = nullptr;
IPooledRenderTarget* LightShadowMaskSubPixelTexture = nullptr;
if (bUsedShadowMaskTexture)
{
LightShadowMaskTexture = ScreenShadowMaskTexture;
LightShadowMaskSubPixelTexture = ScreenShadowMaskSubPixelTexture;
}
// 렌더링(Render)레이턴시 라이팅 , 내에서 LightShadowMaskTexture는 라이트(Light)LightSceneInfo의 그림자(Shadow)정보 .
if (bDirectLighting)
{
RenderLight(RHICmdList, &LightSceneInfo, LightShadowMaskTexture, InHairVisibilityViews, false, true);
}
SceneContext.FinishRenderingSceneColor(RHICmdList);
(......)
}
}
} // shadowed lights
}
}5.6.5.2 렌더섀도우 투영
이전 섹션에서는 광원에 대해 화면 공간의 그림자 폐색 텍스처를 다시 렌더링하기 위해 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];
// 호출(Call)렌더링(Render)그림자(Shadow).
FSceneRenderer::RenderShadowProjections(RHICmdList, LightSceneInfo, ScreenShadowMaskTexture, ScreenShadowMaskSubPixelTexture, false, false, ...);
// 순회(Traverse/Iterate)라이트(Light)의 그림자(Shadow).
for (int32 ShadowIndex = 0; ShadowIndex < VisibleLightInfo.ShadowsToProject.Num(); ShadowIndex++)
{
FProjectedShadowInfo* ProjectedShadowInfo = VisibleLightInfo.ShadowsToProject[ShadowIndex];
// 처리(Process)반투명(Translucent)그림자(Shadow).
if (ProjectedShadowInfo->bAllocated
&& ProjectedShadowInfo->bWholeSceneShadow
&& !ProjectedShadowInfo->bRayTracedDistanceField
&& (!LightSceneInfo->Proxy->HasStaticShadowing() || ProjectedShadowInfo->IsWholeSceneDirectionalShadow()))
{
(......)
}
}
// 렌더링(Render)그림자(Shadow).
RenderCapsuleDirectShadows(RHICmdList, *LightSceneInfo, ScreenShadowMaskTexture, VisibleLightInfo.CapsuleShadowsToProject, false);
// 그림자(Shadow).
for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
{
(......)
}
// 그림자(Shadow).
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);
// 라이트(Light)의 그림자(Shadow)정보 , 로써 오직 개 Pass로써 렌더링(Render).
TArray<FProjectedShadowInfo*> DistanceFieldShadows; // 거리그림자(Shadow).
TArray<FProjectedShadowInfo*> NormalShadows; // 그림자(Shadow).
// 라이트(Light)의 그림자(Shadow)인스턴스 , 에 따라 타입/유형 까지 거리 또는 그림자(Shadow)리스트 내에서 .
for (int32 ShadowIndex = 0; ShadowIndex < VisibleLightInfo.ShadowsToProject.Num(); ShadowIndex++)
{
FProjectedShadowInfo* ProjectedShadowInfo = VisibleLightInfo.ShadowsToProject[ShadowIndex];
// 거리 또는 그림자(Shadow).
if (ProjectedShadowInfo->bRayTracedDistanceField)
{
DistanceFieldShadows.Add(ProjectedShadowInfo);
}
else
{
NormalShadows.Add(ProjectedShadowInfo);
if (ProjectedShadowInfo->bAllocated && ProjectedShadowInfo->RenderTargets.DepthTarget
&& !bMobileModulatedProjections)
{
// 변환(Transform/Convert)리소스 상태 로 .
RHICmdList.TransitionResource(EResourceTransitionAccess::EReadable, ProjectedShadowInfo->RenderTargets.DepthTarget->GetRenderTargetItem().ShaderResourceTexture.GetReference());
}
}
}
// 그림자(Shadow)렌더링(Render).
if (NormalShadows.Num() > 0)
{
// 렌더링(Render)그림자(Shadow)인터페이스 .
auto RenderShadowMask = [&](const FHairStrandsVisibilityViews* HairVisibilityViews)
{
// 로 뷰(View)렌더링(Render)그림자(Shadow).
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);
// 갱신(Update)의 Uniform Buffer.
Scene->UniformBuffers.UpdateViewUniformBuffer(View);
// 투영/프로젝션 그림자(Shadow)뎁스 버퍼(Depth Buffer)까지 .
for (int32 ShadowIndex = 0; ShadowIndex < NormalShadows.Num(); ShadowIndex++)
{
FProjectedShadowInfo* ProjectedShadowInfo = NormalShadows[ShadowIndex];
if (ProjectedShadowInfo->bAllocated && ProjectedShadowInfo->FadeAlphas[ViewIndex] > 1.0f / 256.0f)
{
// 렌더링(Render)포인트 라이트(Light)그림자(Shadow).
if (ProjectedShadowInfo->bOnePassPointLightShadow)
{
ProjectedShadowInfo->RenderOnePassPointLightProjection(RHICmdList, ViewIndex, View, bProjectingForForwardShading, HairVisibilityData);
}
else // 라이트(Light)그림자(Shadow).
{
ProjectedShadowInfo->RenderProjection(RHICmdList, ViewIndex, &View, this, bProjectingForForwardShading, bMobileModulatedProjections, HairVisibilityData);
}
}
}
}
};
(......)
else // 렌더링(Render)그림자(Shadow).
{
// 렌더링(Render)Pass정보 , 렌더링(Render)텍스처(Texture)는 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"));
// 렌더링(Render)그림자(Shadow).
RenderShadowMask(nullptr);
RHICmdList.SetScissorRect(false, 0, 0, 0, 0);
RHICmdList.EndRenderPass();
}
// 픽셀(Pixel)그림자(Shadow).
if (!bMobileModulatedProjections && ScreenShadowMaskSubPixelTexture && InHairVisibilityViews)
{
(......)
}
}
// 거리그림자(Shadow).
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);
// 검사/감지(Detect)그림자(Shadow)의 뷰(View)플래그 는 활성화(Enable), 활성화(Enable)반환(Return).
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();
}
// 페치/가져오기(Fetch)블렌딩(Blending)상태 , 로직 의 .
GraphicsPSOInit.BlendState = GetBlendStateForProjection(bProjectingForForwardShading, bMobileModulatedProjections);
GraphicsPSOInit.PrimitiveType = IsWholeSceneDirectionalShadow() ? PT_TriangleStrip : PT_TriangleList;
{
uint32 LocalQuality = GetShadowQuality();
// 처리(Process)그림자(Shadow)품질 및 그림자(Shadow)해상도 .
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);
}
}
// 바인딩(Bind)포인트
GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GetVertexDeclarationFVector4();
// 바인딩(Bind)그림자(Shadow)투영/프로젝션 shader.
BindShadowProjectionShaders(LocalQuality, RHICmdList, GraphicsPSOInit, ViewIndex, *View, HairVisibilityData, this, bMobileModulatedProjections);
if (bDepthBoundsTestEnabled)
{
SetDepthBoundsTest(RHICmdList, CascadeSettings.SplitNear, CascadeSettings.SplitFar, View->ViewMatrices.GetProjectionMatrix());
}
RHICmdList.SetStencilRef(0);
}
// 실행(Execute)스크린 스페이스의 드로우/렌더(Draw).
if (IsWholeSceneDirectionalShadow()) // 그림자(Shadow).
{
// 설정(Set)포인트 buffer.
RHICmdList.SetStreamSource(0, GClearVertexBuffer.VertexBufferRHI, 0);
// 호출(Call)드로우/렌더(Draw).
RHICmdList.DrawPrimitive(0, 2, 1);
}
else
{
// 동적 생성(Create)포인트 버퍼 .
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);
// 설정(Set)포인트 버퍼 드로우/렌더(Draw).
RHICmdList.SetStreamSource(0, VertexBufferRHI, 0);
// Draw the frustum using the projection shader..
RHICmdList.DrawIndexedPrimitive(GCubeIndexBuffer.IndexBufferRHI, 0, 0, 8, 0, 12, 1);
// 해제(Release)포인트 버퍼 .
VertexBufferRHI.SafeRelease();
}
// 스텐실 버퍼(Stencil Buffer)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)
{
// 레이턴시 렌더링(Render)개 라이트(Light)4개 채널/패스(Pass)(RGBA).
// CSM 및 그림자(Shadow)의 채널/패스(Pass), 로써 CSM까지 계산/산출(Calculate)그림자(Shadow)그림자(Shadow)로써 거리.
// 회 테이블 그림자(Shadow)개 의 채널/패스(Pass).
FRHIBlendState* BlendState = nullptr;
// 렌더링(Render)
if (bProjectingForForwardShading)
{
(......)
}
else // 레이턴시 렌더링(Render)
{
// 라이팅 감쇠(Falloff/Attenuation)(ScreenShadowMaskTexture)의 채널/패스(Pass)할당(Allocate):
// R: WholeSceneShadows, non SSS
// G: WholeSceneShadows, SSS
// B: non WholeSceneShadows, non SSS
// A: non WholeSceneShadows, SSS
//
// 로써 :
// SSS: 회 테이블 산란 머티리얼(Material).
// non SSS: 반투명(Translucent)그림자(Shadow).
// WholeSceneShadows: CSM.
// non WholeSceneShadows: 、그림자(Shadow)、반투명(Translucent)라이팅 、(omni-directional lights).
if (bIsWholeSceneDirectionalShadow) // 그림자(Shadow).
{
// 블렌딩(Blending)로직 매칭 FCompareFProjectedShadowInfoBySplitIndex의 . 블렌딩(Blending)그림자(Shadow)렌더링(Render).
// 그림자(Shadow)오직 활성화(Enable)R 및 G채널/패스(Pass).
if (bUseFadePlane) // 을(를) 활용하여 .
{
// alpha채널/패스(Pass)을(를) 활용하여 레벨 단계 . BO_Min, 이므로 B 및 A채널/패스(Pass)을(를) 활용하여 반투명(Translucent)그림자(Shadow).
BlendState = TStaticBlendState<CW_RG, BO_Add, BF_SourceAlpha, BF_InverseSourceAlpha>::GetRHI();
}
else // 을(를) 활용하여 .
{
// CSM개 레벨 단계 . BO_Min는 를 위해 개 그림자(Shadow)채널/패스(Pass).
BlendState = TStaticBlendState<CW_RG, BO_Min, BF_One, BF_One>::GetRHI();
}
}
else // 그림자(Shadow).
{
if (bMobileModulatedProjections) // 색조/톤(Hue/Tone)그림자(Shadow).
{
// 색조/톤(Hue/Tone)그림자(Shadow), 무시/스킵(Ignore)Alpha.
BlendState = TStaticBlendState<CW_RGB, BO_Add, BF_Zero, BF_SourceColor, BO_Add, BF_Zero, BF_One>::GetRHI();
}
else // 색조/톤(Hue/Tone)그림자(Shadow).
{
// BO_Min는 를 위해 개 그림자(Shadow)채널/패스(Pass).
BlendState = TStaticBlendState<CW_BA, BO_Min, BF_One, BF_One, BO_Min, BF_One, BF_One>::GetRHI();
}
}
}
return BlendState;
}
// 바인딩(Bind)그림자(Shadow)투영/프로젝션 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;
}
// 반투명(Translucent)그림자(Shadow).
if (ShadowInfo->bTranslucentShadow)
{
(......)
}
// 그림자(Shadow).
else if (ShadowInfo->IsWholeSceneDirectionalShadow())
{
// PCF그림자(Shadow).
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);
}
}
}
}
// 로컬 라이트(Light)/그림자(Shadow).
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);
}
}
}
}
}
// 바인딩(Bind)그림자(Shadow)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>();
// 바인딩(Bind)shader 및 렌더링(Render)상태 .
GraphicsPSOInit.BoundShaderState.VertexShaderRHI = VertexShader.GetVertexShader();
GraphicsPSOInit.BoundShaderState.PixelShaderRHI = PixelShader.GetPixelShader();
SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit);
// 설정(Set)shader파라미터 .
VertexShader->SetParameters(RHICmdList, View, ShadowInfo);
PixelShader->SetParameters(RHICmdList, ViewIndex, View, HairVisibilityData, ShadowInfo);
}5.6.5.4 FShadowProjectionNoTransformVS 및 TShadowProjectionPS
이전 섹션에서 볼 수 있듯이 다양한 유형의 섀도우는 서로 다른 VS 및 PS를 호출합니다. 대표적인 파노라마 그림자를 예로 들어 C++ 및 셰이더 로직에서 FShadowProjectionNoTransformVS 및 TShadowProjectionPS의 사용을 분석합니다.
// Engine\Source\Runtime\Renderer\Private\ShadowRendering.h
// 그림자(Shadow)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;
}
// 설정(Set)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);
}
};
// 그림자(Shadow)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();
// 설정(Set)그림자(Shadow)렌더링(Render)의 shader바인딩(Bind).(코드 )
FShadowProjectionPixelShaderInterface::SetParameters(RHICmdList, ViewIndex, View, HairVisibilityData, ShadowInfo);
const bool bUseFadePlaneEnable = ShadowInfo->CascadeSettings.FadePlaneLength > 0;
// 설정(Set)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()));
// 바인딩(Bind)레이턴시 라이트(Light)의 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();
}
// 바인딩(Bind)텍스처(Texture)리소스 .
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);
};
// 설정(Set)그림자(Shadow)의 shader바인딩(Bind)파라미터 .
void FShadowProjectionShaderParameters::Set(FRHICommandList& RHICmdList, FShader* Shader, const FSceneView& View, const FProjectedShadowInfo* ShadowInfo, const FHairStrandsVisibilityData* HairVisibilityData, bool bModulatedShadows, bool bUseFadePlane)
{
FRHIPixelShader* ShaderRHI = RHICmdList.GetBoundPixelShader();
// 설정(Set)텍스처(Texture).
SceneTextureParameters.Set(RHICmdList, ShaderRHI, View.FeatureLevel, ESceneTextureSetupMode::All);
const FIntPoint ShadowBufferResolution = ShadowInfo->GetShadowBufferResolution();
// 그림자(Shadow) 및 해상도/크기 .
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);
}
// 설정(Set)로부터 좌표 변환(Transform/Convert)까지 그림자(Shadow)뎁스 텍스처(Depth Texture)좌표 의 변환(Transform)모멘트(Moment).
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);
}
// 그림자(Shadow)스케일 .
if (SoftTransitionScale.IsBound())
{
const float TransitionSize = ShadowInfo->ComputeTransitionSize();
SetShaderValue(RHICmdList, ShaderRHI, SoftTransitionScale, FVector(0, 0, 1.0f / TransitionSize));
}
// 그림자(Shadow)버퍼 해상도/크기 .
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));
}
// ------ 처리(Process)그림자(Shadow)뎁스 텍스처(Depth Texture) ------
FRHITexture* ShadowDepthTextureValue;
if (ShadowInfo->RenderTargets.DepthTarget)
{
// 만약 그림자(Shadow)뎁스 텍스처(Depth Texture), 이면 로써 PS의 ShadowDepthTextureValue.
ShadowDepthTextureValue = ShadowInfo->RenderTargets.DepthTarget->GetRenderTargetItem().ShaderResourceTexture.GetReference();
}
// 반투명(Translucent)그림자(Shadow)뎁스 텍스처(Depth Texture).
else
{
ShadowDepthTextureValue = GSystemTextures.BlackDummy->GetRenderTargetItem().ShaderResourceTexture.GetReference();
}
// 그림자(Shadow)뎁스 텍스처(Depth Texture)의 샘플러(Sampler), ※ 주의: 는 포인트 샘플링, 는 Clamp.
FRHISamplerState* DepthSamplerState = TStaticSamplerState<SF_Point,AM_Clamp,AM_Clamp,AM_Clamp>::GetRHI();
// 설정(Set)그림자(Shadow)뎁스 텍스처(Depth Texture) 및 샘플러(Sampler).
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);
}
// 라이트(Light) 또는 위치 .
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));
}
(......)
// 그림자(Shadow)파라미터 .
SetShaderValue(RHICmdList, ShaderRHI, PerObjectShadowFadeStart, ShadowInfo->PerObjectShadowFadeStart);
SetShaderValue(RHICmdList, ShaderRHI, InvPerObjectShadowFadeLength, ShadowInfo->InvPerObjectShadowFadeLength);
}위는 C++ 레이어의 FShadowProjectionNoTransformVS 및 TShadowProjectionPS의 로직을 자세히 분석했습니다. 해당 셰이더 코드로 이동해 보겠습니다.
// 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 // 을(를) 활용하여 변환(Transform).
// 변환(Transform/Convert)위치 까지 클리핑/클램핑(Clipping).
float3 WorldPosition = InPosition.xyz * StencilingGeometryPosAndScale.w + StencilingGeometryPosAndScale.xyz;
OutPosition = mul(float4(WorldPosition,1), View.TranslatedWorldToClip);
#else // 을(를) 활용하여 변환(Transform).
// 위치 클리핑/클램핑(Clipping), 변환(Transform).
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);
(......)
// 계산/산출(Calculate)스크린 스페이스/그림자(Shadow)/의 좌표 .
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;
// 계산/산출(Calculate)그림자(Shadow)의 좌표 ( 내에서 그림자(Shadow)클리핑/클램핑(Clipping)좌표 로써 w, 로써 변환(Transform/Convert)까지 그림자(Shadow)의 스크린 스페이스)
float ShadowZ = ShadowPosition.z;
ShadowPosition.xyz /= ShadowPosition.w;
#if MODULATED_SHADOWS
ShadowPosition.xy *= ShadowTileOffsetAndSize.zw;
ShadowPosition.xy += ShadowTileOffsetAndSize.xy;
#endif
// PCSS그림자(Shadow).
#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
// 조정(Adjust)그림자(Shadow)(라이트(Light))의 뎁스 까지 0.99999f, 이므로 그림자(Shadow)뎁스 버퍼(Depth Buffer)클리어 까지 1의 . 로써 라이트(Light)의 픽셀(Pixel)차폐/오클루전 .
float LightSpacePixelDepthForOpaque = min(ShadowZ, 0.99999f);
// SSS의 뎁스 조정(Adjust), 이므로 회 테이블 의 필수: .
float LightSpacePixelDepthForSSS = ShadowZ;
// 그림자(Shadow)실행(Execute), 는 1는 0.
float PerObjectDistanceFadeFraction = 1.0f - saturate((LightSpacePixelDepthForSSS - PerObjectShadowFadeStart) * InvPerObjectShadowFadeLength);
// 초기화(Initialize)그림자(Shadow) 및 파라미터 .
float Shadow = 1;
float SSSTransmission = 1;
float BlendFactor = 1;
// 필터링 의 그림자(Shadow).
#if UNFILTERED_SHADOW_PROJECTION
{
// 대비 조정(Adjust)의 라이트(Light)(그림자(Shadow))의 픽셀(Pixel)뎁스 및 픽셀(Pixel)좌표 의 그림자(Shadow)뎁스 의 r채널/패스(Pass).
// 만약 <, 차폐/오클루전 , Shadow=1; , Shadow=0.
Shadow = LightSpacePixelDepthForOpaque < Texture2DSampleLevel(ShadowDepthTexture, ShadowDepthTextureSampler, ShadowPosition.xy, 0).r;
}
// 반투명(Translucent)그림자(Shadow).
#elif APPLY_TRANSLUCENCY_SHADOWS
{
Shadow = CalculateTranslucencyShadowing(ShadowPosition.xy, ShadowZ);
}
// PCSS그림자(Shadow).
#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그림자(Shadow).
#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).
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
// 회 테이블 그림자(Shadow).
#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그림자(Shadow), 이면 을(를) 활용하여 ShadowSharpen조정(Adjust)그림자(Shadow)의 , Half Lambert의 .
#if !USE_PCSS
Shadow = saturate( (Shadow - 0.5) * ShadowSharpen + 0.5 );
#endif
// 그림자(Shadow). 0는 그림자(Shadow), 1는 . 반환(Return)컬러 , 는 그림자(Shadow)기록/쓰기(Write)씬 컬러(SceneColor).
float FadedShadow = lerp(1.0f, Square(Shadow), ShadowFadeFraction * PerObjectDistanceFadeFraction);
#if FORWARD_SHADING
(......)
#else
// 포인트 라이트(Light)그림자(Shadow)까지 b채널/패스(Pass).
OutColor.b = EncodeLightAttenuation(FadedShadow);
OutColor.rga = 1;
// SSS그림자(Shadow), , 그러나 그림자(Shadow).
OutColor.a = OutColor.b;
#endif
#if USE_FADE_PLANE || SUBPIXEL_SHADOW
// 만약 는 CSM, 이면 출력Alpha채널/패스(Pass)블렌딩(Blending).
if (bUseFadePlane)
{
OutColor.a = BlendFactor;
}
#endif
// 그림자(Shadow).
#if MODULATED_SHADOWS
OutColor.rgb = lerp(ModulatedShadowColor.rgb, float3(1, 1, 1), FadedShadow);
OutColor.a = 0;
#endif
}
(......)위에서 볼 수 있듯이 ScreenShadowMaskTexture를 그릴 때 화면 공간에서 수행되며 ScreenShadowMask는 광원의 그림자 인스턴스 수에 따라 여러 번 겹쳐집니다. 채널마다 다른 유형의 그림자 정보가 저장됩니다.
5.6.5.5 렌더라이트
이 섹션에서는 광원 및 ScreenShadowMaskTexture 정보를 사용할 수 있게 된 후 어떻게 사용하는지 분석합니다. 그림자 관련 로직을 분석하려면 RenderLight를 직접 입력하세요.
void FDeferredShadingSceneRenderer::RenderLight(FRHICommandList& RHICmdList, const FLightSceneInfo* LightSceneInfo, IPooledRenderTarget* ScreenShadowMaskTexture, ...)
{
(......)
// 순회(Traverse/Iterate)view
for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
{
(......)
// .
if (LightSceneInfo->Proxy->GetLightType() == LightType_Directional)
{
(......)
else
{
FDeferredLightPS::FPermutationDomain PermutationVector;
(......)
// 선언 라이팅 픽셀(Pixel)셰이딩 FDeferredLightPS인스턴스 .
TShaderMapRef< FDeferredLightPS > PixelShader( View.ShaderMap, PermutationVector );
GraphicsPSOInit.BoundShaderState.PixelShaderRHI = PixelShader.GetPixelShader();
(......)
SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit);
// ScreenShadowMaskTexture바인딩(Bind)까지 픽셀(Pixel)셰이딩 내에서 .
PixelShader->SetParameters(RHICmdList, View, LightSceneInfo, ScreenShadowMaskTexture, (bHairLighting) ? &RenderLightParams : nullptr);
}
(......)
// 드로우/렌더(Draw)풀스크린(Full Screen)모멘트(Moment), 로써 스크린 스페이스계산/산출(Calculate)라이팅 .
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 // .
{
(......)
}
}
}위에서 볼 수 있듯이 ScreenShadowMaskTexture는 PixelShader->SetParameters를 호출하여 셰이더에 바인딩됩니다. 바인딩 프로세스는 다음과 같이 분석됩니다.
// 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())
{
// 바인딩(Bind)ScreenShadowMaskTexture까지 LightAttenuationTexture
SetTextureParameter(
RHICmdList,
ShaderRHI,
LightAttenuationTexture,
LightAttenuationTextureSampler,
TStaticSamplerState<SF_Point,AM_Wrap,AM_Wrap,AM_Wrap>::GetRHI(),
ScreenShadowMaskTexture ? ScreenShadowMaskTexture->GetRenderTargetItem().ShaderResourceTexture : GWhiteTexture->TextureRHI
);
}
(......)
}위에서 볼 수 있듯이 광원의 그림자 폐색 텍스처 이름은 셰이더에서 LightAttenuationTexture로 지정되고 사용자 이름은 LightAttenuationTextureSampler로 지정됩니다.
5.6.5.6 그림자 셰이더 논리
키워드 LightAttenuationTexture에 대한 모든 셰이더 파일을 검색합니다. common.ush에서 관련 논리를 찾는 것은 어렵지 않습니다.
// Engine\Shaders\Private\Common.ush
(......)
// 선언 그림자(Shadow)변수.
Texture2D LightAttenuationTexture;
SamplerState LightAttenuationTextureSampler;
(......)
// 페치/가져오기(Fetch)픽셀(Pixel)의 라이팅 감쇠(Falloff/Attenuation)(즉, 그림자(Shadow))의 .
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;
// ※ 주의: 호출(Call)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)
{
// 호출(Call)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;
(......)
// 페치/가져오기(Fetch)그림자(Shadow)데이터 , 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)
{
// 매핑 라이팅 감쇠(Falloff/Attenuation)버퍼 . (ShadowRendering.cpp)
// LightAttenuation의 데이터 :
// LightAttenuation.x: 그림자(Shadow).
// LightAttenuation.y: SSS그림자(Shadow).
// LightAttenuation.z: 라이팅 함수 +그림자(Shadow).
// LightAttenuation.w: SSS그림자(Shadow).
// 로부터 의 GBuffer채널/패스(Pass)페치/가져오기(Fetch)정적 그림자(Shadow).
float UsesStaticShadowMap = dot(LightData.ShadowMapChannelMask, float4(1, 1, 1, 1));
// 복구 정적 그림자(Shadow).
float StaticShadowing = lerp(1, dot(GBuffer.PrecomputedShadowFactors, LightData.ShadowMapChannelMask), UsesStaticShadowMap);
if (LightData.bRadialLight) // 라이트(Light)
{
// 복구 그림자(Shadow)데이터 .
// 테이블 그림자(Shadow) = (라이팅 함수 +그림자(Shadow)) * 정적 그림자(Shadow).
Shadow.SurfaceShadow = LightAttenuation.z * StaticShadowing;
Shadow.TransmissionShadow = LightAttenuation.w * StaticShadowing;
Shadow.TransmissionThickness = LightAttenuation.w;
}
else // 라이트(Light)()
{
// 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);
}
}
// 그림자(Shadow).
#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에 캐스팅하고 겹쳐 놓습니다. 이것의 목적은 지연된 셰이딩과 유사하게 여러 그림자를 뷰의 뷰 공간에 미리 병합하여 후속 조명 단계에서 그림자 정보를 얻기 위해 한 번만 샘플링하면 렌더링 효율성을 향상시키는 것입니다. 단점은 광원의 그림자를 병합하고 Draw Call을 추가하기 위해 하나의 패스가 더 필요하며 또한 약간의 비디오 메모리 소비가 증가한다는 것입니다.
기존 깊이 맵 그림자 외에도 ScreenShadowMaskTexture에는 거리 필드 그림자, 하위 표면 그림자, 투명 볼륨 그림자, 캡슐 그림자, 높이 필드 그림자, 머리카락 그림자, RT 그림자 및 기타 유형이 포함될 수 있습니다. 각 채널에는 특별한 목적이 있으며 다양한 유형의 그림자 정보를 세분화하고 저장하는 데 사용됩니다.
ScreenShadowMaskTexture를 렌더링하는 셰이더는 그림자 맵을 필터링할 때 필터링 없음, PCSS 및 사용자 정의 PCF의 세 가지 필터링 방법을 지원합니다.
조명 계산 셰이더에서 ScreenShadowMaskTexture는 LightAttenuationTexture(빛 감쇠 텍스처)의 변수에 바인딩됩니다. 즉, C++ 레이어의 ScreenShadowMaskTexture는 실제로 조명 셰이더의 LightAttenuationTexture입니다.
조명 셰이더 파일 DeferredLightPixelShaders.usf에서 동적 조명을 계산할 때 GetPerPixelLightAttenuation() 값은 한 번만 호출되며(즉, LightAttenuationTexture는 한 번만 샘플링됨) 객체 표면의 그림자에 영향을 받는 조명 결과를 계산할 수 있습니다.
5.6.6 UE 섀도우 요약
UE 섀도우 시스템의 복잡성은 저자의 원래 상상을 훨씬 뛰어넘습니다. 그림자 장에서는 그림자의 주요 렌더링 과정과 기본 기술만을 분석합니다. 다른 유형의 그림자는 포함되지 않습니다. 이미 2만 단어가 넘는 길이로, 원래의 상상과 기획을 훨씬 뛰어넘는 수준이다.
다음 그림에는 섀도우 렌더링의 주요 프로세스와 주요 호출이 요약되어 있습니다.
그래프 TD A(그림자 초기화) -->|InitDynamicShadows| B(그림자 맵 렌더링) B -->|RenderShadowDepthMaps| C(ScreenShadowMaskTexture 렌더링) C -->|RenderShadowProjections| D(빛 감쇠 텍스처 샘플링) D -->|GetPerPixelLightAttenuation| E(표면 그림자 계산) E -->|GetShadowTerms| F(표면 조명 계산) F -->|GetDynamicLighting| G(출력 결과)
위의 가장 눈에 띄는 점은 GBuffer의 기하학적 데이터, 특수 혼합 모드 및 미세 채널 할당을 최대한 활용하여 광원의 복잡한 그림자 데이터를 화면 공간의 광 감쇠 텍스처로 미리 렌더링하는 ScreenShadowMaskTexture 렌더링 단계가 추가된 것입니다. 따라서 조명 계산 중에 광 감쇠 텍스처를 한 번만 샘플링하여 표면 그림자를 재구성하면 됩니다.
ScreenShadowMaskTexture를 렌더링하기 위해 추가 패스를 추가하면 그리기 호출이 증가하고 비디오 메모리 소비가 증가한다는 점을 지적해야 합니다. 단순한 빛과 그림자 장면의 경우 성능 저하가 발생할 수 있습니다.
5.7 이 기사 요약
이 글에서는 UE의 직접 조명 및 그림자 렌더링 프로세스와 주요 알고리즘을 주로 설명하여 독자들이 빛과 그림자 렌더링에 대한 전반적인 이해를 가질 수 있도록 합니다. 보다 기술적인 세부 사항과 원리에 관해서는 독자들이 UE 소스 코드를 직접 연구해야 합니다.
이 글의 길이는 50,000 단어가 넘습니다. 지금까지 이 시리즈 중 가장 긴 글이지만, 작성하는 데 가장 짧은 시간이 걸린 글이기도 하다. 5월은 참으로 효율적인 달이다.
바로 어제(2021.5.27), 오랫동안 기다려온 UE5가 드디어 프리뷰 버전을 출시했습니다. 저자는 UE5의 차세대 빛과 그림자의 매력을 감상하기 위해 즉시 다운로드하고 간략하게 체험해 보았습니다.

UE5 멋진 시작 화면.
저자는 사용된 Lumen 및 Nanite의 기술 원리와 구현 세부 사항을 분석하기 위해 UE5에 대한 특별 기사를 작성하는 데 시간을 할애할 수 있습니다.
5.7.1 이 기사에 대한 생각
평소와 마찬가지로 이 문서에서는 UE BasePass 및 LightingPass에 대한 숙달과 이해를 심화하고 이해하는 데 도움이 되는 몇 가지 작은 생각도 정리했습니다.
UE의 광원 종류와 주요 렌더링 과정에 대해 간략하게 설명해주세요.
UE의 섀도우 종류와 주요 렌더링 과정에 대해 간략하게 설명해주세요.
UE 공식 버전에서 Area Light 하에서 투명한 물체가 비춰질 수 없는 문제를 수정해주세요.
UE의 조명 채널 수를 확장합니다.
각 그리드가 독립적으로 조명될 수 있도록 그리드별 가상 광원을 구현하고 불투명, 마스크, 반투명 개체를 지원하세요.
특별 지침
모든 참고문헌의 저자에게 감사드립니다. 일부 사진은 참고 자료와 인터넷에서 가져온 것이므로 삭제되었습니다.
이 시리즈의 기사는 저자가 직접 작성한 것이며 블로그에만 게시됩니다. 이 글의 링크를 공유하셔도 좋지만, 무단 전재를 금합니다!
계속되는 일련의 기사. 전체 목록을 보려면 콘텐츠 개요를 클릭하세요.
계속되는 일련의 기사. 전체 목록을 보려면 콘텐츠 개요를 클릭하세요.
계속되는 일련의 기사. 전체 목록을 보려면 콘텐츠 개요를 클릭하세요.
📚 참고 자료 및 공식 링크 (References)
- 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