Add improved fallback GI outside the DDGI range to use a special ambient probe for stability

This commit is contained in:
2026-05-07 16:21:17 +02:00
parent bdabcd5e43
commit e7fd901807
3 changed files with 74 additions and 10 deletions
Binary file not shown.
+27 -3
View File
@@ -29,6 +29,7 @@
#define DDGI_FALLBACK_COORDS_ENCODE(coord) ((float3)(coord + 1) / 128.0f)
#define DDGI_FALLBACK_COORDS_DECODE(data) (uint3)(data.xyz * 128.0f - 1)
#define DDGI_FALLBACK_COORDS_VALID(data) (length(data.xyz) > 0)
#define DDGI_FALLBACK_OUTER_DEDICATED_PROBE 1 // Enables using a special probe at (0, 0, 0) of the last cascade to be used for ambient GI on far pixels outside the DDGI range
//#define DDGI_DEBUG_CASCADE 0 // Forces a specific cascade to be only in use (for debugging)
// DDGI data for a constant buffer
@@ -166,6 +167,21 @@ float3 SampleDDGIIrradianceCascade(DDGIData data, Texture2D<snorm float4> probes
{
bool invalidCascade = cascadeIndex >= data.CascadesCount;
cascadeIndex = min(cascadeIndex, data.CascadesCount - 1);
#if DDGI_FALLBACK_OUTER_DEDICATED_PROBE
if (invalidCascade)
{
// Sample a special probe as a fallback for ambient GI outside the last cascade
float2 octahedralCoords = GetOctahedralCoords(worldNormal);
float2 uv = GetDDGIProbeUV(data, cascadeIndex, 0, octahedralCoords, DDGI_PROBE_RESOLUTION_IRRADIANCE);
float3 probeIrradiance = probesIrradiance.SampleLevel(SamplerLinearClamp, uv, 0).rgb;
#if DDGI_SRGB_BLENDING
probeIrradiance = pow(probeIrradiance, data.IrradianceGamma * 0.5f);
probeIrradiance *= probeIrradiance;
#endif
probeIrradiance *= 2.0f * PI;
return probeIrradiance;
}
#endif
uint3 probeCoordsEnd = data.ProbesCounts - uint3(1, 1, 1);
uint3 baseProbeCoords = clamp(uint3((worldPosition - probesOrigin + probesExtent) / probesSpacing), uint3(0, 0, 0), probeCoordsEnd);
@@ -258,8 +274,10 @@ float3 SampleDDGIIrradianceCascade(DDGIData data, Texture2D<snorm float4> probes
irradiance = float4(0, 1, 0, 1);
else if (cascadeIndex == 2)
irradiance = float4(0, 0, 1, 1);
else
else if (invalidCascade) // Area outside the last cascade that clamps to it
irradiance = float4(1, 0, 1, 1);
else
irradiance = float4(0, 1, 1, 1);
#endif
if (irradiance.a > 0.0f)
@@ -267,7 +285,11 @@ float3 SampleDDGIIrradianceCascade(DDGIData data, Texture2D<snorm float4> probes
// Normalize irradiance
//irradiance.rgb /= irradiance.a;
//irradiance.rgb /= lerp(1, irradiance.a, saturate(irradiance.a * irradiance.a + 0.9f));
#if DDGI_FALLBACK_OUTER_DEDICATED_PROBE
irradiance.rgb /= lerp(1, irradiance.a, saturate(irradiance.a * irradiance.a + 0.9f));
#else
irradiance.rgb /= invalidCascade ? irradiance.a : lerp(1, irradiance.a, saturate(irradiance.a * irradiance.a + 0.9f));
#endif
#if DDGI_SRGB_BLENDING
irradiance.rgb *= irradiance.rgb;
#endif
@@ -331,13 +353,15 @@ float3 SampleDDGIIrradiance(DDGIData data, Texture2D<snorm float4> probesData, T
// Blend with the next cascade (or fallback irradiance outside the volume)
#if DDGI_CASCADE_BLEND_SMOOTH && !defined(DDGI_DEBUG_CASCADE)
cascadeIndex++;
if (cascadeIndex < data.CascadesCount && cascadeWeight < 0.99f)
if (cascadeIndex <= data.CascadesCount && cascadeWeight < 0.99f)
{
uint cascadeIndexTmp = cascadeIndex;
cascadeIndex = min(cascadeIndex, data.CascadesCount - 1);
probesSpacing = data.ProbesOriginAndSpacing[cascadeIndex].w;
probesOrigin = data.ProbesScrollOffsets[cascadeIndex].xyz * probesSpacing + data.ProbesOriginAndSpacing[cascadeIndex].xyz;
probesExtent = (data.ProbesCounts - 1) * (probesSpacing * 0.5f);
biasedWorldPosition = worldPosition + GetDDGISurfaceBias(viewDir, probesSpacing, worldNormal, bias);
float3 resultNext = SampleDDGIIrradianceCascade(data, probesData, probesDistance, probesIrradiance, worldPosition, worldNormal, cascadeIndex, probesOrigin, probesExtent, probesSpacing, biasedWorldPosition);
float3 resultNext = SampleDDGIIrradianceCascade(data, probesData, probesDistance, probesIrradiance, worldPosition, worldNormal, cascadeIndexTmp, probesOrigin, probesExtent, probesSpacing, biasedWorldPosition);
result *= cascadeWeight;
result += resultNext * (1 - cascadeWeight);
}
+44 -4
View File
@@ -30,6 +30,7 @@
#define DDGI_PROBE_EMPTY_AREA_DENSITY 8 // Spacing (in probe grid) between fallback probes placed into empty areas to provide valid GI for nearby dynamic objects or transparency
#define DDGI_DEBUG_STATS 0 // Enables additional GPU-driven stats for probe/rays count
#define DDGI_DEBUG_INSTABILITY 0 // Enables additional probe irradiance instability debugging
#define DDGI_SKY_DISTANCE 1e27f // Sky is the limit
META_CB_BEGIN(0, Data0)
DDGIData DDGI;
@@ -191,22 +192,52 @@ void CS_Classify(uint3 DispatchThreadId : SV_DispatchThreadID)
float relocateLimit = probesSpacing * ProbesRelocateLimits[CascadeIndex];
#ifdef DDGI_PROBE_EMPTY_AREA_DENSITY
uint3 probeCoordsStable = GetDDGIProbeCoords(DDGI, probeIndex);
#endif
// Classify probe based on previous state and neighborhood
#if DDGI_FALLBACK_OUTER_DEDICATED_PROBE
if (CascadeIndex == DDGI.CascadesCount - 1 && probeIndex == 0)
{
// Special probe as a fallback for ambient GI outside the last cascade
probeOffset = float3(0, 0, 0);
if (probeStateOld == DDGI_PROBE_STATE_INACTIVE)
{
probeState = DDGI_PROBE_STATE_ACTIVATED;
probeAttention = 1.0f;
}
else
{
probeState = DDGI_PROBE_STATE_ACTIVE;
probeAttention = 0.5f;
}
}
else
#endif
#ifdef DDGI_PROBE_EMPTY_AREA_DENSITY
if (sdf > probesSpacing * DDGI.ProbesCounts.x * 0.3f
#if DDGI_PROBE_EMPTY_AREA_DENSITY > 1
&& (
// Low-density grid grid
(probeCoordsStable.x % DDGI_PROBE_EMPTY_AREA_DENSITY == 0 && probeCoordsStable.y % DDGI_PROBE_EMPTY_AREA_DENSITY == 0 && probeCoordsStable.z % DDGI_PROBE_EMPTY_AREA_DENSITY == 0)
// Edge probes at the last cascade (for good fallback irradiance outside the GI distance)
// Edge probes at the last cascade (for good fallback irradiance outside the GI distance) - not needed anymore as DDGI_FALLBACK_OUTER_DEDICATED_PROBE does a far better job
//|| (CascadeIndex + 1 == DDGI.CascadesCount && IsProbeAtBorder(probeCoords))
)
#endif
)
{
// Addd some fallback probes in empty areas to provide valid GI for nearby dynamic objects or transparency
// Add some fallback probes in empty areas to provide valid GI for nearby dynamic objects or transparency
probeOffset = float3(0, 0, 0);
probeState = wasScrolled || probeStateOld == DDGI_PROBE_STATE_INACTIVE ? DDGI_PROBE_STATE_ACTIVATED : DDGI_PROBE_STATE_ACTIVE;
if (wasScrolled || probeStateOld == DDGI_PROBE_STATE_INACTIVE)
{
probeState = DDGI_PROBE_STATE_ACTIVATED;
probeAttention = 1.0f;
}
else
{
probeState = DDGI_PROBE_STATE_ACTIVE;
probeAttention = DDGI_PROBE_ATTENTION_MIN;
}
}
else
#endif
if (sdfDst > distanceLimit + length(probeOffset))
@@ -479,6 +510,15 @@ void CS_TraceRays(uint3 DispatchThreadId : SV_DispatchThreadID)
float3 probeRayDirection = GetProbeRayDirection(DDGI, rayIndex, probeRaysCount, probeIndex, probeCoords);
// TODO: implement ray-guiding based on the probe irradiance (prioritize directions with high luminance)
#if DDGI_FALLBACK_OUTER_DEDICATED_PROBE
if (CascadeIndex == DDGI.CascadesCount - 1 && probeIndex == 0)
{
// Special probe as a fallback for ambient GI outside the last cascade
RWProbesTrace[uint2(rayIndex, DispatchThreadId.x)] = float4(Skybox.SampleLevel(SamplerLinearClamp, probeRayDirection, 0).rgb * SkyboxIntensity, DDGI_SKY_DISTANCE);
return;
}
#endif
// Trace ray with Global SDF
GlobalSDFTrace trace;
trace.Init(probePosition, probeRayDirection, 0.0f, DDGI.RayMaxDistance);
@@ -513,7 +553,7 @@ void CS_TraceRays(uint3 DispatchThreadId : SV_DispatchThreadID)
{
// Ray hits sky
radiance.rgb = Skybox.SampleLevel(SamplerLinearClamp, probeRayDirection, 0).rgb * SkyboxIntensity;
radiance.a = 1e27f; // Sky is the limit
radiance.a = DDGI_SKY_DISTANCE;
}
// Write into probes trace results