You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
* 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]
905 lines
32 KiB
Plaintext
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);
|
|
}
|
|
}
|