// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "HeterogeneousVolumesAdaptiveVolumetricShadowMapUtils.ush" #include "HeterogeneousVolumesBeerShadowMapUtils.ush" #define AVSM_SAMPLE_MODE_DISABLED 0 #define AVSM_SAMPLE_MODE_TWO_LEVEL_VECTORIZED 1 #define AVSM_SAMPLE_MODE_TWO_LEVEL_VECTORIZED_LOOP 2 //#define AVSM_SAMPLE_MODE_TWO_LEVEL_REFERENCE 3 //#define AVSM_SAMPLE_MODE_BINARY_SEARCH 4 //#define AVSM_SAMPLE_MODE_REFERENCE 5 #ifndef AVSM_SAMPLE_MODE #define AVSM_SAMPLE_MODE AVSM_SAMPLE_MODE_TWO_LEVEL_VECTORIZED_LOOP #endif // AVSM_SAMPLE_MODE // Adaptive Volumetric Shadow Map implementation struct FAdaptiveVolumetricShadowMap { float4x4 TranslatedWorldToShadow[6]; float3 TranslatedWorldOrigin[6]; float4 TranslatedWorldPlane[6]; float4 SplitDepths[6]; float DownsampleFactor; int2 Resolution; int NumShadowMatrices; int MaxSampleCount; bool bIsEmpty; bool bIsDirectionalLight; StructuredBuffer IndirectionBuffer; StructuredBuffer SampleBuffer; Texture2D RadianceTexture; SamplerState TextureSampler; }; bool AVSM_PixelAndDepth( inout FAdaptiveVolumetricShadowMap ShadowMap, int Face, float3 TranslatedWorldPosition, inout float2 Pixel, inout float Depth ) { Depth = 0.0; Pixel = -1; float4 ShadowPositionAsFloat4 = mul(float4(TranslatedWorldPosition, 1), ShadowMap.TranslatedWorldToShadow[Face]); if (ShadowPositionAsFloat4.w > 0) { float3 ShadowPosition = ShadowPositionAsFloat4.xyz / ShadowPositionAsFloat4.w; if (all(ShadowPosition.xyz > 0) || all(ShadowPosition.xy < 1)) { Pixel = clamp(ShadowPosition.xy * ShadowMap.Resolution - 0.5, 0, ShadowMap.Resolution - 1); if (ShadowMap.bIsDirectionalLight) { Depth = dot(ShadowMap.TranslatedWorldPlane[Face].xyz, TranslatedWorldPosition) + ShadowMap.TranslatedWorldPlane[Face].w; } return true; } } return false; } uint AVSM_LinearIndex( inout FAdaptiveVolumetricShadowMap ShadowMap, uint Face, uint2 PixelCoord ) { uint LinearIndex = Face * ShadowMap.Resolution.x * ShadowMap.Resolution.y + PixelCoord.y * ShadowMap.Resolution.x + PixelCoord.x; return LinearIndex; } float SafeRcp(float Value) { return Value > 0 ? rcp(Value) : 0.0; } float SafeLog(float Value) { return Value > 0 ? log(Value) : 0.0; } #define INTERPOLANT_TYPE_LINEAR 0 #define INTERPOLANT_TYPE_EXPONENTIAL 1 #define INTERPOLANT_TYPE_EXPONENTIAL_FIT 2 #define INTERPOLANT_TYPE INTERPOLANT_TYPE_EXPONENTIAL_FIT float Interpolate( FAVSMSampleData ShadowData[2], float WorldHitT ) { float Transmittance = ShadowData[0].Tau; if (INTERPOLANT_TYPE == INTERPOLANT_TYPE_LINEAR) { // Linear interpolant float DeltaX = max(ShadowData[1].X - ShadowData[0].X, 0.0); float X = clamp(WorldHitT - ShadowData[0].X, 0.0, DeltaX); float BlendWeight = saturate(X * SafeRcp(DeltaX)); Transmittance = lerp(ShadowData[0].Tau, ShadowData[1].Tau, BlendWeight); } else if (INTERPOLANT_TYPE == INTERPOLANT_TYPE_EXPONENTIAL) { // Exponential interpolant float DeltaX = max(ShadowData[1].X - ShadowData[0].X, 0.0); float X = clamp(WorldHitT - ShadowData[0].X, 0.0, DeltaX); float BlendWeight = saturate(X * SafeRcp(DeltaX)); //float SigmaT = 10.0; float SigmaT = 1.0; Transmittance = lerp(ShadowData[1].Tau, ShadowData[0].Tau, exp(-BlendWeight * SigmaT)); } else // if (INTERPOLANT_TYPE == INTERPOLANT_TYPE_EXPONENTIAL_FIT) { // Exponential fit float DeltaX = max(ShadowData[1].X - ShadowData[0].X, 0.0); float SigmaT = -log(ShadowData[1].Tau * SafeRcp(ShadowData[0].Tau)) * SafeRcp(DeltaX); float X = clamp(WorldHitT - ShadowData[0].X, 0.0, DeltaX); Transmittance = saturate(ShadowData[0].Tau * exp(-X * SigmaT)); } return Transmittance; } float AVSM_Sample_Reference( inout FAdaptiveVolumetricShadowMap ShadowMap, int Face, int2 Pixel, float WorldHitT ) { uint LinearIndex = AVSM_LinearIndex(ShadowMap, Face, Pixel); FAVSMIndirectionData IndirectionData = AVSM_UnpackIndirectionData(ShadowMap.IndirectionBuffer[LinearIndex]); if (WorldHitT < IndirectionData.BeginX) { return 1.0; } FAVSMSampleData ShadowData[2] = { AVSM_CreateSampleData(0.0, 1.0), AVSM_CreateSampleData(0.0, 1.0) }; int TopLevelSampleCount = CalcTopLevelSampleCount(IndirectionData.SampleCount); if (TopLevelSampleCount > 0) { // Early-out if (WorldHitT >= IndirectionData.EndX) { int LastSampleIndex = IndirectionData.PixelOffset + Align4(TopLevelSampleCount) + IndirectionData.SampleCount - 1; return AVSM_UnpackSampleData(ShadowMap.SampleBuffer[LastSampleIndex], IndirectionData).Tau; } // Initialize from the top-level ShadowData[0] = AVSM_UnpackSampleData(ShadowMap.SampleBuffer[IndirectionData.PixelOffset], IndirectionData); // Iterate through the linear span of offset transmittance information for (int ElementIndex = 0; ElementIndex < IndirectionData.SampleCount - 1; ++ElementIndex) { ShadowData[1] = AVSM_UnpackSampleData(ShadowMap.SampleBuffer[IndirectionData.PixelOffset + Align4(TopLevelSampleCount) + ElementIndex], IndirectionData); if (WorldHitT < ShadowData[1].X) { break; } ShadowData[0] = ShadowData[1]; } } float Transmittance = Interpolate(ShadowData, WorldHitT); return Transmittance; } float AVSM_Sample_BinarySearch( inout FAdaptiveVolumetricShadowMap ShadowMap, int Face, int2 Pixel, float WorldHitT ) { uint LinearIndex = AVSM_LinearIndex(ShadowMap, Face, Pixel); FAVSMIndirectionData IndirectionData = AVSM_UnpackIndirectionData(ShadowMap.IndirectionBuffer[LinearIndex]); if (WorldHitT < IndirectionData.BeginX) { return 1.0; } FAVSMSampleData ShadowData[2] = { AVSM_CreateSampleData(0.0, 1.0), AVSM_CreateSampleData(0.0, 1.0) }; int TopLevelSampleCount = CalcTopLevelSampleCount(IndirectionData.SampleCount); if (TopLevelSampleCount > 0) { // Early-out if (WorldHitT >= IndirectionData.EndX) { int LastSampleIndex = IndirectionData.PixelOffset + Align4(TopLevelSampleCount) + IndirectionData.SampleCount - 1; return AVSM_UnpackSampleData(ShadowMap.SampleBuffer[LastSampleIndex], IndirectionData).Tau; } // Initialize from the top-level ShadowData[0] = AVSM_UnpackSampleData(ShadowMap.SampleBuffer[IndirectionData.PixelOffset], IndirectionData); // Account for the shifted bottom-level int ElementIndexMin = -1; int ElementIndexMax = IndirectionData.SampleCount - 1; while (ElementIndexMax - ElementIndexMin > 1) { int ElementIndex = (ElementIndexMin + ElementIndexMax + 1) / 2; FAVSMSampleData SampleData = AVSM_UnpackSampleData(ShadowMap.SampleBuffer[IndirectionData.PixelOffset + Align4(TopLevelSampleCount) + ElementIndex], IndirectionData); if (WorldHitT < SampleData.X) { ElementIndexMax = ElementIndex; ShadowData[1] = SampleData; } else { ElementIndexMin = ElementIndex; ShadowData[0] = SampleData; } } } float Transmittance = Interpolate(ShadowData, WorldHitT); return Transmittance; } #if 0 float AVSM_Sample_TwoLevel_Reference( inout FAdaptiveVolumetricShadowMap ShadowMap, int Face, int2 Pixel, float WorldHitT ) { uint LinearIndex = AVSM_LinearIndex(ShadowMap, Face, Pixel); FAVSMIndirectionData IndirectionData = AVSM_UnpackIndirectionData(ShadowMap.IndirectionBuffer[LinearIndex]); if (WorldHitT < IndirectionData.BeginX) { return 1.0; } FAVSMSampleData ShadowData[2] = { AVSM_CreateSampleData(0.0, 1.0), AVSM_CreateSampleData(0.0, 1.0) }; int TopLevelSampleIndex = 0; // Level 1 int TopLevelSampleCount = CalcTopLevelSampleCount(IndirectionData.SampleCount); if (TopLevelSampleCount > 0) { if (WorldHitT >= IndirectionData.EndX) { int LastSampleIndex = IndirectionData.PixelOffset + Align4(TopLevelSampleCount) + IndirectionData.SampleCount - 1; return AVSM_UnpackSampleData(AVSM.SampleBuffer[LastSampleIndex], IndirectionData).Tau; } ShadowData[0] = AVSM_UnpackSampleData(ShadowMap.SampleBuffer[IndirectionData.PixelOffset], IndirectionData); // Iterate through the top-level for (TopLevelSampleIndex = 0; TopLevelSampleIndex < TopLevelSampleCount; ++TopLevelSampleIndex) { ShadowData[1] = AVSM_UnpackSampleData(ShadowMap.SampleBuffer[IndirectionData.PixelOffset + TopLevelSampleIndex], IndirectionData); if (WorldHitT < ShadowData[1].X) { break; } ShadowData[0] = ShadowData[1]; } } // Level 2 int SampleIndexMin = max((TopLevelSampleIndex - 1) * 4, 0); // Account for the index shifting in the bottom-level span int SampleIndexMax = min(TopLevelSampleIndex * 4, IndirectionData.SampleCount - 1); for (int SampleIndex = SampleIndexMin; SampleIndex < SampleIndexMax; ++SampleIndex) { ShadowData[1] = AVSM_UnpackSampleData(ShadowMap.SampleBuffer[IndirectionData.PixelOffset + Align4(TopLevelSampleCount) + SampleIndex], IndirectionData); if (WorldHitT < ShadowData[1].X) { break; } ShadowData[0] = ShadowData[1]; } // Lerp the final value float BlendWeight = saturate((WorldHitT - ShadowData[0].X) * SafeRcp(ShadowData[1].X - ShadowData[0].X)); float Transmittance = lerp(ShadowData[0].Tau, ShadowData[1].Tau, BlendWeight); return Transmittance; } #endif float AVSM_Sample_TwoLevel_Vectorized( inout FAdaptiveVolumetricShadowMap ShadowMap, int Face, int2 Pixel, float WorldHitT ) { uint LinearIndex = AVSM_LinearIndex(ShadowMap, Face, Pixel); FAVSMIndirectionData IndirectionData = AVSM_UnpackIndirectionData(ShadowMap.IndirectionBuffer[LinearIndex]); if (WorldHitT < IndirectionData.BeginX) { return 1.0; } FAVSMSampleData ShadowData[2] = { AVSM_CreateSampleData(0.0, 1.0), AVSM_CreateSampleData(0.0, 1.0) }; int TopLevelSampleIndex = 0; // Level 1 int TopLevelSampleCount = CalcTopLevelSampleCount(IndirectionData.SampleCount); if (TopLevelSampleCount > 0) { if (WorldHitT >= IndirectionData.EndX) { int LastIndex = IndirectionData.PixelOffset + Align4(TopLevelSampleCount) + IndirectionData.SampleCount - 1; return AVSM_UnpackSampleData(ShadowMap.SampleBuffer[LastIndex], IndirectionData).Tau; } int TopLevelSampleOffset = 0; #if AVSM_SAMPLE_MODE == AVSM_SAMPLE_MODE_TWO_LEVEL_VECTORIZED_LOOP for (; TopLevelSampleOffset < TopLevelSampleCount; TopLevelSampleOffset += 4) #endif { int4 IndexOffset = TopLevelSampleOffset + int4(0, 1, 2, 3); int4 PackedShadowData4 = int4( ShadowMap.SampleBuffer[IndirectionData.PixelOffset + IndexOffset[0]], ShadowMap.SampleBuffer[IndirectionData.PixelOffset + IndexOffset[1]], ShadowMap.SampleBuffer[IndirectionData.PixelOffset + IndexOffset[2]], ShadowMap.SampleBuffer[IndirectionData.PixelOffset + IndexOffset[3]] ); float4 Depths = float4( AVSM_UnpackSampleData(PackedShadowData4[0], IndirectionData).X, AVSM_UnpackSampleData(PackedShadowData4[1], IndirectionData).X, AVSM_UnpackSampleData(PackedShadowData4[2], IndirectionData).X, AVSM_UnpackSampleData(PackedShadowData4[3], IndirectionData).X ); int4 Result = float4(WorldHitT, WorldHitT, WorldHitT, WorldHitT) > Depths; int LocalSampleIndex = Result[0] + Result[1] + Result[2] + Result[3]; ShadowData[0] = AVSM_UnpackSampleData(PackedShadowData4[max(LocalSampleIndex - 1, 0)], IndirectionData); ShadowData[1] = AVSM_UnpackSampleData(PackedShadowData4[min(LocalSampleIndex, 3)], IndirectionData); TopLevelSampleIndex = min(TopLevelSampleOffset + LocalSampleIndex, TopLevelSampleCount); #if AVSM_SAMPLE_MODE == AVSM_SAMPLE_MODE_TWO_LEVEL_VECTORIZED_LOOP if (LocalSampleIndex < 4) { break; } #endif } } // Level 2 if (TopLevelSampleCount > 0) { int4 IndexOffset = max((TopLevelSampleIndex - 1) * 4, 0); IndexOffset += int4(0, 1, 2, 3); int4 PackedShadowData4 = int4( ShadowMap.SampleBuffer[IndirectionData.PixelOffset + Align4(TopLevelSampleCount) + IndexOffset.x], ShadowMap.SampleBuffer[IndirectionData.PixelOffset + Align4(TopLevelSampleCount) + IndexOffset.y], ShadowMap.SampleBuffer[IndirectionData.PixelOffset + Align4(TopLevelSampleCount) + IndexOffset.z], ShadowMap.SampleBuffer[IndirectionData.PixelOffset + Align4(TopLevelSampleCount) + IndexOffset.w] ); float4 Depths = float4( AVSM_UnpackSampleData(PackedShadowData4.x, IndirectionData).X, AVSM_UnpackSampleData(PackedShadowData4.y, IndirectionData).X, AVSM_UnpackSampleData(PackedShadowData4.z, IndirectionData).X, AVSM_UnpackSampleData(PackedShadowData4.w, IndirectionData).X ); int4 Result = float4(WorldHitT, WorldHitT, WorldHitT, WorldHitT) > Depths; int Index = Result.x + Result.y + Result.z + Result.w; if (Index > 0) { ShadowData[0] = AVSM_UnpackSampleData(PackedShadowData4[max(Index - 1, 0)], IndirectionData); } ShadowData[1] = AVSM_UnpackSampleData(PackedShadowData4[min(Index, 3)], IndirectionData); } float Transmittance = Interpolate(ShadowData, WorldHitT); return Transmittance; } float AVSM_Sample( inout FAdaptiveVolumetricShadowMap ShadowMap, int Face, int2 Pixel, float WorldHitT ) { return // Temporarily disabling vectorized loop for reference #if AVSM_SAMPLE_MODE == AVSM_SAMPLE_MODE_TWO_LEVEL_VECTORIZED_LOOP AVSM_Sample_BinarySearch( #else AVSM_Sample_TwoLevel_Vectorized( #endif ShadowMap, Face, Pixel, WorldHitT ); } float AVSM_SampleTransmittance( inout FAdaptiveVolumetricShadowMap ShadowMap, float3 TranslatedWorldPosition, float3 LightTranslatedWorldPosition ) { if (any(ShadowMap.Resolution == 0)) { return 1.0; } float Transmittance[] = { 1.0, 1.0 }; float Alpha = 1.0; float Depth = length(TranslatedWorldPosition - View.TranslatedWorldCameraOrigin); int Face[] = { ShadowMap.NumShadowMatrices - 1, ShadowMap.NumShadowMatrices - 1 }; if (AVSM.bIsDirectionalLight) { // Find the highest quality cascade for the shade point for (; Face[1] > 0; --Face[1]) { float Far = ShadowMap.SplitDepths[Face[1]].y; if (Depth <= Far) { break; } } Face[0] = max(Face[1] - 1, 0); // Apply near-far distance fades float Near = ShadowMap.SplitDepths[Face[1]].x; float NearFadeDist = ShadowMap.SplitDepths[Face[1]].z; if (Depth - Near < NearFadeDist) { Alpha = saturate((Depth - Near) / NearFadeDist); Face[0] = min(Face[1] + 1, ShadowMap.NumShadowMatrices - 1); } float Far = ShadowMap.SplitDepths[Face[1]].y; float FarFadeDist = ShadowMap.SplitDepths[Face[1]].w; if (Far - Depth < FarFadeDist) { Alpha = saturate((Far - Depth) / FarFadeDist); Face[0] = max(Face[1] - 1, 0); } } else if (AVSM.NumShadowMatrices > 1) { float3 LightDir = TranslatedWorldPosition - ShadowMap.TranslatedWorldOrigin[0]; float3 LightDirAbs = abs(LightDir); Face[0] = LightDirAbs[1] > LightDirAbs[0] ? 1 : 0; Face[0] = LightDirAbs[2] > LightDirAbs[Face[0]] ? 2 : Face[0]; int FaceOffset = (sign(LightDir[Face[0]]) > 0) ? 1 : 0; Face[0] = Face[1] = Face[0] * 2 + FaceOffset; } float4 ShadowPositionAsFloat4 = 0; float3 ShadowPosition = 0; for (int Index = 0; Index < 2; ++Index) { ShadowPositionAsFloat4 = mul(float4(TranslatedWorldPosition, 1), ShadowMap.TranslatedWorldToShadow[Face[Index]]); if (ShadowPositionAsFloat4.w > 0) { ShadowPosition = ShadowPositionAsFloat4.xyz / ShadowPositionAsFloat4.w; if (all(ShadowPosition.xyz > 0) || all(ShadowPosition.xy < 1)) { float2 ShadowMapCoord = clamp(ShadowPosition.xy * ShadowMap.Resolution - 0.5, 0, ShadowMap.Resolution - 1); float ShadowZ = length(TranslatedWorldPosition - ShadowMap.TranslatedWorldOrigin[Face[Index]]); if (ShadowMap.bIsDirectionalLight) { ShadowZ = dot(ShadowMap.TranslatedWorldPlane[Face[Index]].xyz, TranslatedWorldPosition) + ShadowMap.TranslatedWorldPlane[Face[Index]].w; } #define BILINEAR_INTERPOLATION 1 #if BILINEAR_INTERPOLATION // Bilinear interpolation of shadow data int2 ShadowMapCoordX[4] = { clamp(ShadowMapCoord, 0, ShadowMap.Resolution - 1), clamp(ShadowMapCoord + int2(1, 0), 0, ShadowMap.Resolution - 1), clamp(ShadowMapCoord + int2(0, 1), 0, ShadowMap.Resolution - 1), clamp(ShadowMapCoord + int2(1, 1), 0, ShadowMap.Resolution - 1) }; float TransmittanceX[4] = { AVSM_Sample(ShadowMap, Face[Index], ShadowMapCoordX[0], ShadowZ), AVSM_Sample(ShadowMap, Face[Index], ShadowMapCoordX[1], ShadowZ), AVSM_Sample(ShadowMap, Face[Index], ShadowMapCoordX[2], ShadowZ), AVSM_Sample(ShadowMap, Face[Index], ShadowMapCoordX[3], ShadowZ) }; float2 Weight = frac(ShadowMapCoord); float TransmittanceY0 = lerp(TransmittanceX[0], TransmittanceX[1], Weight.x); float TransmittanceY1 = lerp(TransmittanceX[2], TransmittanceX[3], Weight.x); Transmittance[Index] = lerp(TransmittanceY0, TransmittanceY1, Weight.y); #else Transmittance[Index] = AVSM_Sample(ShadowMap, Face[Index], ShadowMapCoord, ShadowZ); #endif } } } return ((1.0 - Alpha) * Transmittance[0]) + (Alpha * Transmittance[1]); } FAdaptiveVolumetricShadowMap GetAdaptiveVolumetricShadowMap() { FAdaptiveVolumetricShadowMap ShadowMap; for (int i = 0; i < 6; ++i) { ShadowMap.TranslatedWorldToShadow[i] = AVSM.TranslatedWorldToShadow[i]; ShadowMap.TranslatedWorldOrigin[i] = AVSM.TranslatedWorldOrigin[i].xyz; ShadowMap.TranslatedWorldPlane[i] = AVSM.TranslatedWorldPlane[i]; ShadowMap.SplitDepths[i] = AVSM.SplitDepths[i]; } ShadowMap.Resolution = AVSM.Resolution; ShadowMap.DownsampleFactor = AVSM.DownsampleFactor; ShadowMap.NumShadowMatrices = AVSM.NumShadowMatrices; ShadowMap.MaxSampleCount = AVSM.MaxSampleCount; ShadowMap.bIsEmpty = AVSM.bIsEmpty; ShadowMap.bIsDirectionalLight = AVSM.bIsDirectionalLight; ShadowMap.IndirectionBuffer = AVSM.IndirectionBuffer; ShadowMap.SampleBuffer = AVSM.SampleBuffer; ShadowMap.RadianceTexture = AVSM.RadianceTexture; ShadowMap.TextureSampler = AVSM.TextureSampler; return ShadowMap; } FAdaptiveVolumetricShadowMap GetAdaptiveVolumetricShadowMapForCamera() { FAdaptiveVolumetricShadowMap ShadowMap; #if USE_CAMERA_AVSM for (int i = 0; i < 6; ++i) { ShadowMap.TranslatedWorldToShadow[i] = CameraAVSM.TranslatedWorldToShadow[i]; ShadowMap.TranslatedWorldOrigin[i] = CameraAVSM.TranslatedWorldOrigin[i].xyz; ShadowMap.TranslatedWorldPlane[i] = CameraAVSM.TranslatedWorldPlane[i]; ShadowMap.SplitDepths[i] = CameraAVSM.SplitDepths[i]; } ShadowMap.Resolution = CameraAVSM.Resolution; ShadowMap.DownsampleFactor = CameraAVSM.DownsampleFactor; ShadowMap.NumShadowMatrices = CameraAVSM.NumShadowMatrices; ShadowMap.MaxSampleCount = CameraAVSM.MaxSampleCount; ShadowMap.bIsEmpty = CameraAVSM.bIsEmpty; ShadowMap.bIsDirectionalLight = CameraAVSM.bIsDirectionalLight; ShadowMap.IndirectionBuffer = CameraAVSM.IndirectionBuffer; ShadowMap.SampleBuffer = CameraAVSM.SampleBuffer; ShadowMap.RadianceTexture = CameraAVSM.RadianceTexture; ShadowMap.TextureSampler = CameraAVSM.TextureSampler; #endif // USE_CAMERA_AVSM return ShadowMap; } float AVSM_SampleTransmittance( float3 TranslatedWorldPosition, float3 LightTranslatedWorldPosition ) { FAdaptiveVolumetricShadowMap AdaptiveVolumetricShadowMap = GetAdaptiveVolumetricShadowMap(); return AVSM_SampleTransmittance(AdaptiveVolumetricShadowMap, TranslatedWorldPosition, LightTranslatedWorldPosition); } float4 AVSM_SampleCameraRadianceAndTransmittance4( float2 ScreenUV, float3 TranslatedWorldPosition, float3 LightTranslatedWorldPosition ) { FAdaptiveVolumetricShadowMap AdaptiveVolumetricShadowMap = GetAdaptiveVolumetricShadowMap(); float Transmittance = AVSM_SampleTransmittance(AdaptiveVolumetricShadowMap, TranslatedWorldPosition, LightTranslatedWorldPosition); // Extract radiance from precomposited render target and weight based on relative opacity float4 Result = Texture2DSample(AdaptiveVolumetricShadowMap.RadianceTexture, AdaptiveVolumetricShadowMap.TextureSampler, ScreenUV); float3 Radiance = Result.rgb * View.OneOverPreExposure; float Weight = saturate((1.0 - Transmittance) * SafeRcp(1.0 - Result.a)); return float4(Radiance * Weight, Transmittance); } // Beer Shadow Map implementation struct FBeerShadowMap { float4x4 TranslatedWorldToShadow[6]; float3 TranslatedWorldOrigin[6]; float4 TranslatedWorldPlane[6]; float4 SplitDepths[6]; float DownsampleFactor; int2 Resolution; int NumShadowMatrices; int MaxSampleCount; bool bIsEmpty; bool bIsDirectionalLight; Texture2D BeerShadowMapTexture; Texture2D RadianceTexture; SamplerState TextureSampler; }; FBeerShadowMap GetBeerShadowMap() { FBeerShadowMap ShadowMap; for (int i = 0; i < 6; ++i) { ShadowMap.TranslatedWorldToShadow[i] = BeerShadowMap.TranslatedWorldToShadow[i]; ShadowMap.TranslatedWorldOrigin[i] = BeerShadowMap.TranslatedWorldOrigin[i].xyz; ShadowMap.TranslatedWorldPlane[i] = BeerShadowMap.TranslatedWorldPlane[i]; ShadowMap.SplitDepths[i] = BeerShadowMap.SplitDepths[i]; } ShadowMap.Resolution = BeerShadowMap.Resolution; ShadowMap.DownsampleFactor = BeerShadowMap.DownsampleFactor; ShadowMap.NumShadowMatrices = BeerShadowMap.NumShadowMatrices; ShadowMap.MaxSampleCount = BeerShadowMap.MaxSampleCount; ShadowMap.bIsEmpty = BeerShadowMap.bIsEmpty; ShadowMap.bIsDirectionalLight = BeerShadowMap.bIsDirectionalLight; ShadowMap.BeerShadowMapTexture = BeerShadowMap.BeerShadowMapTexture; ShadowMap.RadianceTexture = BeerShadowMap.RadianceTexture; ShadowMap.TextureSampler = BeerShadowMap.TextureSampler; return ShadowMap; } float BeerShadowMap_Sample( inout FBeerShadowMap ShadowMap, float2 UV, float ShadowZ ) { float Transmittance = 1.0; // NOTE: Consider using point-sampler for depth and bilinear-sampler for transmittance to improve quality float4 PackedData = Texture2DSample(ShadowMap.BeerShadowMapTexture, ShadowMap.TextureSampler, UV); FBeerShadowMapData ShadowMapData = UnpackBeerShadowMapData(PackedData); if (ShadowZ > ShadowMapData.T[0]) { FAVSMSampleData SampleData[] = { AVSM_CreateSampleData(ShadowMapData.T[0], ShadowMapData.Tau[0]), AVSM_CreateSampleData(ShadowMapData.T[1], ShadowMapData.Tau[1]) }; Transmittance = Interpolate(SampleData, ShadowZ); } return Transmittance; } float BeerShadowMap_SampleTransmittanceSimple( inout FBeerShadowMap ShadowMap, float3 TranslatedWorldPosition, float3 LightTranslatedWorldPosition ) { if (any(ShadowMap.Resolution == 0)) { return 1.0; } float Transmittance[] = { 1.0, 1.0 }; float Alpha = 0.0; float Depth = length(TranslatedWorldPosition - View.TranslatedWorldCameraOrigin); int Face[] = { ShadowMap.NumShadowMatrices - 1, ShadowMap.NumShadowMatrices - 1 }; float4 ShadowPositionAsFloat4 = 0; float3 ShadowPosition = 0; for (int Index = 0; Index < 1; ++Index) { ShadowPositionAsFloat4 = mul(float4(TranslatedWorldPosition, 1), ShadowMap.TranslatedWorldToShadow[Face[Index]]); if (ShadowPositionAsFloat4.w > 0) { ShadowPosition = ShadowPositionAsFloat4.xyz / ShadowPositionAsFloat4.w; // Remap ShadowPosition to Texture UV ShadowPosition.x = (Face[Index] + ShadowPosition.x) / BeerShadowMap.NumShadowMatrices; float3 ClipUV[] = { float3(float(Face[Index]) / BeerShadowMap.NumShadowMatrices, 0, -1), float3(float(Face[Index] + 1) / BeerShadowMap.NumShadowMatrices, 1, 1) }; if (all(ShadowPosition > ClipUV[0]) && all(ShadowPosition < ClipUV[1])) { float ShadowZ = length(TranslatedWorldPosition - ShadowMap.TranslatedWorldOrigin[Face[Index]]); if (ShadowMap.bIsDirectionalLight) { ShadowZ = dot(ShadowMap.TranslatedWorldPlane[Face[Index]].xyz, TranslatedWorldPosition) + ShadowMap.TranslatedWorldPlane[Face[Index]].w; } #define BILINEAR_INTERPOLATION 1 #if BILINEAR_INTERPOLATION // Bilinear interpolation of shadow data float2 ShadowMapCoord = ShadowPosition.xy * ShadowMap.Resolution; int2 ShadowMapCoordX[4] = { clamp(ShadowMapCoord, 0, ShadowMap.Resolution), clamp(ShadowMapCoord + int2(1, 0), 0, ShadowMap.Resolution), clamp(ShadowMapCoord + int2(0, 1), 0, ShadowMap.Resolution), clamp(ShadowMapCoord + int2(1, 1), 0, ShadowMap.Resolution) }; float TransmittanceX[4] = { BeerShadowMap_Sample(ShadowMap, float2(ShadowMapCoordX[0]) / ShadowMap.Resolution, ShadowZ), BeerShadowMap_Sample(ShadowMap, float2(ShadowMapCoordX[1]) / ShadowMap.Resolution, ShadowZ), BeerShadowMap_Sample(ShadowMap, float2(ShadowMapCoordX[2]) / ShadowMap.Resolution, ShadowZ), BeerShadowMap_Sample(ShadowMap, float2(ShadowMapCoordX[3]) / ShadowMap.Resolution, ShadowZ) }; float2 Weight = frac(ShadowMapCoord); float TransmittanceY0 = lerp(TransmittanceX[0], TransmittanceX[1], Weight.x); float TransmittanceY1 = lerp(TransmittanceX[2], TransmittanceX[3], Weight.x); Transmittance[Index] = lerp(TransmittanceY0, TransmittanceY1, Weight.y); #else Transmittance[Index] = BeerShadowMap_Sample(ShadowMap, ShadowPosition.xy, ShadowZ); #endif } } } return ((1.0 - Alpha) * Transmittance[0]) + (Alpha * Transmittance[1]); } float BeerShadowMap_SampleTransmittance( inout FBeerShadowMap ShadowMap, float3 TranslatedWorldPosition, float3 LightTranslatedWorldPosition ) { if (any(ShadowMap.Resolution == 0)) { return 1.0; } float Transmittance[] = { 1.0, 1.0 }; float Alpha = 1.0; float Depth = length(TranslatedWorldPosition - View.TranslatedWorldCameraOrigin); int Face[] = { ShadowMap.NumShadowMatrices - 1, ShadowMap.NumShadowMatrices - 1 }; if (BeerShadowMap.bIsDirectionalLight) { // Find the highest quality cascade for the shade point for (; Face[1] > 0; --Face[1]) { float Far = ShadowMap.SplitDepths[Face[1]].y; if (Depth <= Far) { break; } } Face[0] = max(Face[1] - 1, 0); // Apply near-far distance fades float Near = ShadowMap.SplitDepths[Face[1]].x; float NearFadeDist = ShadowMap.SplitDepths[Face[1]].z; if (Depth - Near < NearFadeDist) { Alpha = saturate((Depth - Near) / NearFadeDist); Face[0] = min(Face[1] + 1, ShadowMap.NumShadowMatrices - 1); } float Far = ShadowMap.SplitDepths[Face[1]].y; float FarFadeDist = ShadowMap.SplitDepths[Face[1]].w; if (Far - Depth < FarFadeDist) { Alpha = saturate((Far - Depth) / FarFadeDist); Face[0] = max(Face[1] - 1, 0); } } else if (BeerShadowMap.NumShadowMatrices > 1) { float3 LightDir = TranslatedWorldPosition - ShadowMap.TranslatedWorldOrigin[0]; float3 LightDirAbs = abs(LightDir); Face[0] = LightDirAbs[1] > LightDirAbs[0] ? 1 : 0; Face[0] = LightDirAbs[2] > LightDirAbs[Face[0]] ? 2 : Face[0]; int FaceOffset = (sign(LightDir[Face[0]]) > 0) ? 1 : 0; Face[0] = Face[1] = Face[0] * 2 + FaceOffset; } float4 ShadowPositionAsFloat4 = 0; float3 ShadowPosition = 0; for (int Index = 0; Index < 2; ++Index) { ShadowPositionAsFloat4 = mul(float4(TranslatedWorldPosition, 1), ShadowMap.TranslatedWorldToShadow[Face[Index]]); if (ShadowPositionAsFloat4.w > 0) { ShadowPosition = ShadowPositionAsFloat4.xyz / ShadowPositionAsFloat4.w; // Remap ShadowPosition to Texture UV ShadowPosition.x = (Face[Index] + ShadowPosition.x) / BeerShadowMap.NumShadowMatrices; float3 ClipUV[] = { float3(float(Face[Index]) / BeerShadowMap.NumShadowMatrices, 0, -1), float3(float(Face[Index] + 1) / BeerShadowMap.NumShadowMatrices, 1, 1) }; if (all(ShadowPosition > ClipUV[0]) && all(ShadowPosition < ClipUV[1])) { float ShadowZ = length(TranslatedWorldPosition - ShadowMap.TranslatedWorldOrigin[Face[Index]]); if (ShadowMap.bIsDirectionalLight) { ShadowZ = dot(ShadowMap.TranslatedWorldPlane[Face[Index]].xyz, TranslatedWorldPosition) + ShadowMap.TranslatedWorldPlane[Face[Index]].w; } #define BILINEAR_INTERPOLATION 1 #if BILINEAR_INTERPOLATION int2 AtlasResolution = ShadowMap.Resolution * int2(BeerShadowMap.NumShadowMatrices, 1); // Bilinear interpolation of shadow data float2 ShadowMapCoord = ShadowPosition.xy * AtlasResolution; int2 ShadowMapCoordX[4] = { clamp(ShadowMapCoord, 0, AtlasResolution), clamp(ShadowMapCoord + int2(1, 0), 0, AtlasResolution), clamp(ShadowMapCoord + int2(0, 1), 0, AtlasResolution), clamp(ShadowMapCoord + int2(1, 1), 0, AtlasResolution) }; float TransmittanceX[4] = { BeerShadowMap_Sample(ShadowMap, float2(ShadowMapCoordX[0]) / AtlasResolution, ShadowZ), BeerShadowMap_Sample(ShadowMap, float2(ShadowMapCoordX[1]) / AtlasResolution, ShadowZ), BeerShadowMap_Sample(ShadowMap, float2(ShadowMapCoordX[2]) / AtlasResolution, ShadowZ), BeerShadowMap_Sample(ShadowMap, float2(ShadowMapCoordX[3]) / AtlasResolution, ShadowZ) }; float2 Weight = frac(ShadowMapCoord); float TransmittanceY0 = lerp(TransmittanceX[0], TransmittanceX[1], Weight.x); float TransmittanceY1 = lerp(TransmittanceX[2], TransmittanceX[3], Weight.x); Transmittance[Index] = lerp(TransmittanceY0, TransmittanceY1, Weight.y); #else Transmittance[Index] = BeerShadowMap_Sample(ShadowMap, ShadowPosition.xy, ShadowZ); #endif } } } return ((1.0 - Alpha) * Transmittance[0]) + (Alpha * Transmittance[1]); } float BeerShadowMap_SampleTransmittance( float3 TranslatedWorldPosition, float3 LightTranslatedWorldPosition ) { FBeerShadowMap BeerShadowMap = GetBeerShadowMap(); return BeerShadowMap_SampleTransmittance(BeerShadowMap, TranslatedWorldPosition, LightTranslatedWorldPosition); } float4 BeerShadowMap_SampleCameraRadianceAndTransmittance4( float2 ScreenUV, float3 TranslatedWorldPosition, float3 LightTranslatedWorldPosition ) { FBeerShadowMap BeerShadowMap = GetBeerShadowMap(); float Transmittance = BeerShadowMap_SampleTransmittanceSimple(BeerShadowMap, TranslatedWorldPosition, LightTranslatedWorldPosition); // Extract radiance from precomposited render target and weight based on relative opacity float4 Result = Texture2DSample(BeerShadowMap.RadianceTexture, BeerShadowMap.TextureSampler, ScreenUV); float3 Radiance = Result.rgb * View.OneOverPreExposure; float Weight = saturate((1.0 - Transmittance) * SafeRcp(1.0 - Result.a)); return float4(Radiance * Weight, Transmittance); }