diff --git a/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp b/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp index dadbc56bd..727de6ecf 100644 --- a/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp +++ b/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp @@ -49,6 +49,7 @@ PACK_STRUCT(struct Data0 Float2 Padding0; float ResetBlend; float TemporalTime; + Int4 ProbeScrollClears[4]; }); PACK_STRUCT(struct Data1 @@ -68,16 +69,14 @@ public: float ProbesSpacing = 0.0f; Int3 ProbeScrollOffsets; Int3 ProbeScrollDirections; - bool ProbeScrollClear[3]; + Int3 ProbeScrollClears; void Clear() { ProbesOrigin = Float3::Zero; ProbeScrollOffsets = Int3::Zero; ProbeScrollDirections = Int3::Zero; - ProbeScrollClear[0] = false; - ProbeScrollClear[1] = false; - ProbeScrollClear[2] = false; + ProbeScrollClears = Int3::Zero; } } Cascades[4]; @@ -315,7 +314,7 @@ bool DynamicDiffuseGlobalIlluminationPass::Render(RenderContext& renderContext, idealDistance *= 2; cascadesCount++; } - + // Calculate the probes count based on the amount of cascades and the distance to cover const float cascadesDistanceScales[] = { 1.0f, 3.0f, 6.0f, 10.0f }; // Scales each cascade further away from the camera origin const float distanceExtent = distance / cascadesDistanceScales[cascadesCount - 1]; @@ -427,7 +426,7 @@ bool DynamicDiffuseGlobalIlluminationPass::Render(RenderContext& renderContext, continue; auto& cascade = ddgiData.Cascades[cascadeIndex]; - // Reset the volume origin and scroll offsets for each axis + // Reset the volume origin and scroll offsets for each axis once it overflows for (int32 axis = 0; axis < 3; axis++) { if (cascade.ProbeScrollOffsets.Raw[axis] != 0 && (cascade.ProbeScrollOffsets.Raw[axis] % ddgiData.ProbeCounts.Raw[axis] == 0)) @@ -445,7 +444,7 @@ bool DynamicDiffuseGlobalIlluminationPass::Render(RenderContext& renderContext, const float value = translation.Raw[axis] / cascade.ProbesSpacing; const int32 scroll = value >= 0.0f ? (int32)Math::Floor(value) : (int32)Math::Ceil(value); cascade.ProbeScrollOffsets.Raw[axis] += scroll; - cascade.ProbeScrollClear[axis] = scroll != 0; + cascade.ProbeScrollClears.Raw[axis] = scroll; cascade.ProbeScrollDirections.Raw[axis] = translation.Raw[axis] >= 0.0f ? 1 : -1; } } @@ -459,10 +458,8 @@ bool DynamicDiffuseGlobalIlluminationPass::Render(RenderContext& renderContext, for (int32 cascadeIndex = 0; cascadeIndex < cascadesCount; cascadeIndex++) { auto& cascade = ddgiData.Cascades[cascadeIndex]; - int32 probeScrollClear = cascade.ProbeScrollClear[0] + cascade.ProbeScrollClear[1] * 2 + cascade.ProbeScrollClear[2] * 4; // Pack clear flags into bits ddgiData.Result.Constants.ProbesOriginAndSpacing[cascadeIndex] = Float4(cascade.ProbesOrigin, cascade.ProbesSpacing); - ddgiData.Result.Constants.ProbesScrollOffsets[cascadeIndex] = Int4(cascade.ProbeScrollOffsets, probeScrollClear); - ddgiData.Result.Constants.ProbeScrollDirections[cascadeIndex] = Int4(cascade.ProbeScrollDirections, 0); + ddgiData.Result.Constants.ProbesScrollOffsets[cascadeIndex] = Int4(cascade.ProbeScrollOffsets, 0); } ddgiData.Result.Constants.RayMaxDistance = 10000.0f; // TODO: adjust to match perf/quality ratio (make it based on Global SDF and Global Surface Atlas distance) ddgiData.Result.Constants.ViewDir = renderContext.View.Direction; @@ -487,6 +484,11 @@ bool DynamicDiffuseGlobalIlluminationPass::Render(RenderContext& renderContext, data.GlobalSDF = bindingDataSDF.Constants; data.GlobalSurfaceAtlas = bindingDataSurfaceAtlas.Constants; data.ResetBlend = clear ? 1.0f : 0.0f; + for (int32 cascadeIndex = 0; cascadeIndex < cascadesCount; cascadeIndex++) + { + auto& cascade = ddgiData.Cascades[cascadeIndex]; + data.ProbeScrollClears[cascadeIndex] = Int4(cascade.ProbeScrollClears, 0); + } if (renderContext.List->Settings.AntiAliasing.Mode == AntialiasingMode::TemporalAntialiasing) { // Use temporal offset in the dithering factor (gets cleaned out by TAA) diff --git a/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.h b/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.h index b8da81a17..48d16d643 100644 --- a/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.h +++ b/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.h @@ -17,7 +17,6 @@ public: { Float4 ProbesOriginAndSpacing[4]; Int4 ProbesScrollOffsets[4]; - Int4 ProbeScrollDirections[4]; uint32 ProbesCounts[3]; uint32 CascadesCount; float IrradianceGamma; diff --git a/Source/Shaders/GI/DDGI.hlsl b/Source/Shaders/GI/DDGI.hlsl index d89862e6e..6b8345ea4 100644 --- a/Source/Shaders/GI/DDGI.hlsl +++ b/Source/Shaders/GI/DDGI.hlsl @@ -13,8 +13,9 @@ #include "./Flax/Math.hlsl" #include "./Flax/Octahedral.hlsl" -#define DDGI_PROBE_STATE_ACTIVE 0 -#define DDGI_PROBE_STATE_INACTIVE 1 +#define DDGI_PROBE_STATE_INACTIVE 0.0f +#define DDGI_PROBE_STATE_ACTIVATED 0.2f +#define DDGI_PROBE_STATE_ACTIVE 1.0f #define DDGI_PROBE_RESOLUTION_IRRADIANCE 6 // Resolution (in texels) for probe irradiance data (excluding 1px padding on each side) #define DDGI_PROBE_RESOLUTION_DISTANCE 14 // Resolution (in texels) for probe distance data (excluding 1px padding on each side) #define DDGI_SRGB_BLENDING 1 // Enables blending in sRGB color space, otherwise irradiance blending is done in linear space @@ -23,8 +24,7 @@ struct DDGIData { float4 ProbesOriginAndSpacing[4]; - int4 ProbesScrollOffsets[4]; - int4 ProbeScrollDirections[4]; + int4 ProbesScrollOffsets[4]; // w unused uint3 ProbesCounts; uint CascadesCount; float IrradianceGamma; @@ -77,7 +77,7 @@ uint2 GetDDGIProbeTexelCoords(DDGIData data, uint cascadeIndex, uint probeIndex) uint GetDDGIScrollingProbeIndex(DDGIData data, uint cascadeIndex, uint3 probeCoords) { // Probes are scrolled on edges to stabilize GI when camera moves - return GetDDGIProbeIndex(data, (probeCoords + data.ProbesScrollOffsets[cascadeIndex].xyz + data.ProbesCounts) % data.ProbesCounts); + return GetDDGIProbeIndex(data, ((int3)probeCoords + data.ProbesScrollOffsets[cascadeIndex].xyz + (int3)data.ProbesCounts) % (int3)data.ProbesCounts); } float3 GetDDGIProbeWorldPosition(DDGIData data, uint cascadeIndex, uint3 probeCoords) @@ -146,7 +146,7 @@ float3 SampleDDGIIrradiance(DDGIData data, Texture2D probesState, uint probeIndex = GetDDGIScrollingProbeIndex(data, cascadeIndex, probeCoords); float4 probeState = probesState.Load(int3(GetDDGIProbeTexelCoords(data, cascadeIndex, probeIndex), 0)); probeStates[i] = probeState; - if (probeState.w == DDGI_PROBE_STATE_ACTIVE) + if (probeState.w != DDGI_PROBE_STATE_INACTIVE) activeCount++; } diff --git a/Source/Shaders/GI/DDGI.shader b/Source/Shaders/GI/DDGI.shader index 370efb509..ebb6bff70 100644 --- a/Source/Shaders/GI/DDGI.shader +++ b/Source/Shaders/GI/DDGI.shader @@ -30,6 +30,7 @@ GBufferData GBuffer; float2 Padding0; float ResetBlend; float TemporalTime; +int4 ProbeScrollClears[4]; META_CB_END META_CB_BEGIN(1, Data1) @@ -56,6 +57,12 @@ float3 GetProbeRayDirection(DDGIData data, uint rayIndex) return normalize(QuaternionRotate(data.RaysRotation, direction)); } +// Checks if the probe states are equal +bool GetProbeState(float a, float b) +{ + return abs(a - b) < 0.05f; +} + #ifdef _CS_Classify RWTexture2D RWProbesState : register(u0); @@ -83,7 +90,7 @@ void CS_Classify(uint3 DispatchThreadId : SV_DispatchThreadID) probeState.xyz *= probesSpacing; // Probe offset is [-1;1] within probes spacing float3 probeBasePosition = GetDDGIProbeWorldPosition(DDGI, CascadeIndex, probeCoords); float3 probePosition = probeBasePosition + probeState.xyz; - probeState.w = DDGI_PROBE_STATE_ACTIVE; + float4 probeStateOld = probeState; // Use Global SDF to quickly get distance and direction to the scene geometry float sdf; @@ -129,13 +136,32 @@ void CS_Classify(uint3 DispatchThreadId : SV_DispatchThreadID) // Reset relocation probeState.xyz = float3(0, 0, 0); } + + // Check if probe was scrolled + int3 probeScrollClears = ProbeScrollClears[CascadeIndex].xyz; + bool wasScrolled = false; + UNROLL + for (uint planeIndex = 0; planeIndex < 3; planeIndex++) + { + int probeCount = (int)DDGI.ProbesCounts[planeIndex]; + int newCord = (int)probeCoords[planeIndex] + probeScrollClears[planeIndex]; + if (newCord < 0 || newCord >= probeCount) + { + wasScrolled = true; + } + } + + // If probe was in different location or was inactive last frame then mark it as activated + bool wasInactive = probeStateOld.w == DDGI_PROBE_STATE_INACTIVE; + bool wasRelocated = distance(probeState.xyz, probeStateOld.xyz) > 1.0f; + probeState.w = wasInactive || wasScrolled || wasRelocated ? DDGI_PROBE_STATE_ACTIVATED : DDGI_PROBE_STATE_ACTIVE; } probeState.xyz /= probesSpacing; RWProbesState[probeDataCoords] = probeState; // Collect active probes - if (probeState.w == DDGI_PROBE_STATE_ACTIVE) + if (probeState.w != DDGI_PROBE_STATE_INACTIVE) { uint activeProbeIndex; RWActiveProbes.InterlockedAdd(0, 1, activeProbeIndex); // Counter at 0 @@ -313,29 +339,6 @@ void CS_UpdateProbes(uint3 GroupThreadId : SV_GroupThreadID, uint3 GroupId : SV_ return; probeCoords = GetDDGIProbeCoords(DDGI, probeIndex); uint2 outputCoords = GetDDGIProbeTexelCoords(DDGI, CascadeIndex, probeIndex) * (DDGI_PROBE_RESOLUTION + 2) + 1 + GroupThreadId.xy; - - // Clear probes that have been scrolled to a new positions - int3 probesScrollOffsets = DDGI.ProbesScrollOffsets[CascadeIndex].xyz; - int probeScrollClear = DDGI.ProbesScrollOffsets[CascadeIndex].w; - int3 probeScrollDirections = DDGI.ProbeScrollDirections[CascadeIndex].xyz; - bool scrolled = false; - UNROLL - for (uint planeIndex = 0; planeIndex < 3; planeIndex++) - { - if (probeScrollClear & (1 << planeIndex)) - { - int scrollOffset = probesScrollOffsets[planeIndex]; - int scrollDirection = probeScrollDirections[planeIndex]; - uint probeCount = DDGI.ProbesCounts[planeIndex]; - uint coord = (probeCount + (scrollDirection ? (scrollOffset - 1) : (scrollOffset % probeCount))) % probeCount; - if (probeCoords[planeIndex] == coord) - scrolled = true; - } - } - if (scrolled) - { - RWOutput[outputCoords] = float4(0, 0, 0, 0); - } // Calculate octahedral projection for probe (unwraps spherical projection into a square) float2 octahedralCoords = GetOctahedralCoords(GroupThreadId.xy, DDGI_PROBE_RESOLUTION); @@ -381,11 +384,16 @@ void CS_UpdateProbes(uint3 GroupThreadId : SV_GroupThreadID, uint3 GroupId : SV_ float epsilon = (float)DDGI.RaysCount * 1e-9f; result.rgb *= 1.0f / (2.0f * max(result.a, epsilon)); - // Blend current value with the previous probe data + // Load current probe value float3 previous = RWOutput[outputCoords].rgb; + bool wasActivated = GetProbeState(probeState, DDGI_PROBE_STATE_ACTIVATED); + if (wasActivated) + previous = float3(0, 0, 0); + + // Blend current value with the previous probe data float historyWeight = DDGI.ProbeHistoryWeight; //historyWeight = 0.0f; - if (ResetBlend || scrolled || dot(previous, previous) == 0) + if (ResetBlend || wasActivated || dot(previous, previous) == 0) historyWeight = 0.0f; #if DDGI_PROBE_UPDATE_MODE == 0 result *= DDGI.IndirectLightingIntensity;