Files
UnrealEngine/Engine/Shaders/Private/VolumetricFog.usf
2025-05-18 13:04:45 +08:00

1131 lines
41 KiB
HLSL

// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================
VolumetricFog.usf
=============================================================================*/
#include "Common.ush"
#include "Definitions.usf"
#define SUPPORT_CONTACT_SHADOWS 0
#define USE_SOURCE_TEXTURE USE_RECT_LIGHT_TEXTURE
#include "DeferredLightingCommon.ush"
#include "LightGridCommon.ush"
#include "HeightFogCommon.ush"
#include "SHCommon.ush"
#if DISTANCE_FIELD_SKY_OCCLUSION
#include "DistanceFieldAOShared.ush"
#include "DistanceField/GlobalDistanceFieldShared.ush"
#endif
#include "VolumeLightingCommon.ush"
#include "VolumetricLightmapShared.ush"
#include "ReflectionEnvironmentShared.ush"
#include "ForwardShadowingCommon.ush"
#include "ParticipatingMediaCommon.ush"
#include "LightDataUniforms.ush"
#if LUMEN_GI
#define FrontLayerTranslucencyReflectionsStruct LumenGIVolumeStruct
#include "Lumen/LumenTranslucencyVolumeShared.ush"
#endif
#include "Random.ush"
#if VIRTUAL_SHADOW_MAP
#include "VirtualShadowMaps/VirtualShadowMapProjectionCommon.ush"
#endif
#if USE_LOCAL_FOG_VOLUMES
#include "LocalFogVolumes/LocalFogVolumeCommon.ush"
#endif
#ifndef USE_EMISSIVE
#define USE_EMISSIVE 1
#endif
#ifndef USE_RAYTRACED_SHADOWS
#define USE_RAYTRACED_SHADOWS 0
#endif
RWTexture3D<float4> RWVBufferA;
#if USE_EMISSIVE
RWTexture3D<float4> RWVBufferB;
#endif
#if !SHADING_PATH_MOBILE
#define USE_CLOUD_TRANSMITTANCE 1
#include "VolumetricCloudCommon.ush"
#endif
float ComputeDepthFromZSlice(float ZSlice)
{
float SliceDepth = (exp2(ZSlice / VolumetricFog.GridZParams.z) - VolumetricFog.GridZParams.y) / VolumetricFog.GridZParams.x;
return SliceDepth;
}
float4x4 UnjitteredClipToTranslatedWorld;
float4x4 UnjitteredPrevTranslatedWorldToClip;
float3 ComputeCellTranslatedWorldPosition(uint3 GridCoordinate, float3 CellOffset, out float SceneDepth)
{
float2 VolumeUV = (GridCoordinate.xy + CellOffset.xy) / VolumetricFog.ViewGridSize.xy;
float2 VolumeNDC = (VolumeUV * 2 - 1) * float2(1, -1);
SceneDepth = ComputeDepthFromZSlice(max(GridCoordinate.z + CellOffset.z, 0.0f));
float TileDeviceZ = ConvertToDeviceZ(SceneDepth);
float4 CenterPosition = mul(float4(VolumeNDC, TileDeviceZ, 1), UnjitteredClipToTranslatedWorld);
return CenterPosition.xyz / CenterPosition.w;
}
float3 ComputeCellTranslatedWorldPosition(uint3 GridCoordinate, float3 CellOffset)
{
float Unused;
return ComputeCellTranslatedWorldPosition(GridCoordinate, CellOffset, Unused);
}
float3 RaleighScattering()
{
float3 Wavelengths = float3(650.0f, 510.0f, 475.0f);
float ParticleDiameter = 60;
float ParticleRefractiveIndex = 1.3f;
float3 ScaleDependentPortion = pow(ParticleDiameter, 6) / pow(Wavelengths, 4);
float RefractiveIndexPortion = (ParticleRefractiveIndex * ParticleRefractiveIndex - 1) / (ParticleRefractiveIndex * ParticleRefractiveIndex + 2);
return (2 * pow(PI, 5) * RefractiveIndexPortion * RefractiveIndexPortion) * ScaleDependentPortion / 3.0f;
}
float3 ScatteringFunction()
{
return 1;
//return RaleighScattering();
}
float4 GlobalAlbedo;
float4 GlobalEmissive;
float GlobalExtinctionScale;
#ifndef THREADGROUP_SIZE
#define THREADGROUP_SIZE 1
#endif
#ifndef THREADGROUP_SIZE_X
#define THREADGROUP_SIZE_X THREADGROUP_SIZE
#endif
#ifndef THREADGROUP_SIZE_Y
#define THREADGROUP_SIZE_Y THREADGROUP_SIZE
#endif
#ifndef THREADGROUP_SIZE_Z
#define THREADGROUP_SIZE_Z THREADGROUP_SIZE
#endif
#ifdef MaterialSetupCS
[numthreads(THREADGROUP_SIZE, THREADGROUP_SIZE, THREADGROUP_SIZE)]
void MaterialSetupCS(
uint3 GroupId : SV_GroupID,
uint3 DispatchThreadId : SV_DispatchThreadID,
uint3 GroupThreadId : SV_GroupThreadID)
{
uint3 GridCoordinate = DispatchThreadId;
// Center of the voxel
float VoxelOffset = .5f;
float3 WorldPosition = ComputeCellTranslatedWorldPosition(GridCoordinate, VoxelOffset) - DFHackToFloat(PrimaryView.PreViewTranslation);
float GlobalDensityFirst = FogStruct.ExponentialFogParameters3.x * exp2(-FogStruct.ExponentialFogParameters.y * (WorldPosition.z - FogStruct.ExponentialFogParameters3.y));
float GlobalDensitySecond = FogStruct.ExponentialFogParameters2.z * exp2(-FogStruct.ExponentialFogParameters2.y * (WorldPosition.z - FogStruct.ExponentialFogParameters2.w));
float GlobalDensity = GlobalDensityFirst + GlobalDensitySecond;
float3 Albedo = GlobalAlbedo.rgb;
// Exponential height fog interprets density differently, match its behavior
float MatchHeightFogFactor = .5f;
GlobalDensity *= MatchHeightFogFactor;
float Extinction = max(GlobalDensity * GlobalExtinctionScale, 0);
float3 Scattering = Albedo * Extinction;
float3 AdditionalEmissive = 0.0f;
#if USE_LOCAL_FOG_VOLUMES
float CellOffset = .5f;
float SceneDepth;
const float3 TranslatedWorldPosition = ComputeCellTranslatedWorldPosition(GridCoordinate, CellOffset, SceneDepth);
const float3 BackCellTranslatedWorldPosition = ComputeCellTranslatedWorldPosition(GridCoordinate, float3(CellOffset, CellOffset, 1.0f), SceneDepth);
const float DistanceToCellBack = GetDistanceToCameraFromViewVector(BackCellTranslatedWorldPosition - PrimaryView.TranslatedWorldCameraOrigin);
if(DistanceToCellBack > LFVGlobalStartDistance)
{
const float3 FrontCellTranslatedWorldPosition = ComputeCellTranslatedWorldPosition(GridCoordinate, float3(CellOffset, CellOffset, 0.0f), SceneDepth);
const float DistanceToCellFront = GetDistanceToCameraFromViewVector(FrontCellTranslatedWorldPosition - PrimaryView.TranslatedWorldCameraOrigin);
float SoftDensityScale = 1;
if (DistanceToCellFront < LFVGlobalStartDistance && LFVGlobalStartDistance < DistanceToCellBack)
{
// Smoothly transition from the front clip planes of LFV to avoid bad straircase/aliasing visual effect.
// Extinction is basically scaled as a fonction of the start distance position inside the froxel along V.
SoftDensityScale = (DistanceToCellBack - LFVGlobalStartDistance) / max(0.0001, DistanceToCellBack - DistanceToCellFront);
}
float4 VertexClipSpacePosition = mul(float4(TranslatedWorldPosition.xyz, 1), PrimaryView.TranslatedWorldToClip);
float2 SvPosition = (VertexClipSpacePosition.xy / VertexClipSpacePosition.w * float2(.5f, -.5f) + .5f) * PrimaryView.ViewSizeAndInvSize.xy;
uint2 TilePos = clamp(uint2(SvPosition.xy / float(LFVTilePixelSize)), uint2(0, 0), LFVTileDataResolution - 1);
uint LFVCount = LFVTileDataTex[uint3(TilePos, 0)];
for (uint LFVIndex = 0; LFVIndex < LFVCount; ++LFVIndex)
{
uint InstanceIndex = LFVTileDataTex[uint3(TilePos, 1 + LFVIndex)];
// LFV_TODO skip if out of sphere of influence
FLocalFogVolumeGPUInstanceData FogInstance = GetLocalFogVolumeGPUInstanceData(InstanceIndex);
float3 UnitSpacePos = mul(TranslatedWorldPosition - FogInstance.TranslatedWorlPos, FogInstance.PreTranslatedInvTransform);
float DistToCenter = length(UnitSpacePos - float3(0.0f, 0.0f, 0.0f));
float SphereFade = saturate(1.0f - DistToCenter);
// Evaluate both radial and height local fog contribution
float LFVExtinctionHeight = SphereFade <= 0.0f ? 0.0f : FogInstance.HeightFogExtinction * exp(-FogInstance.HeightFogFalloff * (UnitSpacePos.z - FogInstance.HeightFogOffset));
float LFVExtinctionRadial = SphereFade <= 0.0f ? 0.0f : FogInstance.RadialFogExtinction * pow(SphereFade, 0.82); // 0.82 is a correction factor needed to get a better match. It is unclear why it is needed...
// As for the local fog volume analitical evaluation, we want to combine both two optical depth for the "opacity"=1-transmittance to be combined together.
// This helps to have soft edges when using height fog.
/*
In this case, we have to find math so that the combined extinction resulting in a transmittance representing the multiplication of coverage = 1 - transmittance factors.
Reminder
Transmittance as T
Optical Depth as Tau
T = exp(-Tau)
Tau = -log(T) (*)
We want to combine the both mode transmittance Tr (radial) and Th (height) as done in LocalFogVolumeEvaluateAnalyticalIntegral to have
T = 1 - (1-Tr)(1-Th) // Where (1-Tr) is virtual coverage of the medium expressed from transmittance for the radial fog.
T = 1 - (1 - exp(-Tau_r)) (1 - exp(-Tau_h))) // Where Tr = exp(-Tau_r)
-Tau = log(1 - (1 - exp(-Tau_r)) (1 - exp(-Tau_h))) // Using (*)
Tau =-log(exp(-Tau_r) - exp((-Tau_r-Tau_h) + exp(-Tau_h)))
Optical Depth Tau = Sigma_t * dt so it is linear with respect to dt and extinction Sigma_t. So we can also write
Sigma_T = -log(exp(-Sigma_T_r) - exp((-Sigma_T_r-Sigma_T_h) + exp(-Sigma_T_h)))
Factoring some values a bit we can get
A = exp(-Sigma_T_r)
B = exp(-Sigma_T_h)
Sigma_T = -log(A - A*B + B)
*/
// Optical Depth is linear with Extinction so there is no need to account for the step length.
float FactorA = exp(-LFVExtinctionHeight);
float FactorB = exp(-LFVExtinctionRadial);
float LFVExtinction = -log(FactorA - FactorA * FactorB + FactorB);
LFVExtinction = max(0.0f, LFVExtinction);
// Convert extinction from unit volume 1/m into extinction for volumetric fog as 1/cm
LFVExtinction *= (1.0f / METER_TO_CENTIMETER);
LFVExtinction = min(LFVMaxDensityIntoVolumetricFog, LFVExtinction) * SoftDensityScale;
float3 LFVScattering = LFVExtinction * FogInstance.Albedo;
Scattering += LFVScattering;
Extinction += LFVExtinction;
#if USE_EMISSIVE
// Emissive follow the density of the medium
AdditionalEmissive += LFVExtinction * FogInstance.Emissive;
#endif
}
}
#endif // USE_LOCAL_FOG_VOLUMES
if (all((int3)GridCoordinate < VolumetricFog.ResourceGridSizeInt))
{
RWVBufferA[GridCoordinate] = float4(Scattering, Extinction);
#if USE_EMISSIVE
float3 HeightFogEmissive = GlobalEmissive.rgb;
#if PROJECT_EXPFOG_MATCHES_VFOG
HeightFogEmissive.rgb *= GlobalDensity; // Emissive needs to follow the height fog density for a better match
#endif
RWVBufferB[GridCoordinate] = float4(HeightFogEmissive.rgb + AdditionalEmissive, 0);
#endif
}
}
#endif
// Positive g = forward scattering
// Zero g = isotropic
// Negative g = backward scattering
float PhaseFunction(float g, float CosTheta)
{
return HenyeyGreensteinPhase(g, CosTheta);
}
struct FWriteToSliceVertexOutput
{
FScreenVertexOutput Vertex;
#if USING_VERTEX_SHADER_LAYER
uint LayerIndex : SV_RenderTargetArrayIndex;
#else
uint LayerIndex : TEXCOORD1;
#endif
};
/** Z index of the minimum slice in the range. */
int MinZ;
float4 ViewSpaceBoundingSphere;
float4x4 ViewToVolumeClip;
/** Vertex shader that writes to a range of slices of a volume texture. */
void WriteToBoundingSphereVS(
float2 InPosition : ATTRIBUTE0,
float2 InUV : ATTRIBUTE1,
uint LayerIndex : SV_InstanceID,
out FWriteToSliceVertexOutput Output
)
{
float SliceDepth = ComputeDepthFromZSlice(LayerIndex + MinZ);
float SliceDepthOffset = abs(SliceDepth - ViewSpaceBoundingSphere.z);
if (SliceDepthOffset < ViewSpaceBoundingSphere.w)
{
// Compute the radius of the circle formed by the intersection of the bounding sphere and the current depth slice
float SliceRadius = sqrt(ViewSpaceBoundingSphere.w * ViewSpaceBoundingSphere.w - SliceDepthOffset * SliceDepthOffset);
// Place the quad vertex to tightly bound the circle
float3 ViewSpaceVertexPosition = float3(ViewSpaceBoundingSphere.xy + (InUV * 2 - 1) * SliceRadius, SliceDepth);
Output.Vertex.Position = mul(float4(ViewSpaceVertexPosition, 1), ViewToVolumeClip);
float2 ClipRatio = float2(1.0f, 1.0f);
// Scale rendered position to account for frustum difference between fog voxelization and rendered scene
Output.Vertex.Position.xy *= ClipRatio;
}
else
{
// Slice does not intersect bounding sphere, emit degenerate triangle
Output.Vertex.Position = 0;
}
// Debug - draw to entire texture in xy
//Output.Vertex.Position = float4(InUV * float2(2, -2) + float2(-1, 1), 0, 1);
Output.Vertex.UV = 0;
Output.LayerIndex = LayerIndex + MinZ;
}
float HistoryWeight;
float LightScatteringSampleJitterMultiplier;
float4 FrameJitterOffsets[16];
uint HistoryMissSuperSampleCount;
float PhaseG;
float InverseSquaredLightDistanceBiasScale;
int VirtualShadowMapId;
#ifndef HISTORY_MISS_SUPER_SAMPLE_COUNT
#define HISTORY_MISS_SUPER_SAMPLE_COUNT 1
#endif
#include "LightFunctionAtlas/LightFunctionAtlasCommon.usf"
Texture2D<float> ConservativeDepthTexture;
float2 PrevConservativeDepthTextureSize;
Texture2D<float> PrevConservativeDepthTexture;
uint UseConservativeDepthTexture;
#if USE_RAYTRACED_SHADOWS
#include "/Engine/Private/RayTracing/RayTracingCommon.ush"
RaytracingAccelerationStructure TLAS;
float ComputeRayTracedShadowFactor(float3 TranslatedWorldPosition, float3 LightDirection, float TMax)
{
uint RayFlags = RAY_FLAG_SKIP_CLOSEST_HIT_SHADER | RAY_FLAG_ACCEPT_FIRST_HIT_AND_END_SEARCH;
uint RaytracingMask = RAY_TRACING_MASK_SHADOW | RAY_TRACING_MASK_THIN_SHADOW;
FRayDesc Ray;
Ray.Origin = TranslatedWorldPosition;
Ray.Direction = LightDirection;
Ray.TMin = 0.0f;
Ray.TMax = TMax;
FMinimalPayload MinimalPayload = TraceVisibilityRay(
TLAS,
RayFlags,
RaytracingMask,
Ray);
if (MinimalPayload.IsHit())
{
return 0.0f;
}
return 1.0;
}
#endif
float4 InjectShadowedLocalLightCommon(uint3 GridCoordinate)
{
float4 OutScattering = 0;
// Somehow pixels are being rasterized outside of the viewport on a 970 GTX, perhaps due to use of a geometry shader bypassing the viewport scissor.
// This triggers the HistoryMissSuperSampleCount path causing significant overhead for shading off-screen pixels.
if (all(int3(GridCoordinate) < VolumetricFog.ResourceGridSizeInt))
{
FDeferredLightData LightData = InitDeferredLightFromUniforms();
float VolumetricScatteringIntensity = DeferredLightUniforms.VolumetricScatteringIntensity;
float3 L = 0;
float3 ToLight = 0;
uint NumSuperSamples = 1;
#if USE_TEMPORAL_REPROJECTION
float3 HistoryUV = ComputeHistoryVolumeUVFromTranslatedPos(ComputeCellTranslatedWorldPosition(GridCoordinate, .5f), UnjitteredPrevTranslatedWorldToClip);
float HistoryAlpha = HistoryWeight;
FLATTEN
if (any(HistoryUV < 0) || any(HistoryUV > 1))
{
HistoryAlpha = 0;
}
NumSuperSamples = HistoryAlpha < .001f ? HistoryMissSuperSampleCount : 1;
#endif
const bool bSoftFadeEnabled = VolumetricFog.LightSoftFading > 0;
for (uint SampleIndex = 0; SampleIndex < NumSuperSamples; SampleIndex++)
{
float3 CellOffset = FrameJitterOffsets[SampleIndex].xyz;
//float CellOffset = .5f;
float SceneDepth;
float3 TranslatedWorldPosition = ComputeCellTranslatedWorldPosition(GridCoordinate, CellOffset, SceneDepth);
float3 CameraVector = GetCameraVectorFromTranslatedWorldPosition(TranslatedWorldPosition);
float CellRadius = length(TranslatedWorldPosition - ComputeCellTranslatedWorldPosition(GridCoordinate + uint3(1, 1, 1), CellOffset));
float Cell2DRadius = length(TranslatedWorldPosition - ComputeCellTranslatedWorldPosition(GridCoordinate + uint3(1, 1, 0), CellOffset));
float LightVolumetricSoftFadeDistance = VolumetricFog.LightSoftFading * Cell2DRadius;
// Bias the inverse squared light falloff based on voxel size to prevent aliasing near the light source
float DistanceBias = max(CellRadius * InverseSquaredLightDistanceBiasScale, 1);
float3 LightColor = DeferredLightUniforms.Color;
float LightMask = GetLocalLightAttenuation(TranslatedWorldPosition, LightData, ToLight, L);
float Lighting;
if( LightData.bRectLight )
{
FRect Rect = GetRect(ToLight, LightData);
float SoftFade = 1.0f;
#if USE_LIGHT_SOFT_FADING
if (bSoftFadeEnabled)
{
SoftFade = GetRectLightVolumetricSoftFading(LightData, Rect, LightVolumetricSoftFadeDistance, ToLight);
}
#endif // USE_LIGHT_SOFT_FADING
Lighting = SoftFade * IntegrateLight(Rect);
#if USE_RECT_LIGHT_TEXTURE
if (IsRectVisible(Rect))
{
const FRectTexture SourceTexture = ConvertToRectTexture(LightData);
const float3 FalloffColor = SampleSourceTexture(Lighting, Rect, SourceTexture);
LightColor *= FalloffColor;
}
#endif // USE_RECT_LIGHT_TEXTURE
}
else
{
FCapsuleLight Capsule = GetCapsule(ToLight, LightData);
Capsule.DistBiasSqr = Pow2(DistanceBias);
float SoftFade = 1.0f;
#if USE_LIGHT_SOFT_FADING
if (LightData.bSpotLight && bSoftFadeEnabled)
{
SoftFade = GetSpotLightVolumetricSoftFading(LightData, LightVolumetricSoftFadeDistance, ToLight);
}
#endif // USE_LIGHT_SOFT_FADING
Lighting = SoftFade * IntegrateLight(Capsule, LightData.bInverseSquared);
}
float CombinedAttenuation = Lighting * LightMask;
float ShadowFactor = 1.0f;
bool IsPointLight = LightData.bRadialLight && !LightData.bSpotLight;
#if ENABLE_SHADOW_COMPUTATION
if (CombinedAttenuation > 0)
{
#if USE_RAYTRACED_SHADOWS
ShadowFactor = ComputeRayTracedShadowFactor(TranslatedWorldPosition, normalize(ToLight), length(ToLight));
#else
bool bUnused = false;
ShadowFactor = ComputeVolumeShadowing(TranslatedWorldPosition, IsPointLight, LightData.bSpotLight, bUnused);
#if VIRTUAL_SHADOW_MAP
FVirtualShadowMapSampleResult VirtualShadowMapSample = SampleVirtualShadowMapTranslatedWorld(VirtualShadowMapId, TranslatedWorldPosition);
ShadowFactor *= VirtualShadowMapSample.ShadowFactor;
#endif // VIRTUAL_SHADOW_MAP
#endif // USE_RAYTRACED_SHADOWS
}
#endif
FLightFunctionColor LightFunctionColor = 1.0f;
#if USE_LIGHT_FUNCTION_ATLAS
LightFunctionColor = GetLocalLightFunctionCommon(TranslatedWorldPosition, LightData.LightFunctionAtlasLightIndex);
#endif
OutScattering.rgb += LightColor * LightFunctionColor * (PhaseFunction(PhaseG, dot(L, -CameraVector)) * CombinedAttenuation * ShadowFactor * VolumetricScatteringIntensity);
// To debug culling
//OutScattering.rgb += DeferredLightUniforms.Color * .0000001f;
}
// We pre-expose the buffer containing shadowed local lights luminance contribution. This helps maintaining details and color at high exposure for very bright scenes.
OutScattering.rgb *= View.PreExposure / (float)NumSuperSamples;
}
return OutScattering;
}
#if USE_RAYTRACED_SHADOWS
RWTexture3D<float4> OutVolumeTexture;
uint3 OutputOffset;
int FirstSlice;
int3 GetVoxelIndex(int ThreadIndex, int3 Resolution)
{
int SliceSize = Resolution.x * Resolution.y;
int SliceIndex = ThreadIndex / SliceSize;
int SliceRemainder = ThreadIndex - SliceIndex * SliceSize;
return int3(
SliceRemainder % Resolution.x,
SliceRemainder / Resolution.x,
SliceIndex
);
}
RAY_TRACING_ENTRY_RAYGEN(InjectShadowedLocalLightRGS)
{
int3 Position = GetVoxelIndex(DispatchRaysIndex().x, VolumetricFog.ResourceGridSizeInt);
Position.z += FirstSlice;
float4 OutScattering = InjectShadowedLocalLightCommon(Position);
float4 DestValue = OutVolumeTexture[Position];
OutVolumeTexture[Position] = DestValue + OutScattering;
}
RWTexture3D<float> OutShadowVolumeTexture;
RAY_TRACING_ENTRY_RAYGEN(InjectShadowedDirectionalLightRGS)
{
int3 Position = GetVoxelIndex(DispatchRaysIndex().x, VolumetricFog.ResourceGridSizeInt);
const int SampleIndex = 0;
uint3 Rand32Bits = Rand4DPCG32(int4(Position.xyz, View.StateFrameIndexMod8 + 8 * SampleIndex)).xyz;
float3 Rand3D = (float3(Rand32Bits) / float(uint(0xffffffff))) * 2.0f - 1.0f;
float3 CellOffset = FrameJitterOffsets[SampleIndex].xyz + LightScatteringSampleJitterMultiplier * Rand3D;
const float3 TranslatedWorldPosition = ComputeCellTranslatedWorldPosition(Position, CellOffset);
OutShadowVolumeTexture[Position] = ComputeRayTracedShadowFactor(TranslatedWorldPosition, ForwardLightStruct.DirectionalLightDirection, 1.0e27);
}
#endif // USE_RAYTRACED_SHADOWS
void InjectShadowedLocalLightPS(
FWriteToSliceGeometryOutput Input,
out float4 OutScattering : SV_Target0
)
{
float4 SvPosition = Input.Vertex.Position;
OutScattering = 0;
if (UseConservativeDepthTexture > 0)
{
const float SceneDepth = ConservativeDepthTexture.Load(uint3(SvPosition.xy, 0));
if (SceneDepth > SvPosition.z)
{
clip(-1); // If the froxel is behind conservative front depth, skip the light injection.
return;
}
}
OutScattering = InjectShadowedLocalLightCommon(uint3(SvPosition.xy, Input.LayerIndex));
}
Texture3D<float4> VBufferA;
Texture3D<float4> VBufferB;
uint UseEmissive;
Texture3D<float4> LightScatteringHistory;
SamplerState LightScatteringHistorySampler;
float2 LightScatteringHistoryPreExposureAndInv;
Texture3D<float4> LocalShadowedLightScattering;
RWTexture3D<float4> RWLightScattering;
#if DISTANCE_FIELD_SKY_OCCLUSION
float HemisphereConeTraceAgainstGlobalDistanceFieldClipmap(
uniform uint ClipmapIndex,
float3 TranslatedWorldShadingPosition,
float3 ConeDirection,
float TanConeHalfAngle)
{
float MinStepSize = GlobalVolumeTranslatedCenterAndExtent[ClipmapIndex].w * 2 / 100.0f;
float InvAOGlobalMaxOcclusionDistance = 1.0f / AOGlobalMaxOcclusionDistance;
float MinVisibility = 1;
float WorldStepOffset = 2;
LOOP
for (uint StepIndex = 0; StepIndex < NUM_CONE_STEPS && WorldStepOffset < AOGlobalMaxOcclusionDistance; StepIndex++)
{
float3 TranslatedWorldSamplePosition = TranslatedWorldShadingPosition + ConeDirection * WorldStepOffset;
float DistanceToOccluder = SampleGlobalDistanceField(TranslatedWorldSamplePosition, AOGlobalMaxOcclusionDistance, ClipmapIndex);
float SphereRadius = WorldStepOffset * TanConeHalfAngle;
float InvSphereRadius = rcpFast(SphereRadius);
// Derive visibility from 1d intersection
float Visibility = saturate(DistanceToOccluder * InvSphereRadius);
float OccluderDistanceFraction = (WorldStepOffset + DistanceToOccluder) * InvAOGlobalMaxOcclusionDistance;
// Fade out occlusion based on distance to occluder to avoid a discontinuity at the max AO distance
Visibility = max(Visibility, saturate(OccluderDistanceFraction * OccluderDistanceFraction * .6f));
MinVisibility = min(MinVisibility, Visibility);
WorldStepOffset += max(DistanceToOccluder, MinStepSize);
}
return MinVisibility;
}
float HemisphereConeTraceAgainstGlobalDistanceField(float3 TranslatedWorldShadingPosition, float3 ConeDirection, float TanConeHalfAngle)
{
int MinClipmapIndex = -1;
for (uint ClipmapIndex = 0; ClipmapIndex < NumGlobalSDFClipmaps; ++ClipmapIndex)
{
float DistanceFromClipmap = ComputeDistanceFromBoxToPointInside(GlobalVolumeTranslatedCenterAndExtent[ClipmapIndex].xyz, GlobalVolumeTranslatedCenterAndExtent[ClipmapIndex].www, TranslatedWorldShadingPosition);
if (DistanceFromClipmap > AOGlobalMaxOcclusionDistance)
{
MinClipmapIndex = ClipmapIndex;
break;
}
}
float MinVisibility = 1.0f;
if (MinClipmapIndex >= 0)
{
MinVisibility = HemisphereConeTraceAgainstGlobalDistanceFieldClipmap(MinClipmapIndex, TranslatedWorldShadingPosition, ConeDirection, TanConeHalfAngle);
}
return MinVisibility;
}
#endif
float SkyLightUseStaticShadowing;
float ComputeSkyVisibility(float3 TranslatedWorldPosition, float3 BrickTextureUVs)
{
float Visibility = 1;
#if DISTANCE_FIELD_SKY_OCCLUSION
// Trace one 45 degree cone straight up for sky occlusion
float TanConeHalfAngle = tan((float)PI / 4);
Visibility = HemisphereConeTraceAgainstGlobalDistanceField(TranslatedWorldPosition, float3(0, 0, 1), TanConeHalfAngle);
#endif
#if ALLOW_STATIC_LIGHTING
if (SkyLightUseStaticShadowing > 0)
{
float3 SkyBentNormal = GetVolumetricLightmapSkyBentNormal(BrickTextureUVs);
Visibility = length(SkyBentNormal);
}
#endif
return Visibility;
}
float4x4 DirectionalLightFunctionTranslatedWorldToShadow;
Texture2D DirectionalLightLightFunctionTexture;
SamplerState DirectionalLightLightFunctionSampler;
uint DirectionalApplyLightFunctionFromAtlas;
uint DirectionalLightFunctionAtlasLightIndex;
FLightFunctionColor GetDirectionalLightFunction(float3 TranslatedWorldPosition) // directional light
{
#if USE_LIGHT_FUNCTION_ATLAS // Used when injecting lights from the light grid
if (DirectionalApplyLightFunctionFromAtlas > 0)
{
return GetLocalLightFunctionCommon(TranslatedWorldPosition, DirectionalLightFunctionAtlasLightIndex);
}
#endif
float4 HomogeneousShadowPosition = mul(float4(TranslatedWorldPosition, 1), DirectionalLightFunctionTranslatedWorldToShadow);
float2 LightFunctionUV = HomogeneousShadowPosition.xy * .5f + .5f;
LightFunctionUV.y = 1 - LightFunctionUV.y;
return Texture2DSampleLevel(DirectionalLightLightFunctionTexture, DirectionalLightLightFunctionSampler, LightFunctionUV, 0).x;
}
uint SampleSkyLightDiffuseEnvMap;
float2 UseHeightFogColors; // x=override directional light using height fog inscattering color, y=override sky light using heigh fog inscattering cubemap
float UseDirectionalLightShadowing;
float StaticLightingScatteringIntensity;
#if SHADING_PATH_MOBILE
uint MobileHasDirectionalLight;
float3 MobileDirectionalLightColor;
float3 MobileDirectionalLightDirection;
#endif
Texture3D<float> RaytracedShadowsVolume;
Texture3D<float3> MegaLightsVolume;
// Modify UV used to sample LightScatteringHistory to avoid cells that were occluded last frame
float2 FixupHistoryUV(float2 UV, float2 Size, float PrevCellNDCPositionZ, out bool bHasValidHistory)
{
float2 FullResUV = UV * Size;
uint2 ScreenCoord = floor(FullResUV - 0.5f);
float2 BilinearWeights = frac(FullResUV - 0.5f);
float2 GatherUV = (ScreenCoord + 1.0f) / Size;
float2 FullResOffset = FullResUV - ScreenCoord;
const float2 PrevConservativeDepthGatherUV = GatherUV / (PrimaryView.VolumetricFogViewGridUVToPrevViewRectUV.xy * PrimaryView.VolumetricFogPrevViewGridRectUVToResourceUV.xy);
const float4 PrevSceneDepths = PrevConservativeDepthTexture.Gather(LightScatteringHistorySampler, PrevConservativeDepthGatherUV);
const bool4 ValidSamples = PrevSceneDepths < PrevCellNDCPositionZ;
bHasValidHistory = true;
if (all(ValidSamples))
{
return UV;
}
// Gather pattern:
// wz
// xy
if (all(ValidSamples.wz))
{
return (ScreenCoord + float2(FullResOffset.x, 0.5f)) / Size;
}
if (all(ValidSamples.xy))
{
return (ScreenCoord + float2(FullResOffset.x, 1.5f)) / Size;
}
if (all(ValidSamples.wx))
{
return (ScreenCoord + float2(0.5f, FullResOffset.y)) / Size;
}
if (all(ValidSamples.zy))
{
return (ScreenCoord + float2(1.5f, FullResOffset.y)) / Size;
}
if (ValidSamples.x)
{
return (ScreenCoord + float2(0.5f, 1.5f)) / Size;
}
if (ValidSamples.y)
{
return (ScreenCoord + float2(1.5f, 1.5f)) / Size;
}
if (ValidSamples.w)
{
return (ScreenCoord + float2(0.5f, 0.5f)) / Size;
}
if (ValidSamples.z)
{
return (ScreenCoord + float2(1.5f, 0.5f)) / Size;
}
bHasValidHistory = false;
return UV;
}
#ifdef LightScatteringCS
[numthreads(THREADGROUP_SIZE_X, THREADGROUP_SIZE_Y, THREADGROUP_SIZE_Z)]
void LightScatteringCS(
uint3 GroupId : SV_GroupID,
uint3 DispatchThreadId : SV_DispatchThreadID,
uint3 GroupThreadId : SV_GroupThreadID)
{
uint3 GridCoordinate = DispatchThreadId;
float3 LightScattering = 0;
uint NumSuperSamples = 1;
#if USE_TEMPORAL_REPROJECTION
float3 HistoryUV = ComputeHistoryVolumeUVFromTranslatedPos(ComputeCellTranslatedWorldPosition(GridCoordinate, .5f), UnjitteredPrevTranslatedWorldToClip);
bool bHasValidHistory = true;
#endif
// If the froxel is behind front depth, do not evaluate any lighting.
if (UseConservativeDepthTexture > 0)
{
// expand by half voxel towards the camera to support bilinear filtering
const float3 FarDepthOffset = float3(0.5f, 0.5f, -0.5f);
float3 CellTranslatedWorldPosition = ComputeCellTranslatedWorldPosition(GridCoordinate, FarDepthOffset);
float4 CellNDCPosition = mul(float4(CellTranslatedWorldPosition, 1), PrimaryView.TranslatedWorldToClip);
CellNDCPosition.xyz /= CellNDCPosition.w;
float2 UVs = CellNDCPosition.xy * float2(0.5f, -0.5f) + 0.5f;
const float SceneDepth = ConservativeDepthTexture.Load(uint3(GridCoordinate.xy, 0));
if (SceneDepth > CellNDCPosition.z)
{
RWLightScattering[GridCoordinate] = float4(0.0f, 0.0f, 0.0f, 0.0f);
return;
}
#if USE_TEMPORAL_REPROJECTION
// TODO: investigate whether using CellTranslatedWorldPosition (which is biased towards camera) is correct here
// it might be more accurate to transform unbiased translated world position to previous frame clip space and apply bias towards camera in that space?
float4 PrevCellNDCPosition = mul(float4(CellTranslatedWorldPosition, 1), UnjitteredPrevTranslatedWorldToClip);
PrevCellNDCPosition.xyz /= PrevCellNDCPosition.w;
HistoryUV.xy = FixupHistoryUV(HistoryUV.xy, View.VolumetricFogPrevResourceGridSize.xy, PrevCellNDCPosition.z, bHasValidHistory);
#endif
}
#if USE_TEMPORAL_REPROJECTION
float HistoryAlpha = HistoryWeight;
// We need to test with View.VolumetricFogPrevUVMaxForTemporalBlend for HistoryUV because that is what we clamp with in ComputeHistoryVolumeUVFromTranslatedPos.
FLATTEN
if (any(HistoryUV < 0) || any(HistoryUV >= float3(View.VolumetricFogPrevUVMaxForTemporalBlend, 1.0f)) || !bHasValidHistory)
{
HistoryAlpha = 0;
}
// Supersample if the history was outside the camera frustum
// The compute shader is dispatched with extra threads, make sure those don't supersample
NumSuperSamples = HistoryAlpha < .001f && all(int3(GridCoordinate) < VolumetricFog.ViewGridSizeInt) ? HISTORY_MISS_SUPER_SAMPLE_COUNT : 1;
#endif
for (uint SampleIndex = 0; SampleIndex < NumSuperSamples; SampleIndex++)
{
uint3 Rand32Bits = Rand4DPCG32(int4(GridCoordinate.xyz, View.StateFrameIndexMod8 + 8 * SampleIndex)).xyz;
float3 Rand3D = (float3(Rand32Bits) / float(uint(0xffffffff))) * 2.0f - 1.0f;
float3 CellOffset = FrameJitterOffsets[SampleIndex].xyz + LightScatteringSampleJitterMultiplier * Rand3D;
//CellOffset = 0.5f;
float SceneDepth;
float3 TranslatedWorldPosition = ComputeCellTranslatedWorldPosition(GridCoordinate, CellOffset, SceneDepth);
float3 WorldPosition = TranslatedWorldPosition - DFHackToFloat(PrimaryView.PreViewTranslation); // LWC_TODO
float CameraVectorLength = GetDistanceToCameraFromViewVector(TranslatedWorldPosition - PrimaryView.TranslatedWorldCameraOrigin);
float3 CameraVector = GetCameraVectorFromTranslatedWorldPosition(TranslatedWorldPosition);
#if SHADING_PATH_MOBILE
const bool bHasDirectionalLight = MobileHasDirectionalLight > 0;
const float3 InDirectionalLightColor = MobileDirectionalLightColor; // DirectionalLightVolumetricScatteringIntensity is baked in MobileDirectionalLightColor
const float3 InDirectionalLightDirection= MobileDirectionalLightDirection;
const bool bEvaluateDirectionalLight = true;
#else
const bool bHasDirectionalLight = ForwardLightStruct.HasDirectionalLight;
const float3 InDirectionalLightColor = ForwardLightStruct.DirectionalLightColor * ForwardLightStruct.DirectionalLightVolumetricScatteringIntensity;
const float3 InDirectionalLightDirection= ForwardLightStruct.DirectionalLightDirection;
const bool bEvaluateDirectionalLight = !USE_MEGA_LIGHTS || !ForwardLightStruct.DirectionalLightHandledByMegaLights;
#endif
BRANCH
if (bHasDirectionalLight && bEvaluateDirectionalLight)
{
float ShadowFactor = 1;
if (UseDirectionalLightShadowing > 0)
{
ShadowFactor *= ComputeDirectionalLightStaticShadowing(TranslatedWorldPosition);
bool bUnused = false;
ShadowFactor *= ComputeDirectionalLightDynamicShadowing(TranslatedWorldPosition, SceneDepth, bUnused);
#if VIRTUAL_SHADOW_MAP
FVirtualShadowMapSampleResult VirtualShadowMapSample = SampleVirtualShadowMapTranslatedWorld(ForwardLightStruct.DirectionalLightVSM, TranslatedWorldPosition);
ShadowFactor *= VirtualShadowMapSample.ShadowFactor;
#endif
#if USE_RAYTRACED_SHADOWS_VOLUME
ShadowFactor *= RaytracedShadowsVolume[GridCoordinate];
#endif
#if !SHADING_PATH_MOBILE
if (CloudShadowmapStrength > 0.0f)
{
float OutOpticalDepth = 0.0f;
ShadowFactor *= lerp(1.0f, GetCloudVolumetricShadow(TranslatedWorldPosition, CloudShadowmapTranslatedWorldToLightClipMatrix, CloudShadowmapFarDepthKm, CloudShadowmapTexture, CloudShadowmapSampler, OutOpticalDepth), CloudShadowmapStrength);
}
#endif
}
FLightFunctionColor LightFunctionColor = GetDirectionalLightFunction(TranslatedWorldPosition);
float3 DirectionalLightColor = InDirectionalLightColor;
if (UseHeightFogColors.x > 0)
{
// Attempt to maintain intensity ratio between sky and sun
DirectionalLightColor = VolumetricFog.HeightFogDirectionalLightInscatteringColor * Luminance(InDirectionalLightColor);
}
LightScattering += DirectionalLightColor * LightFunctionColor * ShadowFactor * PhaseFunction(PhaseG, dot(InDirectionalLightDirection, -CameraVector));
}
FTwoBandSHVector RotatedHGZonalHarmonic;
RotatedHGZonalHarmonic.V = float4(1.0f, CameraVector.y, CameraVector.z, CameraVector.x) * float4(1.0f, PhaseG, PhaseG, PhaseG); // Note: I believe PhaseG here hsould be negated to match the sky & environment.
float3 BrickTextureUVs = 0;
#if ALLOW_STATIC_LIGHTING
if (View.SkyLightVolumetricScatteringIntensity > 0 || StaticLightingScatteringIntensity > 0)
{
BrickTextureUVs = ComputeVolumetricLightmapBrickTextureUVs(WorldPosition);
}
#endif
#if LUMEN_GI
// Lumen Dynamic GI + shadowed Skylight
FTwoBandSHVectorRGB TranslucencyGISH = GetTranslucencyGIVolumeLighting(DFPromote(WorldPosition), PrimaryView.WorldToClip, false); // LUMEN_LWC_TODO
LightScattering += max(DotSH(TranslucencyGISH, RotatedHGZonalHarmonic), 0);
#else
// Skylight
if (View.SkyLightVolumetricScatteringIntensity > 0)
{
float3 SkyLighting;
if (UseHeightFogColors.y > 0)
{
float3 HeightFogInscatteringColor = ComputeInscatteringColor(CameraVector, CameraVectorLength);
float ScalarFactor = SHAmbientFunction();
FTwoBandSHVectorRGB SkyIrradianceSH;
SkyIrradianceSH.R.V = float4(ScalarFactor * HeightFogInscatteringColor.r, 0, 0, 0);
SkyIrradianceSH.G.V = float4(ScalarFactor * HeightFogInscatteringColor.g, 0, 0, 0);
SkyIrradianceSH.B.V = float4(ScalarFactor * HeightFogInscatteringColor.b, 0, 0, 0);
SkyLighting = max(DotSH(SkyIrradianceSH, RotatedHGZonalHarmonic), 0);
}
else
{
// NOTE it should be CameraVector * PhaseG. But we keep it like that for backward compatibility for now. (visible when r.Lumen.TranslucencyVolume.Enable 0)
SkyLighting = SampleSkyLightDiffuseEnvMap > 0 ? View.SkyLightColor.rgb * GetSkySHDiffuseSimple(CameraVector * -PhaseG) : 0.0f.xxx;
}
float SkyVisibility = ComputeSkyVisibility(TranslatedWorldPosition, BrickTextureUVs);
LightScattering += (SkyVisibility * View.SkyLightVolumetricScatteringIntensity) * SkyLighting;
}
#endif
#if ALLOW_STATIC_LIGHTING
// Indirect lighting of Stationary lights and Direct + Indirect lighting of Static lights
if (StaticLightingScatteringIntensity > 0)
{
FTwoBandSHVectorRGB IrradianceSH = GetVolumetricLightmapSH2(BrickTextureUVs);
LightScattering += (StaticLightingScatteringIntensity / PI) * max(DotSH(IrradianceSH, RotatedHGZonalHarmonic), 0);
}
#endif
uint GridIndex = ComputeLightGridCellIndex(GridCoordinate.xy * VolumetricFog.FogGridToPixelXY, SceneDepth, 0);
const FCulledLightsGridHeader CulledLightsGridHeader = GetCulledLightsGridHeader(GridIndex);
float CellRadius = length(TranslatedWorldPosition - ComputeCellTranslatedWorldPosition(GridCoordinate + uint3(1, 1, 1), CellOffset));
float Cell2DRadius = length(TranslatedWorldPosition - ComputeCellTranslatedWorldPosition(GridCoordinate + uint3(1, 1, 0), CellOffset));
float LightVolumetricSoftFadeDistance = VolumetricFog.LightSoftFading * Cell2DRadius;
// Bias the inverse squared light falloff based on voxel size to prevent aliasing near the light source
float DistanceBiasSqr = max(CellRadius * InverseSquaredLightDistanceBiasScale, 1);
DistanceBiasSqr *= DistanceBiasSqr;
#if USE_MEGA_LIGHTS
// when using Mega Lights Volume only need to apply "non-mega" lights here
const uint NumLights = CulledLightsGridHeader.NumLights - CulledLightsGridHeader.NumMegaLights;
#else
const uint NumLights = CulledLightsGridHeader.NumLights;
#endif
// Forward lighting of unshadowed point and spot lights
LOOP
for (uint GridLightListIndex = 0; GridLightListIndex < NumLights; GridLightListIndex++)
{
const FLocalLightData LocalLight = GetLocalLightDataFromGrid(CulledLightsGridHeader.DataStartIndex + GridLightListIndex, 0);
#if USE_MEGA_LIGHTS
checkSlow(!UnpackIsHandledByMegaLights(LocalLight));
#endif
const float VolumetricScatteringIntensity = UnpackVolumetricScatteringIntensity(LocalLight);
if (VolumetricScatteringIntensity > 0)
{
const bool bSoftFadeEnabled = VolumetricFog.LightSoftFading > 0;
const FDeferredLightData LightData = ConvertToDeferredLight(LocalLight);
float3 L = 0;
float3 ToLight = 0;
float LightMask = GetLocalLightAttenuation(TranslatedWorldPosition, LightData, ToLight, L);
float Lighting;
if( LightData.bRectLight )
{
FRect Rect = GetRect( ToLight, LightData );
float SofFade = 1.0f;
#if USE_LIGHT_SOFT_FADING
if (bSoftFadeEnabled)
{
SofFade = GetRectLightVolumetricSoftFading(LightData, Rect, LightVolumetricSoftFadeDistance, ToLight);
}
#endif // USE_LIGHT_SOFT_FADING
Lighting = SofFade * IntegrateLight(Rect);
}
else
{
FCapsuleLight Capsule = GetCapsule(ToLight, LightData);
Capsule.DistBiasSqr = DistanceBiasSqr;
float SofFade = 1.0f;
#if USE_LIGHT_SOFT_FADING
if (LightData.bSpotLight && bSoftFadeEnabled)
{
SofFade = GetSpotLightVolumetricSoftFading(LightData, LightVolumetricSoftFadeDistance, ToLight);
}
#endif // USE_LIGHT_SOFT_FADING
Lighting = SofFade * IntegrateLight(Capsule, LightData.bInverseSquared);
}
float CombinedAttenuation = Lighting * LightMask;
FLightFunctionColor LightFunctionColor = 1.0f;
#if USE_LIGHT_FUNCTION_ATLAS
LightFunctionColor = GetLocalLightFunctionCommon(TranslatedWorldPosition, LightData.LightFunctionAtlasLightIndex);
#endif
LightScattering += LightData.Color * LightFunctionColor * (PhaseFunction(PhaseG, dot(L, -CameraVector)) * CombinedAttenuation * VolumetricScatteringIntensity);
// To debug culling
//LightScattering += UnpackLightColor(LocalLight) * .0000001f;
}
}
}
LightScattering /= (float)NumSuperSamples;
// Shadowed point and spot lights were computed earlier.
// Note: this texture was pre-exposed from InjectShadowedLocalLightPS, se we revert pre-exposure.
LightScattering += View.OneOverPreExposure * LocalShadowedLightScattering[GridCoordinate].xyz;
#if USE_MEGA_LIGHTS
{
// Inject MegaLights which were computed earlier. This texture was already pre-exposed, so need to rever it here.
LightScattering += MegaLightsVolume[GridCoordinate] * View.OneOverPreExposure;
}
#endif
float4 MaterialScatteringAndExtinction = VBufferA[GridCoordinate];
float Extinction = MaterialScatteringAndExtinction.w;
float3 MaterialEmissive = 0.0f;
BRANCH
if (UseEmissive > 0)
{
MaterialEmissive = VBufferB[GridCoordinate].xyz;
}
float4 PreExposedScatteringAndExtinction = float4(View.PreExposure * (LightScattering * MaterialScatteringAndExtinction.xyz + MaterialEmissive), Extinction);
#if USE_TEMPORAL_REPROJECTION
BRANCH
if (HistoryAlpha > 0)
{
float4 PreExposedHistoryScatteringAndExtinction = Texture3DSampleLevel(LightScatteringHistory, LightScatteringHistorySampler, min(HistoryUV, float3(View.VolumetricFogPrevUVMax, 1.0f)), 0);
PreExposedHistoryScatteringAndExtinction.rgb *= LightScatteringHistoryPreExposureAndInv.y * View.PreExposure; // Bring previous frame pre exposed history luminance as current frame pre exposed luminance. Leave extinction untouched!
PreExposedScatteringAndExtinction = lerp(PreExposedScatteringAndExtinction, PreExposedHistoryScatteringAndExtinction, HistoryAlpha);
}
#endif
// Visualize history rejection for debugging purposes
#if 0 && USE_TEMPORAL_REPROJECTION
if (HistoryAlpha < 0.001f)
{
PreExposedScatteringAndExtinction = float4(View.PreExposure * float3(1,0,0), Extinction);
}
#endif
if (all(int3(GridCoordinate) < VolumetricFog.ResourceGridSizeInt))
{
PreExposedScatteringAndExtinction = MakePositiveFinite(PreExposedScatteringAndExtinction);
RWLightScattering[GridCoordinate] = PreExposedScatteringAndExtinction;
}
}
#endif
Texture3D<float4> LightScattering;
RWTexture3D<float4> RWIntegratedLightScattering;
float VolumetricFogNearFadeInDistanceInv;
#ifdef FinalIntegrationCS
[numthreads(THREADGROUP_SIZE, THREADGROUP_SIZE, 1)]
void FinalIntegrationCS(
uint3 GroupId : SV_GroupID,
uint3 DispatchThreadId : SV_DispatchThreadID,
uint3 GroupThreadId : SV_GroupThreadID)
{
uint3 GridCoordinate = DispatchThreadId;
float3 AccumulatedLighting = 0;
float AccumulatedTransmittance = 1.0f;
float3 PreviousSliceTranslatedWorldPosition = ComputeCellTranslatedWorldPosition(uint3(GridCoordinate.xy, 0), float3(0.5f, 0.5f, 0.0f));
float AccumulatedDepth = 0.0;
for (int LayerIndex = 0; LayerIndex < VolumetricFog.ViewGridSizeInt.z; LayerIndex++)
{
uint3 LayerCoordinate = uint3(GridCoordinate.xy, LayerIndex);
float4 PreExposedScatteringAndExtinction = LightScattering[LayerCoordinate];
float3 LayerTranslatedWorldPosition = ComputeCellTranslatedWorldPosition(LayerCoordinate, .5f);
float StepLength = length(LayerTranslatedWorldPosition - PreviousSliceTranslatedWorldPosition);
PreviousSliceTranslatedWorldPosition = LayerTranslatedWorldPosition;
float Transmittance = exp(-PreExposedScatteringAndExtinction.w * StepLength);
AccumulatedDepth += StepLength;
// Fade in as a function of depth
float FadeInLerpValue = saturate(AccumulatedDepth * VolumetricFogNearFadeInDistanceInv);
// See "Physically Based and Unified Volumetric Rendering in Frostbite"
#define ENERGY_CONSERVING_INTEGRATION 1
#if ENERGY_CONSERVING_INTEGRATION
float3 ScatteringIntegratedOverSlice = FadeInLerpValue * (PreExposedScatteringAndExtinction.rgb - PreExposedScatteringAndExtinction.rgb * Transmittance) / max(PreExposedScatteringAndExtinction.w, .00001f);
AccumulatedLighting += ScatteringIntegratedOverSlice * AccumulatedTransmittance;
#else
AccumulatedLighting += FadeInLerpValue * PreExposedScatteringAndExtinction.rgb * AccumulatedTransmittance * StepLength;
#endif
AccumulatedTransmittance *= lerp(1.0f, Transmittance, FadeInLerpValue);
RWIntegratedLightScattering[LayerCoordinate] = float4(AccumulatedLighting, AccumulatedTransmittance);
}
}
#endif