diff --git a/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp b/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp index 238418a47..3fbf2dc47 100644 --- a/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp +++ b/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp @@ -426,7 +426,6 @@ bool DynamicDiffuseGlobalIlluminationPass::RenderInner(RenderContext& renderCont // Shift the volume origin based on scroll offsets for each axis once it overflows for (int32 axis = 0; axis < 3; axis++) { - // different volume scroll that preserves the scroll offset delta relative to the probe count const int32 probeCount = ddgiData.ProbeCounts.Raw[axis]; int32& scrollOffset = cascade.ProbeScrollOffsets.Raw[axis]; while (scrollOffset >= probeCount) diff --git a/Source/Shaders/GI/DDGI.hlsl b/Source/Shaders/GI/DDGI.hlsl index 16e79df68..1c052e8e0 100644 --- a/Source/Shaders/GI/DDGI.hlsl +++ b/Source/Shaders/GI/DDGI.hlsl @@ -148,13 +148,12 @@ float3 SampleDDGIIrradiance(DDGIData data, Texture2D probesData, T float fadeDistance = probesSpacing * 0.5f; float cascadeWeight = saturate(Min3(probesExtent - abs(worldPosition - probesOrigin)) / fadeDistance); if (cascadeWeight > dither) // Use dither to make transition smoother - { break; - } } if (cascadeIndex == data.CascadesCount) return data.FallbackIrradiance; - uint3 baseProbeCoords = clamp(uint3((worldPosition - probesOrigin + probesExtent) / probesSpacing), uint3(0, 0, 0), data.ProbesCounts - uint3(1, 1, 1)); + uint3 probeCoordsEnd = data.ProbesCounts - uint3(1, 1, 1); + uint3 baseProbeCoords = clamp(uint3((worldPosition - probesOrigin + probesExtent) / probesSpacing), uint3(0, 0, 0), probeCoordsEnd); // Bias the world-space position to reduce artifacts float3 viewDir = normalize(data.ViewPos - worldPosition); @@ -167,17 +166,40 @@ float3 SampleDDGIIrradiance(DDGIData data, Texture2D probesData, T // Loop over the closest probes to accumulate their contributions float4 irradiance = float4(0, 0, 0, 0); + const int3 SearchAxisMasks[3] = { int3(1, 0, 0), int3(0, 1, 0), int3(0, 0, 1) }; for (uint i = 0; i < 8; i++) { uint3 probeCoordsOffset = uint3(i, i >> 1, i >> 2) & 1; - uint3 probeCoords = clamp(baseProbeCoords + probeCoordsOffset, uint3(0, 0, 0), data.ProbesCounts - uint3(1, 1, 1)); + uint3 probeCoords = clamp(baseProbeCoords + probeCoordsOffset, uint3(0, 0, 0), probeCoordsEnd); uint probeIndex = GetDDGIScrollingProbeIndex(data, cascadeIndex, probeCoords); // Load probe position and state float4 probeData = LoadDDGIProbeData(data, probesData, cascadeIndex, probeIndex); uint probeState = DecodeDDGIProbeState(probeData); if (probeState == DDGI_PROBE_STATE_INACTIVE) - continue; + { + // Search nearby probes to find any nearby GI sample + for (int searchDistance = 1; searchDistance < 3 && probeState == DDGI_PROBE_STATE_INACTIVE; searchDistance++) + for (uint searchAxis = 0; searchAxis < 3; searchAxis++) + { + int searchAxisDir = probeCoordsOffset[searchAxis] ? 1 : -1; + int3 searchCoordsOffset = SearchAxisMasks[searchAxis] * searchAxisDir * searchDistance; + uint3 searchCoords = clamp((int3)probeCoords + searchCoordsOffset, int3(0, 0, 0), (int3)probeCoordsEnd); + uint searchIndex = GetDDGIScrollingProbeIndex(data, cascadeIndex, searchCoords); + float4 searchData = LoadDDGIProbeData(data, probesData, cascadeIndex, searchIndex); + uint searchState = DecodeDDGIProbeState(searchData); + if (searchState != DDGI_PROBE_STATE_INACTIVE) + { + // Use nearby probe as a fallback (visibility test might ignore it but with smooth gradient) + probeCoords = searchCoords; + probeIndex = searchIndex; + probeData = searchData; + probeState = searchState; + break; + } + } + if (probeState == DDGI_PROBE_STATE_INACTIVE) continue; + } float3 probeBasePosition = baseProbeWorldPosition + ((probeCoords - baseProbeCoords) * probesSpacing); float3 probePosition = probeBasePosition + probeData.xyz * probesSpacing; // Probe offset is [-1;1] within probes spacing @@ -193,15 +215,13 @@ float3 SampleDDGIIrradiance(DDGIData data, Texture2D probesData, T float2 octahedralCoords = GetOctahedralCoords(-biasedPosToProbe); float2 uv = GetDDGIProbeUV(data, cascadeIndex, probeIndex, octahedralCoords, DDGI_PROBE_RESOLUTION_DISTANCE); float2 probeDistance = probesDistance.SampleLevel(SamplerLinearClamp, uv, 0).rg * 2.0f; - float probeDistanceMean = probeDistance.x; // Visibility weight (Chebyshev) - if (biasedPosToProbeDist > probeDistanceMean) + if (biasedPosToProbeDist > probeDistance.x) { - float probeDistanceMean2 = probeDistance.y; - float probeDistanceVariance = abs(Square(probeDistanceMean) - probeDistanceMean2); - float chebyshevWeight = probeDistanceVariance / (probeDistanceVariance + Square(biasedPosToProbeDist - probeDistanceMean)); - weight *= max(chebyshevWeight * chebyshevWeight * chebyshevWeight, 0.05f); + float variance = abs(Square(probeDistance.x) - probeDistance.y); + float visibilityWeight = variance / (variance + Square(biasedPosToProbeDist - probeDistance.x)); + weight *= max(visibilityWeight * visibilityWeight * visibilityWeight, 0.05f); } // Avoid a weight of zero @@ -209,8 +229,7 @@ float3 SampleDDGIIrradiance(DDGIData data, Texture2D probesData, T // Adjust weight curve to inject a small portion of light const float minWeightThreshold = 0.2f; - if (weight < minWeightThreshold) - weight *= weight * weight * (1.0f / (minWeightThreshold * minWeightThreshold)); + if (weight < minWeightThreshold) weight *= Square(weight) / Square(minWeightThreshold); // Calculate trilinear weights based on the distance to each probe to smoothly transition between grid of 8 probes float3 trilinear = lerp(1.0f - biasAlpha, biasAlpha, (float3)probeCoordsOffset); @@ -246,7 +265,7 @@ float3 SampleDDGIIrradiance(DDGIData data, Texture2D probesData, T if (irradiance.a > 0.0f) { // Normalize irradiance - irradiance.rgb *= 1.0f / irradiance.a; + irradiance.rgb /= irradiance.a; #if DDGI_SRGB_BLENDING irradiance.rgb *= irradiance.rgb; #endif diff --git a/Source/Shaders/GI/DDGI.shader b/Source/Shaders/GI/DDGI.shader index e9ee51687..f6c00d331 100644 --- a/Source/Shaders/GI/DDGI.shader +++ b/Source/Shaders/GI/DDGI.shader @@ -19,6 +19,7 @@ // This must match C++ #define DDGI_TRACE_RAYS_PROBES_COUNT_LIMIT 4096 // Maximum amount of probes to update at once during rays tracing and blending #define DDGI_TRACE_RAYS_LIMIT 256 // Limit of rays per-probe (runtime value can be smaller) +#define DDGI_TRACE_NEGATIVE 0 // If true, rays that start inside geometry will use negative distance to indicate backface hit #define DDGI_PROBE_UPDATE_BORDERS_GROUP_SIZE 8 #define DDGI_PROBE_CLASSIFY_GROUP_SIZE 32 #define DDGI_PROBE_RELOCATE_ITERATIVE 1 // If true, probes relocation algorithm tries to move them in additive way, otherwise all nearby locations are checked to find the best position @@ -146,9 +147,9 @@ void CS_Classify(uint3 DispatchThreadId : SV_DispatchThreadID) float sdfDst = abs(sdf); const float ProbesDistanceLimits[4] = { 1.1f, 2.3f, 2.5f, 2.5f }; const float ProbesRelocateLimits[4] = { 0.4f, 0.5f, 0.6f, 0.7f }; - float voxelLimit = GlobalSDF.CascadeVoxelSize[CascadeIndex]; - float distanceLimit = length(probesSpacing) * ProbesDistanceLimits[CascadeIndex]; - float relocateLimit = length(probesSpacing) * ProbesRelocateLimits[CascadeIndex]; + float voxelLimit = GlobalSDF.CascadeVoxelSize[CascadeIndex] * 0.8f; + float distanceLimit = probesSpacing * ProbesDistanceLimits[CascadeIndex]; + float relocateLimit = probesSpacing * ProbesRelocateLimits[CascadeIndex]; if (sdfDst > distanceLimit + length(probeOffset)) // Probe is too far from geometry (or deep inside) { // Disable it @@ -317,12 +318,14 @@ void CS_TraceRays(uint3 DispatchThreadId : SV_DispatchThreadID) float4 radiance; if (hit.IsHit()) { - /*if (hit.HitSDF <= 0.0f && hit.HitTime <= GlobalSDF.CascadeVoxelSize[0]) +#if DDGI_TRACE_NEGATIVE + if (hit.HitSDF <= 0.0f && hit.HitTime <= GlobalSDF.CascadeVoxelSize[0]) { // Ray starts inside geometry (mark as negative distance and reduce it's influence during irradiance blending) radiance = float4(0, 0, 0, hit.HitTime * -0.25f); } - else*/ + else +#endif { // Sample Global Surface Atlas to get the lighting at the hit location float3 hitPosition = hit.GetHitPosition(trace); @@ -393,7 +396,7 @@ void CS_UpdateProbes(uint3 GroupThreadId : SV_GroupThreadID, uint3 GroupId : SV_ uint backfacesLimit = uint(probeRaysCount * 0.1f); #else float probesSpacing = DDGI.ProbesOriginAndSpacing[CascadeIndex].w; - float distanceLimit = length(probesSpacing) * 1.5f; + float distanceLimit = probesSpacing * 1.5f; #endif BRANCH @@ -435,6 +438,7 @@ void CS_UpdateProbes(uint3 GroupThreadId : SV_GroupThreadID, uint3 GroupId : SV_ #if DDGI_PROBE_UPDATE_MODE == 0 float4 rayRadiance = CachedProbesTraceRadiance[rayIndex]; +#if DDGI_TRACE_NEGATIVE if (rayRadiance.w < 0.0f) { // Count backface hits @@ -448,6 +452,7 @@ void CS_UpdateProbes(uint3 GroupThreadId : SV_GroupThreadID, uint3 GroupId : SV_ } continue; } +#endif // Add radiance (RGB) and weight (A) result += float4(rayRadiance.rgb * rayWeight, rayWeight);