diff --git a/Content/Shaders/GI/DDGI.flax b/Content/Shaders/GI/DDGI.flax index 1622d78d9..dea6b002c 100644 --- a/Content/Shaders/GI/DDGI.flax +++ b/Content/Shaders/GI/DDGI.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ba8bc039e5a098638c5a2cc2d2c1d412fda6b10b5747c0075e8755897f65688f -size 21756 +oid sha256:5acf61b87b00dbf2e7f0c3485783f8d6cb9b5bf33bbd90ad0de5be989f88102f +size 23472 diff --git a/Source/Shaders/GI/DDGI.hlsl b/Source/Shaders/GI/DDGI.hlsl index 78a11bb2c..8ae91b3a6 100644 --- a/Source/Shaders/GI/DDGI.hlsl +++ b/Source/Shaders/GI/DDGI.hlsl @@ -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, ((int3)probeCoords + data.ProbesScrollOffsets[cascadeIndex].xyz + (int3)data.ProbesCounts) % (int3)data.ProbesCounts); + return GetDDGIProbeIndex(data, (probeCoords + data.ProbesCounts + data.ProbesScrollOffsets[cascadeIndex].xyz) % data.ProbesCounts); } float3 GetDDGIProbeWorldPosition(DDGIData data, uint cascadeIndex, uint3 probeCoords) diff --git a/Source/Shaders/GI/DDGI.shader b/Source/Shaders/GI/DDGI.shader index 2090ddfb7..edffa9f4a 100644 --- a/Source/Shaders/GI/DDGI.shader +++ b/Source/Shaders/GI/DDGI.shader @@ -65,12 +65,19 @@ bool GetProbeState(float a, float b) #ifdef _CS_Classify +#define DDGI_PROBE_RELOCATE_ITERATIVE 0 // If true, probes relocation algorithm tries to move them in additive way, otherwise all nearby locations are checked to find the best position + RWTexture2D RWProbesState : register(u0); RWByteAddressBuffer RWActiveProbes : register(u1); Texture3D GlobalSDFTex : register(t0); Texture3D GlobalSDFMip : register(t1); +float3 Remap(float3 value, float3 fromMin, float3 fromMax, float3 toMin, float3 toMax) +{ + return (value - fromMin) / (fromMax - fromMin) * (toMax - toMin) + toMin; +} + // Compute shader for updating probes state between active and inactive. META_CS(true, FEATURE_LEVEL_SM5) [numthreads(DDGI_PROBE_CLASSIFY_GROUP_SIZE, 1, 1)] @@ -89,12 +96,15 @@ void CS_Classify(uint3 DispatchThreadId : SV_DispatchThreadID) float4 probeState = RWProbesState[probeDataCoords]; probeState.xyz *= probesSpacing; // Probe offset is [-1;1] within probes spacing float3 probeBasePosition = GetDDGIProbeWorldPosition(DDGI, CascadeIndex, probeCoords); - float3 probePosition = probeBasePosition + probeState.xyz; + float3 probePosition = probeBasePosition; +#if DDGI_PROBE_RELOCATE_ITERATIVE + probePosition += probeState.xyz; +#endif float4 probeStateOld = probeState; // Use Global SDF to quickly get distance and direction to the scene geometry float sdf; - float3 sdfNormal = normalize(SampleGlobalSDFGradient(GlobalSDF, GlobalSDFTex, GlobalSDFMip, probePosition.xyz, sdf)); + float3 sdfNormal = normalize(SampleGlobalSDFGradient(GlobalSDF, GlobalSDFTex, GlobalSDFMip, probePosition, sdf)); float sdfDst = abs(sdf); float threshold = GlobalSDF.CascadeVoxelSize[CascadeIndex]; float distanceLimit = length(probesSpacing) * 1.5f; @@ -106,6 +116,7 @@ void CS_Classify(uint3 DispatchThreadId : SV_DispatchThreadID) } else { +#if DDGI_PROBE_RELOCATE_ITERATIVE if (sdf < threshold) // Probe is inside geometry { if (sdfDst < relocateLimit) @@ -123,7 +134,7 @@ void CS_Classify(uint3 DispatchThreadId : SV_DispatchThreadID) probeState.xyz = float3(0, 0, 0); } } - else if (sdf > threshold * 4.0f) // Probe is far enough any geometry + else if (sdf > threshold * 4.0f) // Probe is far enough from any geometry { // Reset relocation probeState.xyz = float3(0, 0, 0); @@ -136,6 +147,33 @@ void CS_Classify(uint3 DispatchThreadId : SV_DispatchThreadID) // Reset relocation probeState.xyz = float3(0, 0, 0); } +#else + // Sample Global SDF around the probe location + uint sdfCascade = GetGlobalSDFCascade(GlobalSDF, probePosition); + float4 CachedProbeOffsets[64]; + // TODO: test performance diff when using shared memory and larger thread group (is it worth it?) + for(uint x = 0; x < 4; x++) + for(uint y = 0; y < 4; y++) + for(uint z = 0; z < 4; z++) + { + float3 offset = Remap(float3(x, y, z), 0, 3, -0.5f, 0.5f) * relocateLimit; + float offsetSdf = SampleGlobalSDFCascade(GlobalSDF, GlobalSDFTex, probeBasePosition + offset, sdfCascade); + CachedProbeOffsets[x * 16 + y * 4 + z] = float4(offset, offsetSdf); + } + + // Select the best probe location around the base position + float4 bestOffset = CachedProbeOffsets[0]; + for (uint i = 1; i < 64; i++) + { + if (CachedProbeOffsets[i].w > bestOffset.w) + bestOffset = CachedProbeOffsets[i]; + } + + // Relocate the probe to the best found location (or zero if nothing good found) + if (bestOffset.w <= threshold) + bestOffset.xyz = float3(0, 0, 0); + probeState.xyz = bestOffset.xyz; +#endif // Check if probe was scrolled int3 probeScrollClears = ProbeScrollClears[CascadeIndex].xyz; @@ -153,10 +191,11 @@ void CS_Classify(uint3 DispatchThreadId : SV_DispatchThreadID) // 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; + bool wasRelocated = distance(probeState.xyz, probeStateOld.xyz) > 2.0f; probeState.w = wasInactive || wasScrolled || wasRelocated ? DDGI_PROBE_STATE_ACTIVATED : DDGI_PROBE_STATE_ACTIVE; } + // Save probe state probeState.xyz /= probesSpacing; RWProbesState[probeDataCoords] = probeState; diff --git a/Source/Shaders/GlobalSignDistanceField.hlsl b/Source/Shaders/GlobalSignDistanceField.hlsl index 6022ea20f..34c64577d 100644 --- a/Source/Shaders/GlobalSignDistanceField.hlsl +++ b/Source/Shaders/GlobalSignDistanceField.hlsl @@ -68,6 +68,33 @@ void GetGlobalSDFCascadeUV(const GlobalSDFData data, uint cascade, float3 worldP textureUV = float3(((float)cascade + cascadeUV.x) / (float)data.CascadesCount, cascadeUV.y, cascadeUV.z); // cascades are placed next to each other on X axis } +// Gets the Global SDF cascade index for the given world location. +uint GetGlobalSDFCascade(const GlobalSDFData data, float3 worldPosition) +{ + for (uint cascade = 0; cascade < data.CascadesCount; cascade++) + { + float cascadeMaxDistance; + float3 cascadeUV, textureUV; + GetGlobalSDFCascadeUV(data, cascade, worldPosition, cascadeMaxDistance, cascadeUV, textureUV); + if (all(cascadeUV > 0) && all(cascadeUV < 1)) + return cascade; + } + return 0; +} + +// Samples the Global SDF cascade and returns the distance to the closest surface (in world units) at the given world location. +float SampleGlobalSDFCascade(const GlobalSDFData data, Texture3D tex, float3 worldPosition, uint cascade) +{ + float distance = GLOBAL_SDF_WORLD_SIZE; + float cascadeMaxDistance; + float3 cascadeUV, textureUV; + GetGlobalSDFCascadeUV(data, cascade, worldPosition, cascadeMaxDistance, cascadeUV, textureUV); + float cascadeDistance = tex.SampleLevel(SamplerLinearClamp, textureUV, 0); + if (cascadeDistance < 1.0f && !any(cascadeUV < 0) && !any(cascadeUV > 1)) + distance = cascadeDistance * cascadeMaxDistance; + return distance; +} + // Samples the Global SDF and returns the distance to the closest surface (in world units) at the given world location. float SampleGlobalSDF(const GlobalSDFData data, Texture3D tex, float3 worldPosition) {