From cd95ccaaa24390be0a94066a52a7dab7c5404939 Mon Sep 17 00:00:00 2001 From: nickstefan Date: Tue, 23 Dec 2025 23:50:46 -0700 Subject: [PATCH 1/3] fix(shadows-temporal-jitter): use stable hashing --- package/Shaders/Utility.hlsl | 40 ++++++++++++++++++++++++++++++++---- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/package/Shaders/Utility.hlsl b/package/Shaders/Utility.hlsl index 075dce0f38..3219b22ba0 100644 --- a/package/Shaders/Utility.hlsl +++ b/package/Shaders/Utility.hlsl @@ -291,6 +291,27 @@ struct PS_OUTPUT float4 Color : SV_Target0; }; +// FrameCount-independent shadow filtering functions +// Eliminates temporal jitter by using position-based stable hashing +uint StableShadowHash(uint x, uint y, uint z, uint w) +{ + // Jenkins-like mix function for stable shadow rotation + uint h = x * 0x27d4eb2dU; + h ^= y + 0x7feb352dU + (h << 6) + (h >> 2); + h ^= z + 0x9e3779b9U + (h << 6) + (h >> 2); + h ^= w + 0x85ebca6bU + (h << 6) + (h >> 2); + return h; +} + +float StableShadowAngle(float3 worldPos, uint lightIndex, uint cascadeIndex) +{ + // Quantize world position to ~6cm precision (conservative to eliminate jitter) + int3 quantizedPos = int3(floor(worldPos * 16.0)); + uint hash = StableShadowHash(asuint(quantizedPos.x), asuint(quantizedPos.y), lightIndex, cascadeIndex); + // Map hash to [0, TAU) for rotation angle + return Math::TAU * ((hash & 0x00FFFFFFu) / 16777216.0f); +} + #ifdef PSHADER SamplerState SampBaseSampler : register(s0); @@ -375,7 +396,8 @@ float GetPoissonDiskFilteredShadowVisibility(uint3 seed, Texture2DArray float alphaTestOffset = -AlphaTestRef.y; float sampleRadius = ShadowSampleParam.z * 2048.0; float seedNormalized = seed.x * (1.0 / 4294967295.0); // Use only x component for efficiency - uint frameOffset = SharedData::FrameCount * sampleCount; + uint stableOffset = (uint)(positionMS.x * 1000.0) + (uint)(positionMS.y * 100.0) + (uint)(positionMS.z * 10.0); + uint frameOffset = stableOffset * sampleCount; // Use stable to minimize temporal jitter // Pre-compute constants const float3 positionOffsetUp = float3(0, 0, 1); @@ -400,6 +422,11 @@ float GetPoissonDiskFilteredShadowVisibility(uint3 seed, Texture2DArray float positionLength = length(positionLS); float compareValue = saturate(positionLength * rcpShadowLightParam) + alphaTestOffset; + // Handle driver precision differences + #ifdef CS_PLATFORM_VULKAN + compareValue += 0.0015; + #endif + // Optimized hemisphere calculation bool lowerHalf = positionLS.z < 0; float3 normalizedPos = positionLS * rcp(positionLength); // Avoid second normalize @@ -433,6 +460,11 @@ float GetPoissonDiskFilteredShadowVisibility(uint3 seed, Texture2DArray float positionLength = length(positionLS); float compareValue = saturate(positionLength * rcpShadowLightParam) + alphaTestOffset; + // Handle driver precision differences + #ifdef CS_PLATFORM_VULKAN + compareValue += 0.0015; + #endif + bool lowerHalf = positionLS.z < 0; float3 normalizedPos = positionLS * rcp(positionLength); @@ -616,10 +648,10 @@ PS_OUTPUT main(PS_INPUT input) float fadeFactor = input.Alpha.x; # endif - float noise = Random::InterleavedGradientNoise(input.PositionCS.xy, SharedData::FrameCount); - + float noise = Random::InterleavedGradientNoise(input.PositionCS.xy, 0); + precise float stableAngle = StableShadowAngle(positionMS.xyz, 0, 0); float2 rotation; - sincos(Math::TAU * noise, rotation.y, rotation.x); + sincos(stableAngle, rotation.y, rotation.x); // Use stable to minimize temporal jitter float2x2 rotationMatrix = float2x2(rotation.x, rotation.y, -rotation.y, rotation.x); noise = noise * 2.0 - 1.0; From 268eb40183380ef2363edb1066f8a7d76a39a959 Mon Sep 17 00:00:00 2001 From: nickstefan Date: Wed, 24 Dec 2025 01:05:08 -0700 Subject: [PATCH 2/3] fix(shadows-temporal-jitter): remove vulk bias --- package/Shaders/Utility.hlsl | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/package/Shaders/Utility.hlsl b/package/Shaders/Utility.hlsl index 3219b22ba0..97cc99951c 100644 --- a/package/Shaders/Utility.hlsl +++ b/package/Shaders/Utility.hlsl @@ -422,11 +422,6 @@ float GetPoissonDiskFilteredShadowVisibility(uint3 seed, Texture2DArray float positionLength = length(positionLS); float compareValue = saturate(positionLength * rcpShadowLightParam) + alphaTestOffset; - // Handle driver precision differences - #ifdef CS_PLATFORM_VULKAN - compareValue += 0.0015; - #endif - // Optimized hemisphere calculation bool lowerHalf = positionLS.z < 0; float3 normalizedPos = positionLS * rcp(positionLength); // Avoid second normalize @@ -460,11 +455,6 @@ float GetPoissonDiskFilteredShadowVisibility(uint3 seed, Texture2DArray float positionLength = length(positionLS); float compareValue = saturate(positionLength * rcpShadowLightParam) + alphaTestOffset; - // Handle driver precision differences - #ifdef CS_PLATFORM_VULKAN - compareValue += 0.0015; - #endif - bool lowerHalf = positionLS.z < 0; float3 normalizedPos = positionLS * rcp(positionLength); From b513dd0c5bbb24428c38bcd3796e04ca8a6e0c3c Mon Sep 17 00:00:00 2001 From: nickstefan Date: Wed, 24 Dec 2025 02:05:08 -0700 Subject: [PATCH 3/3] fix(shadows-temporal-jitter): use cascadeIndex --- package/Shaders/Utility.hlsl | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/package/Shaders/Utility.hlsl b/package/Shaders/Utility.hlsl index 97cc99951c..6bffc31aaa 100644 --- a/package/Shaders/Utility.hlsl +++ b/package/Shaders/Utility.hlsl @@ -638,8 +638,17 @@ PS_OUTPUT main(PS_INPUT input) float fadeFactor = input.Alpha.x; # endif + uint cascadeIndex = 0; +# if defined(RENDER_SHADOWMASK) + if (2.5 < EndSplitDistances.w && EndSplitDistances.y < shadowMapDepth) { + cascadeIndex = 2; + } else if (EndSplitDistances.x < shadowMapDepth) { + cascadeIndex = 1; + } +# endif + float noise = Random::InterleavedGradientNoise(input.PositionCS.xy, 0); - precise float stableAngle = StableShadowAngle(positionMS.xyz, 0, 0); + precise float stableAngle = StableShadowAngle(positionMS.xyz, 0, cascadeIndex); float2 rotation; sincos(stableAngle, rotation.y, rotation.x); // Use stable to minimize temporal jitter float2x2 rotationMatrix = float2x2(rotation.x, rotation.y, -rotation.y, rotation.x); @@ -652,15 +661,12 @@ PS_OUTPUT main(PS_INPUT input) if (EndSplitDistances.z >= shadowMapDepth) { float4x3 lightProjectionMatrix = ShadowMapProj[eyeIndex][0]; float shadowMapThreshold = AlphaTestRef.y; - float cascadeIndex = 0; if (2.5 < EndSplitDistances.w && EndSplitDistances.y < shadowMapDepth) { lightProjectionMatrix = ShadowMapProj[eyeIndex][2]; shadowMapThreshold = AlphaTestRef.z; - cascadeIndex = 2; } else if (EndSplitDistances.x < shadowMapDepth) { lightProjectionMatrix = ShadowMapProj[eyeIndex][1]; shadowMapThreshold = AlphaTestRef.z; - cascadeIndex = 1; } float shadowVisibility = 0;