Files
UnrealEngine/Engine/Shaders/Private/DistanceField/GlobalDistanceFieldUtils.ush
2025-05-18 13:04:45 +08:00

221 lines
9.7 KiB
HLSL

// Copyright Epic Games, Inc. All Rights Reserved.
// Controls how many voxels around a single sided object will be marked with full coverage
// This should be larger than or equal to r.LumenScene.GlobalSDF.NotCoveredMinStepScale to avoid artifacts (stepping too far)
#define ONE_SIDED_BAND_SIZE (4.0f * GLOBAL_DISTANCE_FIELD_COVERAGE_DOWNSAMPLE_FACTOR)
#ifndef GLOBALSDF_USE_COVERAGE_BASED_EXPAND
#define GLOBALSDF_USE_COVERAGE_BASED_EXPAND 1
#endif
#ifndef GLOBALSDF_SIMPLE_COVERAGE_BASED_EXPAND
#define GLOBALSDF_SIMPLE_COVERAGE_BASED_EXPAND 0
#endif
uint ComputeGlobalDistanceFieldClipmapIndex(float3 TranslatedWorldPosition)
{
uint FoundClipmapIndex = 0;
for (uint ClipmapIndex = 0; ClipmapIndex < NumGlobalSDFClipmaps; ClipmapIndex++)
{
float DistanceFromClipmap = ComputeDistanceFromBoxToPointInside(GlobalVolumeTranslatedCenterAndExtent[ClipmapIndex].xyz, GlobalVolumeTranslatedCenterAndExtent[ClipmapIndex].www, TranslatedWorldPosition);
if (DistanceFromClipmap > GlobalVolumeTranslatedCenterAndExtent[ClipmapIndex].w * GlobalVolumeTexelSize)
{
FoundClipmapIndex = ClipmapIndex;
break;
}
}
return FoundClipmapIndex;
}
struct FGlobalSDFTraceInput
{
float3 TranslatedWorldRayStart;
float3 WorldRayDirection;
float MinTraceDistance;
float MaxTraceDistance;
float StepFactor;
float MinStepFactor;
// The SDF surface is expanded to reduce leaking through thin surfaces, especially foliage meshes with bGenerateDistanceFieldAsIfTwoSided
// Expanding as RayTime increases errors on the side of over-occlusion, especially at grazing angles, which can be desirable for diffuse GI.
// Expanding as MaxDistance increases has less incorrect self-intersection which is desirable for reflections rays.
bool bExpandSurfaceUsingRayTimeInsteadOfMaxDistance;
float InitialMaxDistance;
// Bias relative to the clipmap's voxel size
float VoxelSizeRelativeBias;
// Ray length bias relative to the clipmap's voxel size
float VoxelSizeRelativeRayEndBias;
// For dithered semi-transparency
bool bDitheredTransparency;
float2 DitherScreenCoord;
};
FGlobalSDFTraceInput SetupGlobalSDFTraceInput(float3 InTranslatedWorldRayStart, float3 InWorldRayDirection, float InMinTraceDistance, float InMaxTraceDistance, float InStepFactor, float InMinStepFactor)
{
FGlobalSDFTraceInput TraceInput;
TraceInput.TranslatedWorldRayStart = InTranslatedWorldRayStart;
TraceInput.WorldRayDirection = InWorldRayDirection;
TraceInput.MinTraceDistance = InMinTraceDistance;
TraceInput.MaxTraceDistance = InMaxTraceDistance;
TraceInput.StepFactor = InStepFactor;
TraceInput.MinStepFactor = InMinStepFactor;
TraceInput.bExpandSurfaceUsingRayTimeInsteadOfMaxDistance = true;
TraceInput.InitialMaxDistance = 0;
TraceInput.VoxelSizeRelativeBias = 0.0f;
TraceInput.VoxelSizeRelativeRayEndBias = 0.0f;
TraceInput.bDitheredTransparency = false;
TraceInput.DitherScreenCoord = float2(0, 0);
return TraceInput;
}
struct FGlobalSDFTraceResult
{
float HitTime;
uint HitClipmapIndex;
uint TotalStepsTaken;
// Amount the surface was expanded at the hit
float ExpandSurfaceAmount;
};
bool GlobalSDFTraceResultIsHit(FGlobalSDFTraceResult TraceResult)
{
return TraceResult.HitTime >= 0.0f;
}
FGlobalSDFTraceResult RayTraceGlobalDistanceField(FGlobalSDFTraceInput TraceInput)
{
FGlobalSDFTraceResult TraceResult;
TraceResult.HitTime = -1.0f;
TraceResult.HitClipmapIndex = 0;
TraceResult.TotalStepsTaken = 0;
TraceResult.ExpandSurfaceAmount = 0;
float TraceNoise = InterleavedGradientNoise(TraceInput.DitherScreenCoord.xy, View.StateFrameIndexMod8);
uint MinClipmapIndex = ComputeGlobalDistanceFieldClipmapIndex(TraceInput.TranslatedWorldRayStart + TraceInput.MinTraceDistance * TraceInput.WorldRayDirection);
float MaxDistance = TraceInput.InitialMaxDistance;
float MinRayTime = TraceInput.MinTraceDistance;
LOOP
for (uint ClipmapIndex = MinClipmapIndex; ClipmapIndex < NumGlobalSDFClipmaps && TraceResult.HitTime < 0.0f; ++ClipmapIndex)
{
float ClipmapVoxelExtent = GlobalVolumeTranslatedCenterAndExtent[ClipmapIndex].w * GlobalVolumeTexelSize;
float MinStepSize = TraceInput.MinStepFactor * ClipmapVoxelExtent;
float ExpandSurfaceDistance = ClipmapVoxelExtent;
float ClipmapRayBias = ClipmapVoxelExtent * TraceInput.VoxelSizeRelativeBias;
float ClipmapRayLength = TraceInput.MaxTraceDistance - ClipmapVoxelExtent * TraceInput.VoxelSizeRelativeRayEndBias;
float3 GlobalVolumeTranslatedCenter = GlobalVolumeTranslatedCenterAndExtent[ClipmapIndex].xyz;
float GlobalVolumeExtent = GlobalVolumeTranslatedCenterAndExtent[ClipmapIndex].w - ClipmapVoxelExtent;
float3 TranslatedWorldRayEnd = TraceInput.TranslatedWorldRayStart + TraceInput.WorldRayDirection * ClipmapRayLength;
float2 IntersectionTimes = LineBoxIntersect(TraceInput.TranslatedWorldRayStart, TranslatedWorldRayEnd, GlobalVolumeTranslatedCenter - GlobalVolumeExtent.xxx, GlobalVolumeTranslatedCenter + GlobalVolumeExtent.xxx);
IntersectionTimes.xy *= ClipmapRayLength;
IntersectionTimes.x = max(IntersectionTimes.x, MinRayTime);
IntersectionTimes.x = max(IntersectionTimes.x, ClipmapRayBias);
if (IntersectionTimes.x < IntersectionTimes.y)
{
// Update the trace start for the next clipmap
MinRayTime = IntersectionTimes.y;
float SampleRayTime = IntersectionTimes.x;
const float ClipmapInfluenceRange = GLOBAL_DISTANCE_FIELD_INFLUENCE_RANGE_IN_VOXELS * 2.0f * GlobalVolumeTranslatedCenterAndExtent[ClipmapIndex].w * GlobalVolumeTexelSize;
uint StepIndex = 0;
const uint MaxSteps = 256;
LOOP
for (; StepIndex < MaxSteps; ++StepIndex)
{
float3 SampleTranslatedWorldPosition = TraceInput.TranslatedWorldRayStart + TraceInput.WorldRayDirection * SampleRayTime;
float3 ClipmapVolumeUV = ComputeGlobalUV(SampleTranslatedWorldPosition, ClipmapIndex);
float3 MipUV = ComputeGlobalMipUV(SampleTranslatedWorldPosition, ClipmapIndex);
float DistanceFieldMipValue = Texture3DSampleLevel(GlobalDistanceFieldMipTexture, GlobalDistanceFieldMipTextureSampler, MipUV, 0).x;
float DistanceField = DecodeGlobalDistanceFieldPageDistance(DistanceFieldMipValue, GlobalDistanceFieldMipFactor * ClipmapInfluenceRange);
float Coverage = 1;
FGlobalDistanceFieldPage Page = GetGlobalDistanceFieldPage(ClipmapVolumeUV, ClipmapIndex);
if (Page.bValid && DistanceFieldMipValue < GlobalDistanceFieldMipTransition)
{
float3 PageUV = ComputeGlobalDistanceFieldPageUV(ClipmapVolumeUV, Page);
#if GLOBALSDF_USE_COVERAGE_BASED_EXPAND
if (Page.bCoverage)
{
#if GLOBALSDF_SIMPLE_COVERAGE_BASED_EXPAND
Coverage = 0;
#else
float3 CoveragePageUV;
ComputeGlobalDistanceFieldPageUV(ClipmapVolumeUV, Page, PageUV, CoveragePageUV);
Coverage = Texture3DSampleLevel(GlobalDistanceFieldCoverageAtlasTexture, GlobalDistanceFieldCoverageAtlasTextureSampler, CoveragePageUV, 0).x;
#endif
}
#endif
float DistanceFieldValue = Texture3DSampleLevel(GlobalDistanceFieldPageAtlasTexture, GlobalDistanceFieldPageAtlasTextureSampler, PageUV, 0).x;
DistanceField = DecodeGlobalDistanceFieldPageDistance(DistanceFieldValue, ClipmapInfluenceRange);
}
MaxDistance = max(DistanceField, MaxDistance);
float ExpandSurfaceTime = TraceInput.bExpandSurfaceUsingRayTimeInsteadOfMaxDistance ? SampleRayTime - ClipmapRayBias : MaxDistance;
float ExpandSurfaceScale = lerp(NotCoveredExpandSurfaceScale, CoveredExpandSurfaceScale, Coverage);
const float ExpandSurfaceFalloff = 2.0f * ExpandSurfaceDistance;
const float ExpandSurfaceAmount = ExpandSurfaceDistance * saturate(ExpandSurfaceTime / ExpandSurfaceFalloff) * ExpandSurfaceScale;
float StepNoise = InterleavedGradientNoise(TraceInput.DitherScreenCoord.xy, View.StateFrameIndexMod8 * MaxSteps + StepIndex);
if (DistanceField < ExpandSurfaceAmount
&& (!TraceInput.bDitheredTransparency || (StepNoise * (1 - Coverage) <= DitheredTransparencyStepThreshold && TraceNoise * (1 - Coverage) <= DitheredTransparencyTraceThreshold)))
{
TraceResult.HitTime = max(SampleRayTime + DistanceField - ExpandSurfaceAmount, 0.0f);
TraceResult.HitClipmapIndex = ClipmapIndex;
TraceResult.ExpandSurfaceAmount = ExpandSurfaceAmount;
break;
}
float LocalMinStepSize = MinStepSize * lerp(NotCoveredMinStepScale, 1.0f, Coverage);
float StepDistance = max(DistanceField * TraceInput.StepFactor, LocalMinStepSize);
SampleRayTime += StepDistance;
// Terminate the trace if we went past the end of the ray or achieved enough occlusion
if (SampleRayTime > IntersectionTimes.y || TraceResult.HitTime >= 0.0f)
{
break;
}
}
TraceResult.TotalStepsTaken += StepIndex;
}
}
return TraceResult;
}
void ClipRayToStartOutsideGlobalSDF(inout float3 TranslatedRayOrigin, float3 RayDirection, inout float RayLength)
{
uint ClipmapIndex = NumGlobalSDFClipmaps - 1;
float3 TranslatedWorldRayEnd = TranslatedRayOrigin + RayDirection * RayLength;
float ClipmapVoxelExtent = GlobalVolumeTranslatedCenterAndExtent[ClipmapIndex].w * GlobalVolumeTexelSize;
float GlobalVolumeExtent = GlobalVolumeTranslatedCenterAndExtent[ClipmapIndex].w - ClipmapVoxelExtent;
float3 GlobalVolumeTranslatedCenter = GlobalVolumeTranslatedCenterAndExtent[ClipmapIndex].xyz;
float2 IntersectionTimes = LineBoxIntersect(TranslatedRayOrigin, TranslatedWorldRayEnd, GlobalVolumeTranslatedCenter - GlobalVolumeExtent.xxx, GlobalVolumeTranslatedCenter + GlobalVolumeExtent.xxx);
if (IntersectionTimes.x < IntersectionTimes.y)
{
// Pull the origin inside the box slightly to reduce the gap created by jittering ray step offset
// t + (t - 1) / (n * 2 - 1) where n is the number of distant screen trace steps
IntersectionTimes.y = max((32.f / 31.f) * IntersectionTimes.y - 1.f / 31.f, 0);
TranslatedRayOrigin += RayDirection * IntersectionTimes.y * RayLength;
RayLength = max(RayLength * (1.0f - IntersectionTimes.y), 0);
}
}