// Copyright Epic Games, Inc. All Rights Reserved. /** * TranslucentLightInjectionShaders.usf: Shaders for calculating lighting in a volume to use on translucency */ #include "Common.ush" #include "SHCommon.ush" #include "ComputeShaderUtils.ush" #define TREAT_MAXDEPTH_UNSHADOWED 1 #if INJECTION_PIXEL_SHADER #include "/Engine/Generated/Material.ush" #include "VolumeLightingCommon.ush" #endif #include "DynamicLightingCommon.ush" #define SUPPORT_CONTACT_SHADOWS 0 #include "DeferredLightingCommon.ush" uint VolumeCascadeIndex; float4 SimpleLightPositionAndRadius; float4 SimpleLightColorAndExponent; #if RADIAL_ATTENUATION==0 #define USE_CLOUD_TRANSMITTANCE 1 #include "VolumetricCloudCommon.ush" uint VolumetricCloudShadowEnabled; #include "/Engine/Private/SkyAtmosphereCommon.ush" uint AtmospherePerPixelTransmittanceEnabled; #endif #if VIRTUAL_SHADOW_MAP #include "VirtualShadowMaps/VirtualShadowMapProjectionCommon.ush" #endif #ifndef ADAPTIVE_VOLUMETRIC_SHADOW_MAP #define ADAPTIVE_VOLUMETRIC_SHADOW_MAP 0 #endif // ADAPTIVE_VOLUMETRIC_SHADOW_MAP #if ADAPTIVE_VOLUMETRIC_SHADOW_MAP #include "HeterogeneousVolumes/HeterogeneousVolumesAdaptiveVolumetricShadowMapSampling.ush" #endif // ADAPTIVE_VOLUMETRIC_SHADOW_MAP // To use DEBUG_ONE_VOXEL, one must enable ShaderPrint from TranslucentLighting.cpp. #define DEBUG_ONE_VOXEL 0 #if DEBUG_ONE_VOXEL #include "/Engine/Private/ShaderPrint.ush" #endif float3 GetTranslatedWorldPosition(float2 UV, uint LayerIndex) { float ZPosition = View.TranslucencyLightingVolumeMin[VolumeCascadeIndex].z + (LayerIndex + .5f) * View.TranslucencyLightingVolumeInvSize[VolumeCascadeIndex].w; float3 TranslatedWorldPosition = float3(View.TranslucencyLightingVolumeMin[VolumeCascadeIndex].xy + UV / View.TranslucencyLightingVolumeInvSize[VolumeCascadeIndex].xy, ZPosition); return TranslatedWorldPosition; } float3 GetTranslatedWorldPosition(uint3 VolumeCoord) { return View.TranslucencyLightingVolumeMin[VolumeCascadeIndex].xyz + (VolumeCoord + .5f) * View.TranslucencyLightingVolumeInvSize[VolumeCascadeIndex].w; } void AccumulateSHLighting( float3 NormalizedLightVector, float3 Lighting, float DirectionalLightContribution, inout float4 OutColor0, inout float4 OutColor1) { FTwoBandSHVectorRGB SHLighting = MulSH(SHBasisFunction(NormalizedLightVector), Lighting); // Directional light contribution in w OutColor0 += float4(SHLighting.R.V.x, SHLighting.G.V.x, SHLighting.B.V.x, DirectionalLightContribution); float3 LuminanceWeights = LuminanceFactors(); float3 Coefficient0 = float3(SHLighting.R.V.y, SHLighting.G.V.y, SHLighting.B.V.y); float3 Coefficient1 = float3(SHLighting.R.V.z, SHLighting.G.V.z, SHLighting.B.V.z); float3 Coefficient2 = float3(SHLighting.R.V.w, SHLighting.G.V.w, SHLighting.B.V.w); OutColor1 += float4(dot(Coefficient0, LuminanceWeights), dot(Coefficient1, LuminanceWeights), dot(Coefficient2, LuminanceWeights), 0); } float CalcSimpleLightAttenuation(float3 TranslatedWorldPosition) { float3 WorldLightVector = SimpleLightPositionAndRadius.xyz - TranslatedWorldPosition; float Attenuation = 1; if (SimpleLightColorAndExponent.w == 0) { float DistanceSqr = dot( WorldLightVector, WorldLightVector ); // Sphere falloff (technically just 1/d2 but this avoids inf) Attenuation = 1 / ( DistanceSqr + 1 ); float LightRadiusMask = Square(saturate(1 - Square(DistanceSqr / (SimpleLightPositionAndRadius.w * SimpleLightPositionAndRadius.w)))); Attenuation *= LightRadiusMask; } else { Attenuation = RadialAttenuation(WorldLightVector / SimpleLightPositionAndRadius.w, SimpleLightColorAndExponent.w); } return Attenuation; } float3 GetTranslatedWorldPositionForLighting(float3 TranslatedWorldPosition, float3 NormalizedLightVector) { float VoxelSize = View.TranslucencyLightingVolumeInvSize[VolumeCascadeIndex].w; // Push out the position for lighting in the oposite direction of the L vector // to make sure we always have half a voxel distance from the light to avoid overblown lighting intensities. float3 LightingOffset = -NormalizedLightVector * 0.5f * VoxelSize; return TranslatedWorldPosition + LightingOffset; } /** Pixel shader that calculates direct lighting for a simple light (unshadowed point light) for all the affected voxels of a volume texture. */ void SimpleLightInjectMainPS( FWriteToSliceGeometryOutput Input, out float4 OutColor0 : SV_Target0, out float4 OutColor1 : SV_Target1 ) { OutColor0 = 0; OutColor1 = 0; float3 TranslatedWorldPosition = GetTranslatedWorldPosition(Input.Vertex.UV, Input.LayerIndex); float3 NormalizedLightVector = normalize(SimpleLightPositionAndRadius.xyz - TranslatedWorldPosition); float3 TranslatedWorldPositionForLighting = GetTranslatedWorldPositionForLighting(TranslatedWorldPosition, NormalizedLightVector); float Attenuation = CalcSimpleLightAttenuation(TranslatedWorldPositionForLighting); float3 Lighting = SimpleLightColorAndExponent.rgb / PI * Attenuation; float DirectionalLightContribution = 0.0f; AccumulateSHLighting(NormalizedLightVector, Lighting, DirectionalLightContribution, OutColor0, OutColor1); } float GetBorderLerpFactorFromVolumeUVs(float3 VolumeUVs) { // Larger values result in a shorter transition distance float TransitionScale = 10; // Rescale the UVs to make the fade go to 0 before the edge of the volume float3 FadeUVs = VolumeUVs * (1 + 4 * View.TranslucencyLightingVolumeMin[VolumeCascadeIndex].w) - 2 * View.TranslucencyLightingVolumeMin[VolumeCascadeIndex].w; // Setup a 3d lerp factor going to 0 near the edge of the outer volume float3 LerpFactors = saturate((.5f - abs(FadeUVs - .5f)) * TransitionScale); float FinalLerpFactor = LerpFactors.x * LerpFactors.y * LerpFactors.z; return FinalLerpFactor; } float GetBorderLerpFactor(float2 UV, uint LayerIndex) { const float3 VolumeUVs = float3(UV, (LayerIndex + .5f) * View.TranslucencyLightingVolumeMin[VolumeCascadeIndex].w); return GetBorderLerpFactorFromVolumeUVs(VolumeUVs); } float GetBorderLerpFactor(uint3 VolumeCoord) { const float3 VolumeUVs = (VolumeCoord + 0.5f) * View.TranslucencyLightingVolumeMin[VolumeCascadeIndex].w; return GetBorderLerpFactorFromVolumeUVs(VolumeUVs); } float GetTranslucencyVolumeLocalLightAttenuation(float3 TranslatedWorldPosition, FDeferredLightData LightData) { float3 ToLight, L; float Attenuation = GetLocalLightAttenuation(TranslatedWorldPosition, LightData, ToLight, L); if (LightData.bRectLight) { if (Attenuation > 0.0f) { // Use a more accurate rect light approximation FRect Rect = GetRect(ToLight, LightData); Attenuation *= IntegrateLight(Rect); } } else if (LightData.bInverseSquared) { // This path still uses the legacy local light attenuation, so add that back in float DistanceSqr = dot(ToLight, ToLight); Attenuation *= 1.0f / (DistanceSqr + 1.0f); } return Attenuation; } #if INJECTION_PIXEL_SHADER #include "LightDataUniforms.ush" #include "LightFunctionCommon.ush" // WorldSpace planes to clip the cascade for ShadoewMethod1 float4 ClippingPlanes[2]; /** 1 if the light is a spotlight, 0 otherwise. */ float SpotlightMask; float GetLightFunctionShadowFactor(float3 TranslatedWorldPositionForLighting) { float ShadowFactor = 1; // Apply light function after edge fading, so that a black light function at the edges can cause distant translucency to also be black #if APPLY_LIGHT_FUNCTION float4 LightVector = mul(float4(TranslatedWorldPositionForLighting, 1),LightFunctionTranslatedWorldToLight); LightVector.xyz /= LightVector.w; float3 LightFunction = GetLightFunctionColor(LightVector.xyz, TranslatedWorldPositionForLighting); // We only suport monochrome light functions ShadowFactor = dot(LightFunction, .3333f).x; #endif return ShadowFactor; } int VirtualShadowMapId; /** Pixel shader that calculates direct lighting for all the affected voxels of a volume texture. */ void InjectMainPS( FWriteToSliceGeometryOutput Input, out float4 OutColor0 : SV_Target0, out float4 OutColor1 : SV_Target1 ) { ResolvedView = ResolveView(); OutColor0 = 0; OutColor1 = 0; float3 TranslatedWorldPosition = GetTranslatedWorldPosition(Input.Vertex.UV, Input.LayerIndex); FDeferredLightData LightData = InitDeferredLightFromUniforms(); float3 LightVector = LightData.Direction; // 0: no contribution, 1:full contribution float Masking = 1.0f; #if DEBUG_ONE_VOXEL const float DebugVoxelSize = View.TranslucencyLightingVolumeInvSize[VolumeCascadeIndex].w; const float DebugHalfVoxelSize = DebugVoxelSize * 0.5f; const float3 WorldPosition = (TranslatedWorldPosition - DFDemote(PrimaryView.PreViewTranslation)); const int3 IntegerPos = (int3)(WorldPosition / (DebugVoxelSize)); // Enable shader print for a single voxel containing VoxelIncludedWorldPosition for cascade 0. const float3 VoxelIncludedWorldPosition = float3(0.5, 0.5, 0.5); FShaderPrintContext Context = InitShaderPrintContext(all((WorldPosition - DebugHalfVoxelSize) <= VoxelIncludedWorldPosition) && all((WorldPosition + DebugHalfVoxelSize) > VoxelIncludedWorldPosition) && VolumeCascadeIndex == 0, uint2(200, 200)); AddAABBTWS(Context, TranslatedWorldPosition - DebugHalfVoxelSize, TranslatedWorldPosition + DebugHalfVoxelSize, float4(1, 1, 1, 1)); AddSphereTWS(Context, TranslatedWorldPosition, 2.0f, float4(0, 1, 0, 1)); #endif #if RADIAL_ATTENUATION { // cull voxels outside the light radius (value is < 0) LightVector = LightData.TranslatedWorldPosition - TranslatedWorldPosition; clip(1.0f / (LightData.InvRadius * LightData.InvRadius) - dot(LightVector, LightVector)); } #else { // directional light float DistToNear = -dot(ClippingPlanes[0], float4(TranslatedWorldPosition, 1)); float DistToFar = -dot(ClippingPlanes[1], float4(TranslatedWorldPosition, 1)); // cull volumes outside the cascade (value is < 0) clip(DistToNear); clip(DistToFar); // fade cascade transition regions (additivebly blended so it does a cross fade) Masking *= saturate(DistToNear * ShadowInjectParams.x); Masking *= saturate(DistToFar * ShadowInjectParams.y); } #endif float3 NormalizedLightVector = normalize(LightVector); float3 TranslatedWorldPositionForLighting = GetTranslatedWorldPositionForLighting(TranslatedWorldPosition, NormalizedLightVector); #if DEBUG_ONE_VOXEL AddSphereTWS(Context, TranslatedWorldPositionForLighting, 5.0f, float4(0, 1, 1, 1)); #endif { float Attenuation = 1.0f; float ShadowFactor = 1.0f; #if RADIAL_ATTENUATION Attenuation *= GetTranslucencyVolumeLocalLightAttenuation(TranslatedWorldPositionForLighting, LightData); #endif { bool bUnused = false; ShadowFactor *= ComputeVolumeShadowing( TranslatedWorldPositionForLighting, // Maintain the legacy logic of "spotlight" vs "everything else" !LightData.bSpotLight, LightData.bSpotLight, bUnused); } #if VIRTUAL_SHADOW_MAP FVirtualShadowMapSampleResult VirtualShadowMapSample = SampleVirtualShadowMapTranslatedWorld(VirtualShadowMapId, TranslatedWorldPositionForLighting); ShadowFactor *= VirtualShadowMapSample.ShadowFactor; #endif #if ADAPTIVE_VOLUMETRIC_SHADOW_MAP ShadowFactor *= AVSM_SampleTransmittance(TranslatedWorldPositionForLighting, LightData.TranslatedWorldPosition); #endif // ADAPTIVE_VOLUMETRIC_SHADOW_MAP // Apply light function (it will also fade out at the edge of the volume so overall dimming of light using light function is not a recommended workflow) ShadowFactor *= GetLightFunctionShadowFactor(TranslatedWorldPositionForLighting); #if RADIAL_ATTENUATION==0 // Apply cloud shadow for atmosphere directional lights only if needed if (VolumetricCloudShadowEnabled > 0) { float OutOpticalDepth = 0.0f; ShadowFactor *= lerp(1.0f, GetCloudVolumetricShadow(TranslatedWorldPositionForLighting, CloudShadowmapTranslatedWorldToLightClipMatrix, CloudShadowmapFarDepthKm, CloudShadowmapTexture, CloudShadowmapSampler, OutOpticalDepth), CloudShadowmapStrength); } #endif if (VolumeCascadeIndex == 1) { float FinalLerpFactor = GetBorderLerpFactor(Input.Vertex.UV, Input.LayerIndex); #if RADIAL_ATTENUATION // For local lights, fade attenuation to 0 for the border voxels Attenuation = lerp(0, Attenuation, FinalLerpFactor); ShadowFactor = lerp(0.0f, ShadowFactor, FinalLerpFactor); #else // Fade out shadowing for the border voxels // The border voxels are used to light all translucency outside of both lighting volumes ShadowFactor = lerp(1.0f, ShadowFactor, FinalLerpFactor); #endif } float3 Lighting = LightData.Color / PI * Attenuation * ShadowFactor; #if RADIAL_ATTENUATION==0 // Apply color atmosphere transmittance for atmosphere directional lights only if ndeeded if (AtmospherePerPixelTransmittanceEnabled > 0) { const float3 TranslatedWorldPlanetCenterToPos = (TranslatedWorldPositionForLighting - View.SkyPlanetTranslatedWorldCenterAndViewHeight.xyz) * CM_TO_SKY_UNIT; Lighting *= GetAtmosphereTransmittance(TranslatedWorldPlanetCenterToPos, NormalizedLightVector, View.SkyAtmosphereBottomRadiusKm, View.SkyAtmosphereTopRadiusKm, View.TransmittanceLutTexture, View.TransmittanceLutTextureSampler); } #endif float DirectionalLightContribution = 0; #if !RADIAL_ATTENUATION DirectionalLightContribution = Attenuation * ShadowFactor; #endif AccumulateSHLighting(NormalizedLightVector, Lighting, DirectionalLightContribution, OutColor0, OutColor1); } // debug, make inner cascase green // if(VolumeCascadeIndex == 0) OutColor0 = float4(0,1,0,1); OutColor0 *= Masking; OutColor1 *= Masking; } #endif // #if INJECTION_PIXEL_SHADER #ifdef InjectBatchMainCS #include "LightGridCommon.ush" #include "LightData.ush" #include "LightFunctionAtlas/LightFunctionAtlasCommon.usf" //int VirtualShadowMapId; StructuredBuffer BatchedLocalLights; uint MaxBatchedLocalLights; uint3 VolumeSize; RWTexture3D RWTranslucencyLightingVolumeAmbient; RWTexture3D RWTranslucencyLightingVolumeDirectional; #if !USE_UAV_TYPED_LOAD Texture3D TranslucencyLightingVolumeAmbient; Texture3D TranslucencyLightingVolumeDirectional; #endif #if INDIRECT_VOXEL_DISPATCH StructuredBuffer VoxelAllocator; StructuredBuffer VoxelData; #endif /** Compute shader that calculates direct lighting for all the affected voxels of a volume texture. */ [numthreads(THREADGROUP_SIZE_X, THREADGROUP_SIZE_Y, THREADGROUP_SIZE_Z)] void InjectBatchMainCS( uint3 DispatchThreadId : SV_DispatchThreadID, uint3 GroupId : SV_GroupID, uint GroupIndex : SV_GroupIndex) { #if INDIRECT_VOXEL_DISPATCH const uint VoxelIndex = GetUnWrappedDispatchThreadId(GroupId, GroupIndex, THREADGROUP_SIZE_X); if (VoxelIndex >= VoxelAllocator[0]) { return; } const uint PackedVolumeCoord = VoxelData[VoxelIndex]; const uint3 VolumeCoord = uint3(PackedVolumeCoord & 0x3FF, (PackedVolumeCoord >> 10) & 0x3FF, (PackedVolumeCoord >> 20) & 0x3FF); #else const uint3 VolumeCoord = DispatchThreadId.xyz; if (any(VolumeCoord >= VolumeSize)) { return; } #endif float4 OutColor0 = 0; float4 OutColor1 = 0; float3 TranslatedWorldPosition = GetTranslatedWorldPosition(VolumeCoord); const uint NumUints = (MaxBatchedLocalLights + 31U) / 32U; for (uint UintIndex = 0; UintIndex < NumUints; ++UintIndex) { uint CurrentMask = BatchedLocalLights[UintIndex]; while (CurrentMask != 0) { int BitIndex = firstbitlow(CurrentMask); CurrentMask = CurrentMask & ~(1U << BitIndex); uint LocalLightIndex = 32U * UintIndex + BitIndex; { FLocalLightData LocalLightData = GetLocalLightData(LocalLightIndex, 0); FDeferredLightData LightData = ConvertToDeferredLight(LocalLightData); float3 LightVector = LightData.TranslatedWorldPosition - TranslatedWorldPosition; if (rcp(Square(LightData.InvRadius)) > dot(LightVector, LightVector)) { float3 NormalizedLightVector = normalize(LightVector); float3 TranslatedWorldPositionForLighting = GetTranslatedWorldPositionForLighting(TranslatedWorldPosition, NormalizedLightVector); float Attenuation = GetTranslucencyVolumeLocalLightAttenuation(TranslatedWorldPositionForLighting, LightData); float ShadowFactor = 1.0f; if (Attenuation > 0.0f) { #if VIRTUAL_SHADOW_MAP if (LocalLightData.Internal.VirtualShadowMapId != INDEX_NONE) { FVirtualShadowMapSampleResult VirtualShadowMapSample = SampleVirtualShadowMapTranslatedWorld(LocalLightData.Internal.VirtualShadowMapId, TranslatedWorldPositionForLighting); ShadowFactor *= VirtualShadowMapSample.ShadowFactor; } #endif if (VolumeCascadeIndex == 1) { float FinalLerpFactor = GetBorderLerpFactor(VolumeCoord); // For local lights, fade attenuation to 0 for the border voxels Attenuation = lerp(0, Attenuation, FinalLerpFactor); ShadowFactor = lerp(0.0f, ShadowFactor, FinalLerpFactor); } FLightFunctionColor LightFunctionColor = 1.0f; #if USE_LIGHT_FUNCTION_ATLAS LightFunctionColor = GetLocalLightFunctionCommon(TranslatedWorldPosition, LightData.LightFunctionAtlasLightIndex); #endif float3 Lighting = LightFunctionColor * LightData.Color / PI * Attenuation * ShadowFactor; float DirectionalLightContribution = 0; AccumulateSHLighting(NormalizedLightVector, Lighting, DirectionalLightContribution, OutColor0, OutColor1); } } } } } #if USE_UAV_TYPED_LOAD RWTranslucencyLightingVolumeAmbient[VolumeCoord] = RWTranslucencyLightingVolumeAmbient[VolumeCoord] + OutColor0; RWTranslucencyLightingVolumeDirectional[VolumeCoord] = RWTranslucencyLightingVolumeDirectional[VolumeCoord] + OutColor1; #else RWTranslucencyLightingVolumeAmbient[VolumeCoord] = TranslucencyLightingVolumeAmbient[VolumeCoord] + OutColor0; RWTranslucencyLightingVolumeDirectional[VolumeCoord] = TranslucencyLightingVolumeDirectional[VolumeCoord] + OutColor1; #endif } #endif // InjectBatchMainCS #ifdef InjectMegaLightsCS uint3 VolumeSize; RWTexture3D RWTranslucencyLightingVolumeAmbient; RWTexture3D RWTranslucencyLightingVolumeDirectional; #if !USE_UAV_TYPED_LOAD Texture3D TranslucencyLightingVolumeAmbient; Texture3D TranslucencyLightingVolumeDirectional; #endif Texture3D MegaLightsAmbient; Texture3D MegaLightsDirectional; #if INDIRECT_VOXEL_DISPATCH StructuredBuffer VoxelAllocator; StructuredBuffer VoxelData; #endif [numthreads(THREADGROUP_SIZE_X, THREADGROUP_SIZE_Y, THREADGROUP_SIZE_Z)] void InjectMegaLightsCS( uint3 DispatchThreadId : SV_DispatchThreadID, uint3 GroupId : SV_GroupID, uint GroupIndex : SV_GroupIndex) { #if INDIRECT_VOXEL_DISPATCH const uint VoxelIndex = GetUnWrappedDispatchThreadId(GroupId, GroupIndex, THREADGROUP_SIZE_X); if (VoxelIndex >= VoxelAllocator[0]) { return; } const uint PackedVolumeCoord = VoxelData[VoxelIndex]; const uint3 VolumeCoord = uint3(PackedVolumeCoord & 0x3FF, (PackedVolumeCoord >> 10) & 0x3FF, (PackedVolumeCoord >> 20) & 0x3FF); #else const uint3 VolumeCoord = DispatchThreadId.xyz; if (any(VolumeCoord >= VolumeSize)) { return; } #endif float4 Ambient = MegaLightsAmbient[VolumeCoord]; float4 Directional = MegaLightsDirectional[VolumeCoord]; if (VolumeCascadeIndex == 1) { float FinalLerpFactor = GetBorderLerpFactor(VolumeCoord); // For local lights, fade attenuation to 0 for the border voxels Ambient = lerp(0, Ambient, FinalLerpFactor); Directional = lerp(0.0f, Directional, FinalLerpFactor); } #if USE_UAV_TYPED_LOAD RWTranslucencyLightingVolumeAmbient[VolumeCoord] = RWTranslucencyLightingVolumeAmbient[VolumeCoord] + Ambient; RWTranslucencyLightingVolumeDirectional[VolumeCoord] = RWTranslucencyLightingVolumeDirectional[VolumeCoord] + Directional; #else RWTranslucencyLightingVolumeAmbient[VolumeCoord] = TranslucencyLightingVolumeAmbient[VolumeCoord] + Ambient; RWTranslucencyLightingVolumeDirectional[VolumeCoord] = TranslucencyLightingVolumeDirectional[VolumeCoord] + Directional; #endif } #endif // InjectMegaLightsCS #if CLEAR_COMPUTE_SHADER RWTexture3D RWAmbient0; RWTexture3D RWDirectional0; RWTexture3D RWAmbient1; RWTexture3D RWDirectional1; [numthreads(CLEAR_BLOCK_SIZE, CLEAR_BLOCK_SIZE, CLEAR_BLOCK_SIZE)] void ClearTranslucentLightingVolumeCS( uint3 GroupId : SV_GroupID, uint3 DispatchThreadId : SV_DispatchThreadID, uint3 GroupThreadId : SV_GroupThreadID) { uint3 Index = uint3(GroupId.x * CLEAR_BLOCK_SIZE + GroupThreadId.x, GroupId.y * CLEAR_BLOCK_SIZE + GroupThreadId.y, GroupId.z * CLEAR_BLOCK_SIZE + GroupThreadId.z); RWAmbient0[Index.xyz] = float4(0.0, 0.0, 0.0, 0.0); RWDirectional0[Index.xyz] = float4(0.0, 0.0, 0.0, 0.0); RWAmbient1[Index.xyz] = float4(0.0, 0.0, 0.0, 0.0); RWDirectional1[Index.xyz] = float4(0.0, 0.0, 0.0, 0.0); } #endif