Files
UnrealEngineUWP/Engine/Shaders/Private/VolumetricFog.usf
aleksander netzel b504e801b1 Add support for injecting raytraced directional shadows into volumetric fog:
* Rendered in a separate pass into a separate volume texture that is then sampled in LightScatteringCS pass.
* Controlled by r.VolumetricFog.InjectRaytracedLights (and disabled by default)

#rb Sebastien.Hillaire

[CL 26776453 by aleksander netzel in ue5-main branch]
2023-08-02 12:39:14 -04:00

905 lines
32 KiB
Plaintext

/*=============================================================================
VolumetricFog.usf
=============================================================================*/
#include "Common.ush"
#include "Definitions.usf"
#define SUPPORT_CONTACT_SHADOWS 0
#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 "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
#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
#ifndef USE_CLOUD_TRANSMITTANCE
#define USE_CLOUD_TRANSMITTANCE 0
#endif
#if USE_CLOUD_TRANSMITTANCE
#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(GridCoordinate.z + CellOffset.z);
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 ComputeCellWorldPosition(uint3 GridCoordinate, float3 CellOffset, out float SceneDepth)
{
return ComputeCellTranslatedWorldPosition(GridCoordinate, CellOffset, SceneDepth) - LWCHackToFloat(PrimaryView.PreViewTranslation);
}
float3 ComputeCellWorldPosition(uint3 GridCoordinate, float3 CellOffset)
{
float Unused;
return ComputeCellWorldPosition(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
[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 = ComputeCellWorldPosition(GridCoordinate, VoxelOffset);
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;
float Extinction = max(GlobalDensity * GlobalExtinctionScale * MatchHeightFogFactor, 0);
float3 Scattering = Albedo * Extinction;
float Absorption = max(Extinction - Luminance(Scattering), 0.0f);
if (all((int3)GridCoordinate < VolumetricFog.ResourceGridSizeInt))
{
RWVBufferA[GridCoordinate] = float4(Scattering, Absorption);
#if USE_EMISSIVE
RWVBufferB[GridCoordinate] = float4(GlobalEmissive.rgb, 0);
#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
#if USE_LIGHT_FUNCTION
/** Matrix can be either WorldToShadow or WorldToLight depending on light type. */
float4x4 LocalLightFunctionMatrix;
float4 LightFunctionAtlasTileMinMaxUvBound;
Texture2D LightFunctionAtlasTexture;
SamplerState LightFunctionAtlasSampler;
float GetLocalLightFunction(float3 TranslatedWorldPosition, bool bSpotLight, bool bRectLight)
{
float2 LightFunctionUV;
if(bSpotLight)
{
float4 HomogeneousShadowPosition = mul(float4(TranslatedWorldPosition, 1), LocalLightFunctionMatrix);
HomogeneousShadowPosition.xyz /= HomogeneousShadowPosition.w;
LightFunctionUV = HomogeneousShadowPosition.xy;
LightFunctionUV.x = 1 - LightFunctionUV.x;
LightFunctionUV.y = 1 - LightFunctionUV.y;
}
else if (bRectLight)
{
float4 HomogeneousShadowPosition = mul(float4(TranslatedWorldPosition, 1), LocalLightFunctionMatrix).zyxw;
HomogeneousShadowPosition.xyz /= HomogeneousShadowPosition.w;
LightFunctionUV = HomogeneousShadowPosition.xy;
LightFunctionUV = LightFunctionUV * 0.5 + 0.5;
LightFunctionUV = frac(LightFunctionUV);
}
else // Point light
{
// Get vector from light to cell world pos.
float3 LightVec = normalize(TranslatedWorldPosition - DeferredLightUniforms.TranslatedWorldPosition);
// Transform and swizzle
LightVec = mul(float4(LightVec.xyz, 0), LocalLightFunctionMatrix).zyx;
// Map to spherical coordinate
LightFunctionUV = float2((atan2Fast(LightVec.y, LightVec.x) + PI) / (2 * PI), acosFast(LightVec.z) / PI);
}
return Texture2DSampleLevel(LightFunctionAtlasTexture, LightFunctionAtlasSampler, LightFunctionAtlasTileMinMaxUvBound.xy + LightFunctionUV * (LightFunctionAtlasTileMinMaxUvBound.zw - LightFunctionAtlasTileMinMaxUvBound.xy), 0).x;
}
#endif
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 = ComputeHistoryVolumeUV_DEPRECATED(ComputeCellTranslatedWorldPosition(GridCoordinate, .5f), UnjitteredPrevTranslatedWorldToClip);
float HistoryAlpha = HistoryWeight;
FLATTEN
if (any(HistoryUV < 0) || any(HistoryUV > 1))
{
HistoryAlpha = 0;
}
NumSuperSamples = HistoryAlpha < .001f ? HistoryMissSuperSampleCount : 1;
#endif
for (uint SampleIndex = 0; SampleIndex < NumSuperSamples; SampleIndex++)
{
float3 CellOffset = FrameJitterOffsets[SampleIndex].xyz;
//float CellOffset = .5f;
float SceneDepth;
float3 TranslatedWorldPosition = ComputeCellTranslatedWorldPosition(GridCoordinate, CellOffset, SceneDepth);
float3 WorldPosition = TranslatedWorldPosition - LWCHackToFloat(PrimaryView.PreViewTranslation);
float3 CameraVector = normalize(TranslatedWorldPosition - PrimaryView.TranslatedWorldCameraOrigin);
float CellRadius = length(TranslatedWorldPosition - ComputeCellTranslatedWorldPosition(GridCoordinate + uint3(1, 1, 1), CellOffset));
// 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);
Lighting = IntegrateLight(Rect);
}
else
{
FCapsuleLight Capsule = GetCapsule(ToLight, LightData);
Capsule.DistBiasSqr = Pow2(DistanceBias);
Lighting = 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
#if USE_LIGHT_FUNCTION
ShadowFactor *= GetLocalLightFunction(TranslatedWorldPosition, LightData.bSpotLight, LightData.bRectLight);
#endif
OutScattering.rgb += LightColor * (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, ForwardLightData.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 LightFunctionTexture;
SamplerState LightFunctionSampler;
float GetDirectionalLightFunction(float3 TranslatedWorldPosition) // directional light
{
float4 HomogeneousShadowPosition = mul(float4(TranslatedWorldPosition, 1), DirectionalLightFunctionTranslatedWorldToShadow);
float2 LightFunctionUV = HomogeneousShadowPosition.xy * .5f + .5f;
LightFunctionUV.y = 1 - LightFunctionUV.y;
return Texture2DSampleLevel(LightFunctionTexture, LightFunctionSampler, LightFunctionUV, 0).x;
}
float SkyLightVolumetricScatteringIntensity;
float4 SkySH[3];
float2 UseHeightFogColors; // x=override directional light using height fog inscattering color, y=override sky light using heigh fog inscattering cubemap
float UseDirectionalLightShadowing;
float StaticLightingScatteringIntensity;
Texture3D<float> RaytracedShadowsVolume;
[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 the froxel is behind front depth, do not evaluate any lighting.
bool CellWasOccluded = false;
if (UseConservativeDepthTexture > 0)
{
const float3 FarDepthOffset = float3(0.5f, 0.5f, -1.0f);
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;
}
float4 PrevCellNDCPosition = mul(float4(CellTranslatedWorldPosition, 1), UnjitteredPrevTranslatedWorldToClip);
PrevCellNDCPosition.xyz /= PrevCellNDCPosition.w;
float2 PrevUVs = PrevCellNDCPosition.xy * float2(0.5f, -0.5f) + 0.5f;
const float PrevSceneDepth = PrevConservativeDepthTexture.Load(uint3(PrevConservativeDepthTextureSize * PrevUVs, 0));
if (PrevSceneDepth > CellNDCPosition.z)
{
CellWasOccluded = true;
}
}
#if USE_TEMPORAL_REPROJECTION
float3 HistoryUV = ComputeHistoryVolumeUV_DEPRECATED(ComputeCellTranslatedWorldPosition(GridCoordinate, .5f), UnjitteredPrevTranslatedWorldToClip);
float HistoryAlpha = HistoryWeight;
// We need to test with View.VolumetricFogPrevUVMax for HistoryUV because that is what we clamp with in ComputeHistoryVolumeUV_DEPRECATED.
FLATTEN
if (any(HistoryUV < 0) || any(HistoryUV >= float3(View.VolumetricFogPrevUVMax, 1.0f)) || CellWasOccluded)
{
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 - LWCHackToFloat(PrimaryView.PreViewTranslation); // LWC_TODO
float CameraVectorLength = length(TranslatedWorldPosition - PrimaryView.TranslatedWorldCameraOrigin);
float3 CameraVector = (TranslatedWorldPosition - PrimaryView.TranslatedWorldCameraOrigin) / CameraVectorLength;
BRANCH
if (ForwardLightData.HasDirectionalLight)
{
float ShadowFactor = 1;
if (UseDirectionalLightShadowing > 0)
{
ShadowFactor *= ComputeDirectionalLightStaticShadowing(TranslatedWorldPosition);
bool bUnused = false;
ShadowFactor *= ComputeDirectionalLightDynamicShadowing(TranslatedWorldPosition, SceneDepth, bUnused);
#if VIRTUAL_SHADOW_MAP
FVirtualShadowMapSampleResult VirtualShadowMapSample = SampleVirtualShadowMapTranslatedWorld(ForwardLightData.DirectionalLightVSM, TranslatedWorldPosition);
ShadowFactor *= VirtualShadowMapSample.ShadowFactor;
#endif
#if USE_RAYTRACED_SHADOWS_VOLUME
ShadowFactor *= RaytracedShadowsVolume[GridCoordinate];
#endif
#if USE_CLOUD_TRANSMITTANCE
float OutOpticalDepth = 0.0f;
ShadowFactor *= lerp(1.0f, GetCloudVolumetricShadow(TranslatedWorldPosition, CloudShadowmapTranslatedWorldToLightClipMatrix, CloudShadowmapFarDepthKm, CloudShadowmapTexture, CloudShadowmapSampler, OutOpticalDepth), CloudShadowmapStrength);
#endif
}
ShadowFactor *= GetDirectionalLightFunction(TranslatedWorldPosition);
float3 DirectionalLightColor = ForwardLightData.DirectionalLightColor;
if (UseHeightFogColors.x > 0)
{
// Attempt to maintain intensity ratio between sky and sun
DirectionalLightColor = VolumetricFog.HeightFogDirectionalLightInscatteringColor * Luminance(ForwardLightData.DirectionalLightColor);
}
LightScattering += DirectionalLightColor
* (ShadowFactor
* ForwardLightData.DirectionalLightVolumetricScatteringIntensity
* PhaseFunction(PhaseG, dot(ForwardLightData.DirectionalLightDirection, -CameraVector)));
}
FTwoBandSHVector RotatedHGZonalHarmonic;
RotatedHGZonalHarmonic.V = float4(1.0f, CameraVector.y, CameraVector.z, CameraVector.x) * float4(1.0f, PhaseG, PhaseG, PhaseG);
float3 BrickTextureUVs = 0;
#if ALLOW_STATIC_LIGHTING
if (SkyLightVolumetricScatteringIntensity > 0 || StaticLightingScatteringIntensity > 0)
{
BrickTextureUVs = ComputeVolumetricLightmapBrickTextureUVs(WorldPosition);
}
#endif
#if LUMEN_GI
// Lumen Dynamic GI + shadowed Skylight
FTwoBandSHVectorRGB TranslucencyGISH = GetTranslucencyGIVolumeLighting(LWCPromote(WorldPosition), PrimaryView.WorldToClip, false); // LUMEN_LWC_TODO
LightScattering += max(DotSH(TranslucencyGISH, RotatedHGZonalHarmonic), 0);
#else
// Skylight
if (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
{
FTwoBandSHVectorRGB SkyIrradianceSH;
SkyIrradianceSH.R.V = SkySH[0];
SkyIrradianceSH.G.V = SkySH[1];
SkyIrradianceSH.B.V = SkySH[2];
SkyLighting = View.SkyLightColor.rgb * max(DotSH(SkyIrradianceSH, RotatedHGZonalHarmonic), 0) / PI;
}
float SkyVisibility = ComputeSkyVisibility(TranslatedWorldPosition, BrickTextureUVs);
LightScattering += (SkyVisibility * 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 FCulledLightsGridData CulledLightsGrid = GetCulledLightsGrid(GridIndex, 0);
float CellRadius = length(TranslatedWorldPosition - ComputeCellTranslatedWorldPosition(GridCoordinate + uint3(1, 1, 1), CellOffset));
// 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;
// Forward lighting of unshadowed point and spot lights
LOOP
for (uint LocalLightListIndex = 0; LocalLightListIndex < CulledLightsGrid.NumLocalLights; LocalLightListIndex++)
{
const FLocalLightData LocalLight = GetLocalLightData(CulledLightsGrid.DataStartIndex + LocalLightListIndex, 0);
const float VolumetricScatteringIntensity = UnpackVolumetricScatteringIntensity(LocalLight);
if (VolumetricScatteringIntensity > 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 );
Lighting = IntegrateLight(Rect);
}
else
{
FCapsuleLight Capsule = GetCapsule(ToLight, LightData);
Capsule.DistBiasSqr = DistanceBiasSqr;
Lighting = IntegrateLight(Capsule, LightData.bInverseSquared);
}
float CombinedAttenuation = Lighting * LightMask;
LightScattering += LightData.Color * (PhaseFunction(PhaseG, dot(L, -CameraVector)) * CombinedAttenuation * VolumetricScatteringIntensity);
// To debug culling
//LightScattering += LocalLight.LightColorAndFalloffExponent.xyz * .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;
float4 MaterialScatteringAndAbsorption = VBufferA[GridCoordinate];
float Extinction = MaterialScatteringAndAbsorption.w + Luminance(MaterialScatteringAndAbsorption.xyz);
float3 MaterialEmissive = 0.0f;
BRANCH
if (UseEmissive > 0)
{
MaterialEmissive = VBufferB[GridCoordinate].xyz;
}
float4 PreExposedScatteringAndExtinction = float4(View.PreExposure * (LightScattering * MaterialScatteringAndAbsorption.xyz + MaterialEmissive), Extinction);
#if USE_TEMPORAL_REPROJECTION
BRANCH
if (HistoryAlpha > 0)
{
float4 PreExposedHistoryScatteringAndExtinction = Texture3DSampleLevel(LightScatteringHistory, LightScatteringHistorySampler, HistoryUV, 0);
PreExposedHistoryScatteringAndExtinction *= LightScatteringHistoryPreExposureAndInv.y * View.PreExposure; // Bring previous frame pre exposed history luminance as current frame pre exposed luminance.
PreExposedScatteringAndExtinction = lerp(PreExposedScatteringAndExtinction, PreExposedHistoryScatteringAndExtinction, HistoryAlpha);
}
#endif
if (all(int3(GridCoordinate) < VolumetricFog.ResourceGridSizeInt))
{
PreExposedScatteringAndExtinction = MakePositiveFinite(PreExposedScatteringAndExtinction);
RWLightScattering[GridCoordinate] = PreExposedScatteringAndExtinction;
}
}
Texture3D<float4> LightScattering;
RWTexture3D<float4> RWIntegratedLightScattering;
float VolumetricFogNearFadeInDistanceInv;
[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);
}
}