From 1964e2380a3d7a6886c4fa0f8f49ca812a3fca55 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 24 May 2026 11:57:36 +0000 Subject: [PATCH 01/20] feat(skylighting): add per-probe shadow visibility from cascade map Augments the skylighting probe update with a second output: a 32-frame bitmask of sun shadow visibility per probe, accumulated from cascade shadow map samples with per-frame fixed-pattern offsets. The bitmask average becomes a smoothed shadow visibility value usable by other features without rerunning shadow taps. Adds two new 3D textures (texShadowBitmask R32_UINT, texShadowVisibility R16_FLOAT) and a new SRV slot t53 for sampling visibility from the pixel shader. Adds a Skylighting::CaptureShadowCascadeSRV() that grabs the active cascade SRV during the shadowmask render pass so the next probe update has it. Refactors the Skylighting::Sample API to take screen position for per-pixel jitter, and renames GetVertexSkylightingDiffuse to GetSkylightingDiffuse, which now takes a pre-sampled SH so callers can reuse it for both sampling and evaluation. Adds a new SampleShadowVisibility(positionMS, normalWS, screenPos) entry point. Updates all in-tree callers (DeferredCompositeCS, Lighting, RunGrass). Bumps Skylighting feature version 1-3-0 -> 1-4-0. --- .../Shaders/Skylighting/Skylighting.hlsli | 77 +++++++++++-- .../Shaders/Skylighting/UpdateProbesCS.hlsl | 103 +++++++++++++++++- package/Shaders/DeferredCompositeCS.hlsl | 4 +- package/Shaders/Lighting.hlsl | 4 +- package/Shaders/RunGrass.hlsl | 6 +- src/Features/Skylighting.cpp | 48 +++++++- src/Features/Skylighting.h | 5 + src/State.cpp | 4 + 8 files changed, 229 insertions(+), 22 deletions(-) diff --git a/features/Skylighting/Shaders/Skylighting/Skylighting.hlsli b/features/Skylighting/Shaders/Skylighting/Skylighting.hlsli index fbbe1fe448..b1e6869cd0 100644 --- a/features/Skylighting/Shaders/Skylighting/Skylighting.hlsli +++ b/features/Skylighting/Shaders/Skylighting/Skylighting.hlsli @@ -2,6 +2,7 @@ #define __SKYLIGHTING_DEPENDENCY_HLSL__ #include "Common/Math.hlsli" +#include "Common/Random.hlsli" #include "Common/Shading.hlsli" #include "Common/SharedData.hlsli" #include "Common/Spherical Harmonics/SphericalHarmonics.hlsli" @@ -14,6 +15,10 @@ namespace Skylighting Texture3D SkylightingProbeArray : register(t50); #endif +#if defined(PSHADER) + Texture3D ShadowVisibilityProbeArray : register(t53); +#endif + const static sh2 UNIT_SH = float4(sqrt(4.0 * Math::PI), 0, 0, 0); const static uint3 ARRAY_DIM = uint3(256, 256, 128); @@ -75,7 +80,7 @@ namespace Skylighting #endif #if defined(PSHADER) || defined(SKYLIGHTING_PROBE_REGISTER) - sh2 Sample(float3 positionMS, float3 normalWS) + sh2 Sample(float3 positionMS, float3 normalWS, float2 screenPosition) { sh2 scaledUnitSH = UNIT_SH / 1e-10; @@ -84,6 +89,11 @@ namespace Skylighting positionMS.xyz += normalWS * CELL_SIZE * 0.5; // Receiver normal bias + if (SharedData::FrameCount) { + float3 offset = float3(Random::pcg3d(uint3(screenPosition.xy, SharedData::FrameCount))) / 4294967295.0 * 2.0 - 1.0; + positionMS.xyz += offset * CELL_SIZE * 0.5; + } + float3 positionMSAdjusted = positionMS - SharedData::skylightingSettings.PosOffset.xyz; float3 uvw = positionMSAdjusted / ARRAY_SIZE + .5; @@ -125,26 +135,20 @@ namespace Skylighting return SphericalHarmonics::Scale(sum, rcp(wsum + EPSILON_WEIGHT_SUM)); } - // Compute skylighting diffuse for a receiver biased to face upward (grass/foliage). - // The result is pre-divided by vertexAO so that a subsequent multiply by vertexAO - // yields min(skylightingDiffuse, vertexAO). Pass vertexAO = 1 to skip this compensation. - float GetVertexSkylightingDiffuse(float3 positionMS, float3 normalWS, float vertexAO) + float GetSkylightingDiffuse(sh2 skylightingSH, float3 positionMS, float3 evalNormal, float vertexAO = 1.0) { if (SharedData::InInterior) return 1.0; + float3 biasedNormal = normalize(float3(evalNormal.xy, max(0.0, evalNormal.z))); float fadeOutFactor = GetFadeOutFactor(positionMS); - - float3 biasedNormal = normalWS; - biasedNormal.z = max(0.0, biasedNormal.z); - biasedNormal = normalize(biasedNormal); - - sh2 skylightingSH = Sample(positionMS, normalWS); float skylightingDiffuse = EvaluateDiffuse(skylightingSH, biasedNormal, fadeOutFactor); - return saturate(skylightingDiffuse / max(vertexAO, 1e-5)); + return saturate(skylightingDiffuse / max(vertexAO, EPSILON_DIVISION)); } + + sh2 SampleNoBias(float3 positionMS) { sh2 scaledUnitSH = UNIT_SH / 1e-10; @@ -190,6 +194,55 @@ namespace Skylighting return SphericalHarmonics::Scale(sum, rcp(wsum + EPSILON_WEIGHT_SUM)); } #endif + +#if defined(PSHADER) + float SampleShadowVisibility(float3 positionMS, float3 normalWS, float2 screenPosition) + { + if (SharedData::InInterior) + return 1.0; + + positionMS.xyz += normalWS * CELL_SIZE * 0.5; + + if (SharedData::FrameCount) { + float3 offset = float3(Random::pcg3d(uint3(screenPosition.xy, SharedData::FrameCount))) / 4294967295.0 * 2.0 - 1.0; + positionMS.xyz += offset * CELL_SIZE * 0.5; + } + + float3 positionMSAdjusted = positionMS - SharedData::skylightingSettings.PosOffset.xyz; + float3 uvw = positionMSAdjusted / ARRAY_SIZE + .5; + + if (any(uvw < 0) || any(uvw > 1)) + return 1.0; + + float3 cellVxCoord = uvw * ARRAY_DIM; + int3 cell000 = floor(cellVxCoord - 0.5); + float3 trilinearPos = cellVxCoord - 0.5 - cell000; + + float sum = 0; + float wsum = 0; + [unroll] for (int i = 0; i < 2; i++) + [unroll] for (int j = 0; j < 2; j++) + [unroll] for (int k = 0; k < 2; k++) + { + int3 offset = int3(i, j, k); + int3 cellID = cell000 + offset; + + if (any(cellID < 0) || any((uint3)cellID >= ARRAY_DIM)) + continue; + + float3 trilinearWeights = 1 - abs(offset - trilinearPos); + float w = trilinearWeights.x * trilinearWeights.y * trilinearWeights.z; + + uint3 cellTexID = (cellID + SharedData::skylightingSettings.ArrayOrigin.xyz) % ARRAY_DIM; + sum += ShadowVisibilityProbeArray[cellTexID] * w; + wsum += w; + } + + float fadeOut = GetFadeOutFactor(positionMS); + float shadow = sum / max(wsum, 0.0001); + return lerp(1.0, shadow, fadeOut); + } +#endif } #endif diff --git a/features/Skylighting/Shaders/Skylighting/UpdateProbesCS.hlsl b/features/Skylighting/Shaders/Skylighting/UpdateProbesCS.hlsl index c2906f4619..6402287907 100644 --- a/features/Skylighting/Shaders/Skylighting/UpdateProbesCS.hlsl +++ b/features/Skylighting/Shaders/Skylighting/UpdateProbesCS.hlsl @@ -1,13 +1,62 @@ +#include "Common/FrameBuffer.hlsli" #include "Common/Math.hlsli" #include "Skylighting/Skylighting.hlsli" Texture2D srcOcclusionDepth : register(t0); +Texture2DArray ShadowCascadeMap : register(t1); + +struct DirectionalShadowLightData +{ + column_major float4x4 ShadowProj[2]; + column_major float4x4 InvShadowProj[2]; + float2 EndSplitDistances; + float2 StartSplitDistances; + float4 CascadeDepthParams; +}; +StructuredBuffer DirectionalShadowLights : register(t2); RWTexture3D outProbeArray : register(u0); RWTexture3D outAccumFramesArray : register(u1); +RWTexture3D outShadowBitmask : register(u2); +RWTexture3D outShadowVisibility : register(u3); SamplerComparisonState comparisonSampler : register(s0); +static const float3 noise3D[32] = { + float3(0.247, -0.583, 0.891), + float3(-0.672, 0.315, -0.428), + float3(0.934, 0.762, -0.153), + float3(-0.391, -0.847, 0.526), + float3(0.618, 0.094, 0.739), + float3(-0.825, -0.271, -0.683), + float3(0.152, 0.968, 0.347), + float3(0.503, -0.714, -0.592), + float3(-0.436, 0.629, 0.814), + float3(0.887, -0.198, 0.461), + float3(-0.759, 0.852, -0.305), + float3(0.321, -0.476, -0.921), + float3(-0.094, 0.543, -0.768), + float3(0.776, 0.418, 0.632), + float3(-0.538, -0.695, 0.279), + float3(0.649, -0.921, 0.186), + float3(-0.913, 0.127, 0.574), + float3(0.285, 0.806, -0.447), + float3(0.471, -0.352, 0.698), + float3(-0.627, -0.194, -0.856), + float3(0.834, 0.591, -0.712), + float3(-0.173, -0.968, -0.421), + float3(0.562, 0.239, -0.785), + float3(-0.745, 0.487, 0.316), + float3(0.108, -0.631, 0.894), + float3(0.926, -0.845, -0.267), + float3(-0.384, 0.712, -0.539), + float3(0.697, 0.163, 0.825), + float3(-0.851, -0.429, 0.641), + float3(0.214, 0.934, 0.372), + float3(0.578, -0.762, -0.614), + float3(-0.469, 0.381, 0.947) +}; + [numthreads(8, 8, 1)] void main(uint3 dtid : SV_DispatchThreadID) { const float fadeInThreshold = 15; const static sh2 unitSH = Skylighting::UNIT_SH; @@ -39,8 +88,60 @@ SamplerComparisonState comparisonSampler : register(s0); outProbeArray[dtid] = occlusionSH; outAccumFramesArray[dtid] = accumFrames; + + // Shadow cascade sampling with bitmask accumulation and Gaussian spatial blur + { + float shadowSample = 1.0; + + if (SharedData::HasDirectionalShadows && !SharedData::InInterior) { + DirectionalShadowLightData shadowData = DirectionalShadowLights[0]; + + uint bitIndex = (accumFrames - 1) % 32; + float3 jitteredMS = cellCentreMS + noise3D[bitIndex] * Skylighting::CELL_SIZE; + + float ndcDepth = FrameBuffer::GetShadowDepth(jitteredMS, 0); + float linearDepth = SharedData::GetScreenDepth(ndcDepth); + + if (linearDepth > 0 && linearDepth < shadowData.EndSplitDistances.y) { + float3 positionWS = jitteredMS + FrameBuffer::CameraPosAdjust[0].xyz; + + float cascadeSelect = saturate((linearDepth - shadowData.StartSplitDistances.y) / + (shadowData.EndSplitDistances.x - shadowData.StartSplitDistances.y)); + uint cascadeIndex = uint(cascadeSelect); + + float3 positionLS = mul(shadowData.ShadowProj[cascadeIndex], float4(positionWS, 1)).xyz; + + if (all(positionLS.xy >= 0) && all(positionLS.xy <= 1)) { + shadowSample = ShadowCascadeMap.SampleCmpLevelZero(comparisonSampler, float3(positionLS.xy, cascadeIndex), positionLS.z); + } + + float fade = saturate(linearDepth / shadowData.EndSplitDistances.y); + float fadeFactor = 1.0 - pow(fade * fade, 8); + shadowSample = lerp(1.0, shadowSample, fadeFactor); + } + + uint bitmask = isValid ? outShadowBitmask[dtid] : 0; + bitmask &= ~(1u << bitIndex); + if (shadowSample > 0.5) + bitmask |= (1u << bitIndex); + + outShadowBitmask[dtid] = bitmask; + + uint validBits = min(accumFrames, 32u); + float shadow = float(countbits(bitmask)) / float(validBits); + shadow = lerp(1.0, shadow, min(fadeInThreshold, accumFrames) / fadeInThreshold); + outShadowVisibility[dtid] = shadow; + } else { + if (!isValid) { + outShadowBitmask[dtid] = 0; + outShadowVisibility[dtid] = 1.0; + } + } + } } else if (!isValid) { outProbeArray[dtid] = unitSH; outAccumFramesArray[dtid] = 0; + outShadowBitmask[dtid] = 0; + outShadowVisibility[dtid] = 1.0; } -} \ No newline at end of file +} diff --git a/package/Shaders/DeferredCompositeCS.hlsl b/package/Shaders/DeferredCompositeCS.hlsl index 9075bfc0ba..4ce3ec0ccf 100644 --- a/package/Shaders/DeferredCompositeCS.hlsl +++ b/package/Shaders/DeferredCompositeCS.hlsl @@ -136,7 +136,7 @@ void SampleSSGISpecular(uint2 pixCoord, sh2 lobe, inout float ao, out float3 il, # if defined(SKYLIGHTING) float3 positionMS = positionWS.xyz; - sh2 skylightingSH = Skylighting::Sample(positionMS.xyz, normalWS); + sh2 skylightingSH = Skylighting::Sample(positionMS.xyz, normalWS, float2(dispatchID.xy)); float skylightingDiffuse = Skylighting::EvaluateDiffuse(skylightingSH, normalWS); directionalAmbientColor = ImageBasedLighting::GetDiffuseIBLOccluded(vanillaDALC, -normalWS, skylightingDiffuse) * albedo; # else @@ -202,7 +202,7 @@ void SampleSSGISpecular(uint2 pixCoord, sh2 lobe, inout float ao, out float3 il, # if defined(SKYLIGHTING) float3 positionMS = positionWS.xyz; - sh2 skylightingSH = Skylighting::Sample(positionMS.xyz, R); + sh2 skylightingSH = Skylighting::Sample(positionMS.xyz, R, float2(dispatchID.xy)); float skylightingSpecular = Skylighting::EvaluateSpecular(skylightingSH, specularLobe); # endif diff --git a/package/Shaders/Lighting.hlsl b/package/Shaders/Lighting.hlsl index 5a7740508f..6a1cfd910f 100644 --- a/package/Shaders/Lighting.hlsl +++ b/package/Shaders/Lighting.hlsl @@ -2409,9 +2409,9 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) # if defined(SKYLIGHTING) float3 positionMSSkylight = input.WorldPosition.xyz; # if defined(DEFERRED) - sh2 skylightingSH = Skylighting::Sample(positionMSSkylight, worldNormal); + sh2 skylightingSH = Skylighting::Sample(positionMSSkylight, worldNormal, input.Position.xy); # else - sh2 skylightingSH = inWorld ? Skylighting::Sample(positionMSSkylight, worldNormal) : Skylighting::UNIT_SH; + sh2 skylightingSH = inWorld ? Skylighting::Sample(positionMSSkylight, worldNormal, input.Position.xy) : Skylighting::UNIT_SH; # endif # endif diff --git a/package/Shaders/RunGrass.hlsl b/package/Shaders/RunGrass.hlsl index d41f5258de..77c042a415 100644 --- a/package/Shaders/RunGrass.hlsl +++ b/package/Shaders/RunGrass.hlsl @@ -559,7 +559,8 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) # if defined(SKYLIGHTING) float3 positionMSSkylight = input.WorldPosition.xyz; - float skylightingDiffuse = Skylighting::GetVertexSkylightingDiffuse(positionMSSkylight, normal, vertexAO); + sh2 skylightingSH = Skylighting::Sample(positionMSSkylight, normal, input.HPosition.xy); + float skylightingDiffuse = Skylighting::GetSkylightingDiffuse(skylightingSH, positionMSSkylight, normal, vertexAO); # endif // SKYLIGHTING float3 albedo = baseColor.xyz * vertexColor; @@ -820,7 +821,8 @@ PS_OUTPUT main(PS_INPUT input) # if defined(SKYLIGHTING) float3 positionMSSkylight = input.WorldPosition.xyz; - float skylightingDiffuse = Skylighting::GetVertexSkylightingDiffuse(positionMSSkylight, normal, vertexAO); + sh2 skylightingSH = Skylighting::Sample(positionMSSkylight, normal, input.HPosition.xy); + float skylightingDiffuse = Skylighting::GetSkylightingDiffuse(skylightingSH, positionMSSkylight, normal, vertexAO); # endif // SKYLIGHTING float3 directionalAmbientColor = Color::Ambient(max(0, SharedData::GetAmbient(normal))); diff --git a/src/Features/Skylighting.cpp b/src/Features/Skylighting.cpp index 6a44d86b0d..b5030e6667 100644 --- a/src/Features/Skylighting.cpp +++ b/src/Features/Skylighting.cpp @@ -1,5 +1,6 @@ #include "Skylighting.h" +#include "Deferred.h" #include "I18n/I18n.h" #include "ShaderCache.h" #include "State.h" @@ -33,6 +34,11 @@ void Skylighting::ResetSkylighting() auto context = globals::d3d::context; UINT clr[1] = { 0 }; context->ClearUnorderedAccessViewUint(texAccumFramesArray->uav.get(), clr); + context->ClearUnorderedAccessViewUint(texShadowBitmask->uav.get(), clr); + + float clrf[4] = { 1.0f, 1.0f, 1.0f, 1.0f }; + context->ClearUnorderedAccessViewFloat(texShadowVisibility->uav.get(), clrf); + queuedResetSkylighting = false; } @@ -113,6 +119,18 @@ void Skylighting::SetupResources() texAccumFramesArray = new Texture3D(texDesc, "Skylighting::AccumFramesArray"); texAccumFramesArray->CreateSRV(srvDesc); texAccumFramesArray->CreateUAV(uavDesc); + + texDesc.Format = srvDesc.Format = uavDesc.Format = DXGI_FORMAT_R32_UINT; + + texShadowBitmask = new Texture3D(texDesc, "Skylighting::ShadowBitmask"); + texShadowBitmask->CreateSRV(srvDesc); + texShadowBitmask->CreateUAV(uavDesc); + + texDesc.Format = srvDesc.Format = uavDesc.Format = DXGI_FORMAT_R16_FLOAT; + + texShadowVisibility = new Texture3D(texDesc, "Skylighting::ShadowVisibility"); + texShadowVisibility->CreateSRV(srvDesc); + texShadowVisibility->CreateUAV(uavDesc); } { @@ -220,9 +238,20 @@ void Skylighting::Prepass() auto context = globals::d3d::context; { - std::array srvs = { texOcclusion->srv.get() }; - std::array uavs = { texProbeArray->uav.get(), texAccumFramesArray->uav.get() }; - std::array samplers = { comparisonSampler.get() }; + std::array srvs = { + texOcclusion->srv.get(), + shadowCascadeSRV, + globals::deferred->directionalShadowLights->srv.get() + }; + std::array uavs = { + texProbeArray->uav.get(), + texAccumFramesArray->uav.get(), + texShadowBitmask->uav.get(), + texShadowVisibility->uav.get() + }; + std::array samplers = { + comparisonSampler.get() + }; // Update probe array { @@ -252,6 +281,9 @@ void Skylighting::Prepass() { ID3D11ShaderResourceView* srv = texProbeArray->srv.get(); context->PSSetShaderResources(50, 1, &srv); + + srv = texShadowVisibility->srv.get(); + context->PSSetShaderResources(53, 1, &srv); } } @@ -601,6 +633,16 @@ void Skylighting::RenderOcclusion() } } +void Skylighting::CaptureShadowCascadeSRV() +{ + auto context = globals::d3d::context; + ID3D11ShaderResourceView* srv = nullptr; + context->PSGetShaderResources(4, 1, &srv); + if (shadowCascadeSRV) + shadowCascadeSRV->Release(); + shadowCascadeSRV = srv; +} + void Skylighting::Main_Precipitation_RenderOcclusion::thunk() { globals::features::skylighting.RenderOcclusion(); diff --git a/src/Features/Skylighting.h b/src/Features/Skylighting.h index 78aa3ee0c6..767ec51996 100644 --- a/src/Features/Skylighting.h +++ b/src/Features/Skylighting.h @@ -72,6 +72,10 @@ struct Skylighting : Feature Texture2D* texOcclusion = nullptr; Texture3D* texProbeArray = nullptr; Texture3D* texAccumFramesArray = nullptr; + Texture3D* texShadowBitmask = nullptr; + Texture3D* texShadowVisibility = nullptr; + + ID3D11ShaderResourceView* shadowCascadeSRV = nullptr; winrt::com_ptr probeUpdateCompute = nullptr; @@ -87,6 +91,7 @@ struct Skylighting : Feature uint frameCount = 0; void ResetSkylighting(); + void CaptureShadowCascadeSRV(); std::chrono::time_point lastUpdateTimer = std::chrono::system_clock::now(); diff --git a/src/State.cpp b/src/State.cpp index fbc6d07892..8f577cfaf2 100644 --- a/src/State.cpp +++ b/src/State.cpp @@ -13,6 +13,7 @@ #include "Features/InteriorSun.h" #include "Features/PerformanceOverlay.h" #include "Features/Skin.h" +#include "Features/Skylighting.h" #include "Features/SkySync.h" #include "Features/TerrainBlending.h" #include "Features/TerrainHelper.h" @@ -59,6 +60,7 @@ void State::Draw() auto& truePBR = globals::features::truePBR; auto context = globals::d3d::context; auto& volumetricShadows = globals::features::volumetricShadows; + auto& skylighting = globals::features::skylighting; if (shaderCache->IsEnabled()) { // Process deferred cell transitions (interior detection) @@ -106,6 +108,8 @@ void State::Draw() volumetricShadows.CopyShadowLightData(); if (globals::features::exponentialHeightFog.loaded) globals::features::exponentialHeightFog.CaptureDirectionalShadowMap(); + if (skylighting.loaded) + skylighting.CaptureShadowCascadeSRV(); } } } From 43925e42f872fc260a0888696b3e9fb2b82a5165 Mon Sep 17 00:00:00 2001 From: doodlum <15017472+doodlum@users.noreply.github.com> Date: Wed, 10 Jun 2026 23:42:06 +0100 Subject: [PATCH 02/20] fix(skylighting): address review findings for shadow cascade probes - Remove CascadeDepthParams from HLSL struct to match CPU-side layout - Fix cascade selection collapsing to cascade 0 by using threshold compare - Guard null shadowCascadeSRV before compute dispatch - Use EPSILON_WEIGHT_SUM constant in SampleShadowVisibility Co-Authored-By: Claude Opus 4.6 --- features/Skylighting/Shaders/Skylighting/Skylighting.hlsli | 2 +- features/Skylighting/Shaders/Skylighting/UpdateProbesCS.hlsl | 5 +---- src/Features/Skylighting.cpp | 4 ++-- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/features/Skylighting/Shaders/Skylighting/Skylighting.hlsli b/features/Skylighting/Shaders/Skylighting/Skylighting.hlsli index b1e6869cd0..39b6b6d025 100644 --- a/features/Skylighting/Shaders/Skylighting/Skylighting.hlsli +++ b/features/Skylighting/Shaders/Skylighting/Skylighting.hlsli @@ -239,7 +239,7 @@ namespace Skylighting } float fadeOut = GetFadeOutFactor(positionMS); - float shadow = sum / max(wsum, 0.0001); + float shadow = sum / max(wsum, EPSILON_WEIGHT_SUM); return lerp(1.0, shadow, fadeOut); } #endif diff --git a/features/Skylighting/Shaders/Skylighting/UpdateProbesCS.hlsl b/features/Skylighting/Shaders/Skylighting/UpdateProbesCS.hlsl index 6402287907..7c4edbefad 100644 --- a/features/Skylighting/Shaders/Skylighting/UpdateProbesCS.hlsl +++ b/features/Skylighting/Shaders/Skylighting/UpdateProbesCS.hlsl @@ -11,7 +11,6 @@ struct DirectionalShadowLightData column_major float4x4 InvShadowProj[2]; float2 EndSplitDistances; float2 StartSplitDistances; - float4 CascadeDepthParams; }; StructuredBuffer DirectionalShadowLights : register(t2); @@ -105,9 +104,7 @@ static const float3 noise3D[32] = { if (linearDepth > 0 && linearDepth < shadowData.EndSplitDistances.y) { float3 positionWS = jitteredMS + FrameBuffer::CameraPosAdjust[0].xyz; - float cascadeSelect = saturate((linearDepth - shadowData.StartSplitDistances.y) / - (shadowData.EndSplitDistances.x - shadowData.StartSplitDistances.y)); - uint cascadeIndex = uint(cascadeSelect); + uint cascadeIndex = (linearDepth > shadowData.EndSplitDistances.x) ? 1u : 0u; float3 positionLS = mul(shadowData.ShadowProj[cascadeIndex], float4(positionWS, 1)).xyz; diff --git a/src/Features/Skylighting.cpp b/src/Features/Skylighting.cpp index b5030e6667..9ee15c9377 100644 --- a/src/Features/Skylighting.cpp +++ b/src/Features/Skylighting.cpp @@ -240,8 +240,8 @@ void Skylighting::Prepass() { std::array srvs = { texOcclusion->srv.get(), - shadowCascadeSRV, - globals::deferred->directionalShadowLights->srv.get() + shadowCascadeSRV ? shadowCascadeSRV : nullptr, + shadowCascadeSRV ? globals::deferred->directionalShadowLights->srv.get() : nullptr }; std::array uavs = { texProbeArray->uav.get(), From 6db654b940d1985e7658a842be8972f37d9e0e1c Mon Sep 17 00:00:00 2001 From: doodlum <15017472+doodlum@users.noreply.github.com> Date: Wed, 10 Jun 2026 23:48:06 +0100 Subject: [PATCH 03/20] refactor(skylighting): merge shadow visibility into Sample loop Fold SampleShadowVisibility into Sample via a conditional out parameter, eliminating the duplicate trilinear loop. In PSHADER contexts the shadow visibility probe is read in the same 8-cell pass as the SH probe, halving texture fetches and shared setup (jitter, UVW, bounds checks). Compute shader callers (SKYLIGHTING_PROBE_REGISTER) are unaffected. Co-Authored-By: Claude Opus 4.6 --- .../Shaders/Skylighting/Skylighting.hlsli | 115 +++++++----------- package/Shaders/Lighting.hlsl | 6 +- package/Shaders/RunGrass.hlsl | 6 +- 3 files changed, 48 insertions(+), 79 deletions(-) diff --git a/features/Skylighting/Shaders/Skylighting/Skylighting.hlsli b/features/Skylighting/Shaders/Skylighting/Skylighting.hlsli index 39b6b6d025..7e7ef5fa74 100644 --- a/features/Skylighting/Shaders/Skylighting/Skylighting.hlsli +++ b/features/Skylighting/Shaders/Skylighting/Skylighting.hlsli @@ -80,14 +80,22 @@ namespace Skylighting #endif #if defined(PSHADER) || defined(SKYLIGHTING_PROBE_REGISTER) - sh2 Sample(float3 positionMS, float3 normalWS, float2 screenPosition) + sh2 Sample(float3 positionMS, float3 normalWS, float2 screenPosition +#if defined(PSHADER) + , out float shadowVisibility +#endif + ) { sh2 scaledUnitSH = UNIT_SH / 1e-10; +#if defined(PSHADER) + shadowVisibility = 1.0; +#endif + if (SharedData::InInterior) return scaledUnitSH; - positionMS.xyz += normalWS * CELL_SIZE * 0.5; // Receiver normal bias + positionMS.xyz += normalWS * CELL_SIZE * 0.5; if (SharedData::FrameCount) { float3 offset = float3(Random::pcg3d(uint3(screenPosition.xy, SharedData::FrameCount))) / 4294967295.0 * 2.0 - 1.0; @@ -104,35 +112,48 @@ namespace Skylighting int3 cell000 = floor(cellVxCoord - 0.5); float3 trilinearPos = cellVxCoord - 0.5 - cell000; - sh2 sum = 0; - float wsum = 0; + sh2 shSum = 0; + float shWsum = 0; +#if defined(PSHADER) + float shadowSum = 0; + float shadowWsum = 0; +#endif + for (int i = 0; i < 2; i++) for (int j = 0; j < 2; j++) for (int k = 0; k < 2; k++) { - int3 offset = int3(i, j, k); - int3 cellID = cell000 + offset; + int3 cellOffset = int3(i, j, k); + int3 cellID = cell000 + cellOffset; if (any(cellID < 0) || any((uint3)cellID >= ARRAY_DIM)) continue; - float3 cellCentreMS = cellID + 0.5 - ARRAY_DIM / 2; - cellCentreMS = cellCentreMS * CELL_SIZE; + float3 cellCentreMS = (cellID + 0.5 - ARRAY_DIM / 2) * CELL_SIZE; - // https://handmade.network/p/75/monter/blog/p/7288-engine_work__global_illumination_with_irradiance_probes - // basic tangent checks - float tangentWeight = dot(normalize(cellCentreMS - positionMSAdjusted), normalWS) * 0.5 + 0.5; - - float3 trilinearWeights = 1 - abs(offset - trilinearPos); - float w = trilinearWeights.x * trilinearWeights.y * trilinearWeights.z * tangentWeight; + float3 trilinearWeights = 1 - abs(cellOffset - trilinearPos); + float triW = trilinearWeights.x * trilinearWeights.y * trilinearWeights.z; uint3 cellTexID = (cellID + SharedData::skylightingSettings.ArrayOrigin.xyz) % ARRAY_DIM; - sh2 probe = SphericalHarmonics::Scale(SkylightingProbeArray[cellTexID], w); - sum = SphericalHarmonics::Add(sum, probe); - wsum += w; + // SH probe: tangent-weighted interpolation + float tangentWeight = dot(normalize(cellCentreMS - positionMSAdjusted), normalWS) * 0.5 + 0.5; + float shW = triW * tangentWeight; + shSum = SphericalHarmonics::Add(shSum, SphericalHarmonics::Scale(SkylightingProbeArray[cellTexID], shW)); + shWsum += shW; + +#if defined(PSHADER) + // Shadow visibility: simple trilinear + shadowSum += ShadowVisibilityProbeArray[cellTexID] * triW; + shadowWsum += triW; +#endif } - return SphericalHarmonics::Scale(sum, rcp(wsum + EPSILON_WEIGHT_SUM)); +#if defined(PSHADER) + float fadeOut = GetFadeOutFactor(positionMS); + shadowVisibility = lerp(1.0, shadowSum / max(shadowWsum, EPSILON_WEIGHT_SUM), fadeOut); +#endif + + return SphericalHarmonics::Scale(shSum, rcp(shWsum + EPSILON_WEIGHT_SUM)); } float GetSkylightingDiffuse(sh2 skylightingSH, float3 positionMS, float3 evalNormal, float vertexAO = 1.0) @@ -147,8 +168,6 @@ namespace Skylighting return saturate(skylightingDiffuse / max(vertexAO, EPSILON_DIVISION)); } - - sh2 SampleNoBias(float3 positionMS) { sh2 scaledUnitSH = UNIT_SH / 1e-10; @@ -178,71 +197,19 @@ namespace Skylighting if (any(cellID < 0) || any((uint3)cellID >= ARRAY_DIM)) continue; - float3 cellCentreMS = cellID + 0.5 - ARRAY_DIM / 2; - cellCentreMS = cellCentreMS * CELL_SIZE; + float3 cellCentreMS = (cellID + 0.5 - ARRAY_DIM / 2) * CELL_SIZE; float3 trilinearWeights = 1 - abs(offset - trilinearPos); float w = trilinearWeights.x * trilinearWeights.y * trilinearWeights.z; uint3 cellTexID = (cellID + SharedData::skylightingSettings.ArrayOrigin.xyz) % ARRAY_DIM; - sh2 probe = SphericalHarmonics::Scale(SkylightingProbeArray[cellTexID], w); - - sum = SphericalHarmonics::Add(sum, probe); + sum = SphericalHarmonics::Add(sum, SphericalHarmonics::Scale(SkylightingProbeArray[cellTexID], w)); wsum += w; } return SphericalHarmonics::Scale(sum, rcp(wsum + EPSILON_WEIGHT_SUM)); } #endif - -#if defined(PSHADER) - float SampleShadowVisibility(float3 positionMS, float3 normalWS, float2 screenPosition) - { - if (SharedData::InInterior) - return 1.0; - - positionMS.xyz += normalWS * CELL_SIZE * 0.5; - - if (SharedData::FrameCount) { - float3 offset = float3(Random::pcg3d(uint3(screenPosition.xy, SharedData::FrameCount))) / 4294967295.0 * 2.0 - 1.0; - positionMS.xyz += offset * CELL_SIZE * 0.5; - } - - float3 positionMSAdjusted = positionMS - SharedData::skylightingSettings.PosOffset.xyz; - float3 uvw = positionMSAdjusted / ARRAY_SIZE + .5; - - if (any(uvw < 0) || any(uvw > 1)) - return 1.0; - - float3 cellVxCoord = uvw * ARRAY_DIM; - int3 cell000 = floor(cellVxCoord - 0.5); - float3 trilinearPos = cellVxCoord - 0.5 - cell000; - - float sum = 0; - float wsum = 0; - [unroll] for (int i = 0; i < 2; i++) - [unroll] for (int j = 0; j < 2; j++) - [unroll] for (int k = 0; k < 2; k++) - { - int3 offset = int3(i, j, k); - int3 cellID = cell000 + offset; - - if (any(cellID < 0) || any((uint3)cellID >= ARRAY_DIM)) - continue; - - float3 trilinearWeights = 1 - abs(offset - trilinearPos); - float w = trilinearWeights.x * trilinearWeights.y * trilinearWeights.z; - - uint3 cellTexID = (cellID + SharedData::skylightingSettings.ArrayOrigin.xyz) % ARRAY_DIM; - sum += ShadowVisibilityProbeArray[cellTexID] * w; - wsum += w; - } - - float fadeOut = GetFadeOutFactor(positionMS); - float shadow = sum / max(wsum, EPSILON_WEIGHT_SUM); - return lerp(1.0, shadow, fadeOut); - } -#endif } #endif diff --git a/package/Shaders/Lighting.hlsl b/package/Shaders/Lighting.hlsl index 6a1cfd910f..fc1adb4a50 100644 --- a/package/Shaders/Lighting.hlsl +++ b/package/Shaders/Lighting.hlsl @@ -2408,12 +2408,12 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) # if defined(SKYLIGHTING) float3 positionMSSkylight = input.WorldPosition.xyz; + float skylightingShadowVisibility = 1.0; # if defined(DEFERRED) - sh2 skylightingSH = Skylighting::Sample(positionMSSkylight, worldNormal, input.Position.xy); + sh2 skylightingSH = Skylighting::Sample(positionMSSkylight, worldNormal, input.Position.xy, skylightingShadowVisibility); # else - sh2 skylightingSH = inWorld ? Skylighting::Sample(positionMSSkylight, worldNormal, input.Position.xy) : Skylighting::UNIT_SH; + sh2 skylightingSH = inWorld ? Skylighting::Sample(positionMSSkylight, worldNormal, input.Position.xy, skylightingShadowVisibility) : Skylighting::UNIT_SH; # endif - # endif float4 waterData = SharedData::GetWaterData(input.WorldPosition.xyz); diff --git a/package/Shaders/RunGrass.hlsl b/package/Shaders/RunGrass.hlsl index 77c042a415..bf36f45b69 100644 --- a/package/Shaders/RunGrass.hlsl +++ b/package/Shaders/RunGrass.hlsl @@ -559,7 +559,8 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) # if defined(SKYLIGHTING) float3 positionMSSkylight = input.WorldPosition.xyz; - sh2 skylightingSH = Skylighting::Sample(positionMSSkylight, normal, input.HPosition.xy); + float skylightingShadowVisibility = 1.0; + sh2 skylightingSH = Skylighting::Sample(positionMSSkylight, normal, input.HPosition.xy, skylightingShadowVisibility); float skylightingDiffuse = Skylighting::GetSkylightingDiffuse(skylightingSH, positionMSSkylight, normal, vertexAO); # endif // SKYLIGHTING @@ -821,7 +822,8 @@ PS_OUTPUT main(PS_INPUT input) # if defined(SKYLIGHTING) float3 positionMSSkylight = input.WorldPosition.xyz; - sh2 skylightingSH = Skylighting::Sample(positionMSSkylight, normal, input.HPosition.xy); + float skylightingShadowVisibility = 1.0; + sh2 skylightingSH = Skylighting::Sample(positionMSSkylight, normal, input.HPosition.xy, skylightingShadowVisibility); float skylightingDiffuse = Skylighting::GetSkylightingDiffuse(skylightingSH, positionMSSkylight, normal, vertexAO); # endif // SKYLIGHTING From bdfc81b9c0a055f528dbcb2e0bb95da98569fbdd Mon Sep 17 00:00:00 2001 From: doodlum <15017472+doodlum@users.noreply.github.com> Date: Wed, 10 Jun 2026 23:52:31 +0100 Subject: [PATCH 04/20] refactor(skylighting): restrict shadow visibility to rim/soft/back lighting Gate shadow visibility sampling behind SKYLIGHTING_SHADOW_VIS, defined only for RIM_LIGHTING, SOFT_LIGHTING, LOAD_SOFT_LIGHTING, and BACK_LIGHTING permutations. Other Lighting.hlsl permutations and RunGrass.hlsl skip the shadow visibility texture fetch entirely. Apply visibility to material.rimSoftLightColor and backLightColor. Co-Authored-By: Claude Opus 4.6 --- .../Shaders/Skylighting/Skylighting.hlsli | 13 +++++------ package/Shaders/Lighting.hlsl | 23 +++++++++++++++++-- package/Shaders/RunGrass.hlsl | 6 ++--- 3 files changed, 29 insertions(+), 13 deletions(-) diff --git a/features/Skylighting/Shaders/Skylighting/Skylighting.hlsli b/features/Skylighting/Shaders/Skylighting/Skylighting.hlsli index 7e7ef5fa74..3dac63d102 100644 --- a/features/Skylighting/Shaders/Skylighting/Skylighting.hlsli +++ b/features/Skylighting/Shaders/Skylighting/Skylighting.hlsli @@ -15,7 +15,7 @@ namespace Skylighting Texture3D SkylightingProbeArray : register(t50); #endif -#if defined(PSHADER) +#if defined(SKYLIGHTING_SHADOW_VIS) Texture3D ShadowVisibilityProbeArray : register(t53); #endif @@ -81,14 +81,14 @@ namespace Skylighting #if defined(PSHADER) || defined(SKYLIGHTING_PROBE_REGISTER) sh2 Sample(float3 positionMS, float3 normalWS, float2 screenPosition -#if defined(PSHADER) +#if defined(SKYLIGHTING_SHADOW_VIS) , out float shadowVisibility #endif ) { sh2 scaledUnitSH = UNIT_SH / 1e-10; -#if defined(PSHADER) +#if defined(SKYLIGHTING_SHADOW_VIS) shadowVisibility = 1.0; #endif @@ -114,7 +114,7 @@ namespace Skylighting sh2 shSum = 0; float shWsum = 0; -#if defined(PSHADER) +#if defined(SKYLIGHTING_SHADOW_VIS) float shadowSum = 0; float shadowWsum = 0; #endif @@ -141,14 +141,13 @@ namespace Skylighting shSum = SphericalHarmonics::Add(shSum, SphericalHarmonics::Scale(SkylightingProbeArray[cellTexID], shW)); shWsum += shW; -#if defined(PSHADER) - // Shadow visibility: simple trilinear +#if defined(SKYLIGHTING_SHADOW_VIS) shadowSum += ShadowVisibilityProbeArray[cellTexID] * triW; shadowWsum += triW; #endif } -#if defined(PSHADER) +#if defined(SKYLIGHTING_SHADOW_VIS) float fadeOut = GetFadeOutFactor(positionMS); shadowVisibility = lerp(1.0, shadowSum / max(shadowWsum, EPSILON_WEIGHT_SUM), fadeOut); #endif diff --git a/package/Shaders/Lighting.hlsl b/package/Shaders/Lighting.hlsl index fc1adb4a50..f165453341 100644 --- a/package/Shaders/Lighting.hlsl +++ b/package/Shaders/Lighting.hlsl @@ -845,6 +845,9 @@ float GetSnowParameterY(float texProjTmp, float alpha) # endif # if defined(SKYLIGHTING) +# if defined(RIM_LIGHTING) || defined(SOFT_LIGHTING) || defined(LOAD_SOFT_LIGHTING) || defined(BACK_LIGHTING) +# define SKYLIGHTING_SHADOW_VIS +# endif # include "Skylighting/Skylighting.hlsli" # endif @@ -2273,9 +2276,15 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) # endif # if (defined(RIM_LIGHTING) || defined(SOFT_LIGHTING) || defined(LOAD_SOFT_LIGHTING)) material.rimSoftLightColor = rimSoftLightColor.xyz; +# if defined(SKYLIGHTING_SHADOW_VIS) + material.rimSoftLightColor *= skylightingShadowVisibility; +# endif # endif # if defined(BACK_LIGHTING) material.backLightColor = backLightColor.xyz; +# if defined(SKYLIGHTING_SHADOW_VIS) + material.backLightColor *= skylightingShadowVisibility; +# endif # endif # endif // TRUE_PBR @@ -2408,11 +2417,21 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) # if defined(SKYLIGHTING) float3 positionMSSkylight = input.WorldPosition.xyz; +# if defined(SKYLIGHTING_SHADOW_VIS) float skylightingShadowVisibility = 1.0; +# endif # if defined(DEFERRED) - sh2 skylightingSH = Skylighting::Sample(positionMSSkylight, worldNormal, input.Position.xy, skylightingShadowVisibility); + sh2 skylightingSH = Skylighting::Sample(positionMSSkylight, worldNormal, input.Position.xy +# if defined(SKYLIGHTING_SHADOW_VIS) + , skylightingShadowVisibility +# endif + ); # else - sh2 skylightingSH = inWorld ? Skylighting::Sample(positionMSSkylight, worldNormal, input.Position.xy, skylightingShadowVisibility) : Skylighting::UNIT_SH; + sh2 skylightingSH = inWorld ? Skylighting::Sample(positionMSSkylight, worldNormal, input.Position.xy +# if defined(SKYLIGHTING_SHADOW_VIS) + , skylightingShadowVisibility +# endif + ) : Skylighting::UNIT_SH; # endif # endif diff --git a/package/Shaders/RunGrass.hlsl b/package/Shaders/RunGrass.hlsl index bf36f45b69..77c042a415 100644 --- a/package/Shaders/RunGrass.hlsl +++ b/package/Shaders/RunGrass.hlsl @@ -559,8 +559,7 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) # if defined(SKYLIGHTING) float3 positionMSSkylight = input.WorldPosition.xyz; - float skylightingShadowVisibility = 1.0; - sh2 skylightingSH = Skylighting::Sample(positionMSSkylight, normal, input.HPosition.xy, skylightingShadowVisibility); + sh2 skylightingSH = Skylighting::Sample(positionMSSkylight, normal, input.HPosition.xy); float skylightingDiffuse = Skylighting::GetSkylightingDiffuse(skylightingSH, positionMSSkylight, normal, vertexAO); # endif // SKYLIGHTING @@ -822,8 +821,7 @@ PS_OUTPUT main(PS_INPUT input) # if defined(SKYLIGHTING) float3 positionMSSkylight = input.WorldPosition.xyz; - float skylightingShadowVisibility = 1.0; - sh2 skylightingSH = Skylighting::Sample(positionMSSkylight, normal, input.HPosition.xy, skylightingShadowVisibility); + sh2 skylightingSH = Skylighting::Sample(positionMSSkylight, normal, input.HPosition.xy); float skylightingDiffuse = Skylighting::GetSkylightingDiffuse(skylightingSH, positionMSSkylight, normal, vertexAO); # endif // SKYLIGHTING From 53b85f0c06a96e410dec3fa40acc851805e9b4cb Mon Sep 17 00:00:00 2001 From: doodlum <15017472+doodlum@users.noreply.github.com> Date: Wed, 10 Jun 2026 23:53:43 +0100 Subject: [PATCH 05/20] fix(skylighting): guard degenerate normal and remove dead code Handle zero-length biasedNormal in GetSkylightingDiffuse when evalNormal points straight down. Remove unused cellCentreMS in SampleNoBias. Co-Authored-By: Claude Opus 4.6 --- features/Skylighting/Shaders/Skylighting/Skylighting.hlsli | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/features/Skylighting/Shaders/Skylighting/Skylighting.hlsli b/features/Skylighting/Shaders/Skylighting/Skylighting.hlsli index 3dac63d102..5ebc840fbd 100644 --- a/features/Skylighting/Shaders/Skylighting/Skylighting.hlsli +++ b/features/Skylighting/Shaders/Skylighting/Skylighting.hlsli @@ -160,7 +160,10 @@ namespace Skylighting if (SharedData::InInterior) return 1.0; - float3 biasedNormal = normalize(float3(evalNormal.xy, max(0.0, evalNormal.z))); + float3 candidateNormal = float3(evalNormal.xy, max(0.0, evalNormal.z)); + float3 biasedNormal = dot(candidateNormal, candidateNormal) > 1e-6 + ? normalize(candidateNormal) + : float3(0, 0, 1); float fadeOutFactor = GetFadeOutFactor(positionMS); float skylightingDiffuse = EvaluateDiffuse(skylightingSH, biasedNormal, fadeOutFactor); @@ -196,8 +199,6 @@ namespace Skylighting if (any(cellID < 0) || any((uint3)cellID >= ARRAY_DIM)) continue; - float3 cellCentreMS = (cellID + 0.5 - ARRAY_DIM / 2) * CELL_SIZE; - float3 trilinearWeights = 1 - abs(offset - trilinearPos); float w = trilinearWeights.x * trilinearWeights.y * trilinearWeights.z; From f92a98834339f4891135826fb2c9e029d7f63ed0 Mon Sep 17 00:00:00 2001 From: doodlum <15017472+doodlum@users.noreply.github.com> Date: Wed, 10 Jun 2026 23:55:36 +0100 Subject: [PATCH 06/20] fix(skylighting): use R8_UNORM for shadow visibility texture Halves VRAM for the shadow visibility volume (values are [0,1]). Co-Authored-By: Claude Opus 4.6 --- src/Features/Skylighting.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Features/Skylighting.cpp b/src/Features/Skylighting.cpp index 9ee15c9377..daf5a881bd 100644 --- a/src/Features/Skylighting.cpp +++ b/src/Features/Skylighting.cpp @@ -126,7 +126,7 @@ void Skylighting::SetupResources() texShadowBitmask->CreateSRV(srvDesc); texShadowBitmask->CreateUAV(uavDesc); - texDesc.Format = srvDesc.Format = uavDesc.Format = DXGI_FORMAT_R16_FLOAT; + texDesc.Format = srvDesc.Format = uavDesc.Format = DXGI_FORMAT_R8_UNORM; texShadowVisibility = new Texture3D(texDesc, "Skylighting::ShadowVisibility"); texShadowVisibility->CreateSRV(srvDesc); From 2b909c2f59713b8ed2f8b519974109d474d8efb1 Mon Sep 17 00:00:00 2001 From: doodlum <15017472+doodlum@users.noreply.github.com> Date: Wed, 10 Jun 2026 23:56:36 +0100 Subject: [PATCH 07/20] fix(skylighting): correct GetShadowDepth call to single parameter Co-Authored-By: Claude Opus 4.6 --- features/Skylighting/Shaders/Skylighting/UpdateProbesCS.hlsl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/Skylighting/Shaders/Skylighting/UpdateProbesCS.hlsl b/features/Skylighting/Shaders/Skylighting/UpdateProbesCS.hlsl index 7c4edbefad..e5ead5de9c 100644 --- a/features/Skylighting/Shaders/Skylighting/UpdateProbesCS.hlsl +++ b/features/Skylighting/Shaders/Skylighting/UpdateProbesCS.hlsl @@ -98,7 +98,7 @@ static const float3 noise3D[32] = { uint bitIndex = (accumFrames - 1) % 32; float3 jitteredMS = cellCentreMS + noise3D[bitIndex] * Skylighting::CELL_SIZE; - float ndcDepth = FrameBuffer::GetShadowDepth(jitteredMS, 0); + float ndcDepth = FrameBuffer::GetShadowDepth(jitteredMS); float linearDepth = SharedData::GetScreenDepth(ndcDepth); if (linearDepth > 0 && linearDepth < shadowData.EndSplitDistances.y) { From 192aa0dfaf6bcfc6e9f55908b52c3205bb251d88 Mon Sep 17 00:00:00 2001 From: doodlum <15017472+doodlum@users.noreply.github.com> Date: Thu, 11 Jun 2026 00:00:19 +0100 Subject: [PATCH 08/20] fix(skylighting): CameraPosAdjust is no longer an array post-VR removal Co-Authored-By: Claude Opus 4.6 --- features/Skylighting/Shaders/Skylighting/UpdateProbesCS.hlsl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/Skylighting/Shaders/Skylighting/UpdateProbesCS.hlsl b/features/Skylighting/Shaders/Skylighting/UpdateProbesCS.hlsl index e5ead5de9c..a05324b85e 100644 --- a/features/Skylighting/Shaders/Skylighting/UpdateProbesCS.hlsl +++ b/features/Skylighting/Shaders/Skylighting/UpdateProbesCS.hlsl @@ -102,7 +102,7 @@ static const float3 noise3D[32] = { float linearDepth = SharedData::GetScreenDepth(ndcDepth); if (linearDepth > 0 && linearDepth < shadowData.EndSplitDistances.y) { - float3 positionWS = jitteredMS + FrameBuffer::CameraPosAdjust[0].xyz; + float3 positionWS = jitteredMS + FrameBuffer::CameraPosAdjust.xyz; uint cascadeIndex = (linearDepth > shadowData.EndSplitDistances.x) ? 1u : 0u; From 7c5af30640c900eb0d7ab9ba64fbaf2c0f7dfa11 Mon Sep 17 00:00:00 2001 From: doodlum <15017472+doodlum@users.noreply.github.com> Date: Thu, 11 Jun 2026 00:06:38 +0100 Subject: [PATCH 09/20] feat(skylighting): apply shadow visibility via dirSoftShadow replacement Replace dirSoftShadow with skylightingShadowVisibility when SKYLIGHTING_SHADOW_VIS is defined, so probe shadow visibility flows naturally through the existing softShadow path to drive rim, soft, back, and coat lighting. Co-Authored-By: Claude Opus 4.6 --- extern/CommonLibSSE-NG | 2 +- package/Shaders/Lighting.hlsl | 10 ++++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/extern/CommonLibSSE-NG b/extern/CommonLibSSE-NG index 8f4205da56..8c4025b01f 160000 --- a/extern/CommonLibSSE-NG +++ b/extern/CommonLibSSE-NG @@ -1 +1 @@ -Subproject commit 8f4205da56f01cbe98557422c008a5719c180fb9 +Subproject commit 8c4025b01fac2bea1bbe73a3a9da7b4fde338343 diff --git a/package/Shaders/Lighting.hlsl b/package/Shaders/Lighting.hlsl index f165453341..5deea1a3d2 100644 --- a/package/Shaders/Lighting.hlsl +++ b/package/Shaders/Lighting.hlsl @@ -2276,15 +2276,9 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) # endif # if (defined(RIM_LIGHTING) || defined(SOFT_LIGHTING) || defined(LOAD_SOFT_LIGHTING)) material.rimSoftLightColor = rimSoftLightColor.xyz; -# if defined(SKYLIGHTING_SHADOW_VIS) - material.rimSoftLightColor *= skylightingShadowVisibility; -# endif # endif # if defined(BACK_LIGHTING) material.backLightColor = backLightColor.xyz; -# if defined(SKYLIGHTING_SHADOW_VIS) - material.backLightColor *= skylightingShadowVisibility; -# endif # endif # endif // TRUE_PBR @@ -2645,6 +2639,10 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) } # endif +# if defined(SKYLIGHTING_SHADOW_VIS) + dirSoftShadow = skylightingShadowVisibility; +# endif + float3 diffuseColor = 0.0.xxx; float3 specularColor = 0.0.xxx; float3 transmissionColor = 0.0.xxx; From b82cc26dcf9e529836a0719dfa1c114bbea216ee Mon Sep 17 00:00:00 2001 From: doodlum <15017472+doodlum@users.noreply.github.com> Date: Thu, 11 Jun 2026 00:09:22 +0100 Subject: [PATCH 10/20] fix(skylighting): revert unintended CommonLibSSE-NG submodule change Co-Authored-By: Claude Opus 4.6 --- extern/CommonLibSSE-NG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extern/CommonLibSSE-NG b/extern/CommonLibSSE-NG index 8c4025b01f..8f4205da56 160000 --- a/extern/CommonLibSSE-NG +++ b/extern/CommonLibSSE-NG @@ -1 +1 @@ -Subproject commit 8c4025b01fac2bea1bbe73a3a9da7b4fde338343 +Subproject commit 8f4205da56f01cbe98557422c008a5719c180fb9 From e1fdb61c295deb1e01aa587ce5bbf43173aaebfb Mon Sep 17 00:00:00 2001 From: doodlum <15017472+doodlum@users.noreply.github.com> Date: Thu, 11 Jun 2026 00:44:39 +0100 Subject: [PATCH 11/20] fix(skylighting): define SKYLIGHTING_SHADOW_VIS before DynamicCubemaps include DynamicCubemaps.hlsli transitively includes Skylighting.hlsli, and the include guard prevented re-inclusion when SKYLIGHTING_SHADOW_VIS was defined later. Move the define before all feature includes so the 4-param Sample overload is compiled. Co-Authored-By: Claude Opus 4.6 --- package/Shaders/Lighting.hlsl | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/package/Shaders/Lighting.hlsl b/package/Shaders/Lighting.hlsl index 5deea1a3d2..c72bd41983 100644 --- a/package/Shaders/Lighting.hlsl +++ b/package/Shaders/Lighting.hlsl @@ -786,6 +786,12 @@ float GetSnowParameterY(float texProjTmp, float alpha) # undef SKYLIGHTING # endif +# if defined(SKYLIGHTING) +# if defined(RIM_LIGHTING) || defined(SOFT_LIGHTING) || defined(LOAD_SOFT_LIGHTING) || defined(BACK_LIGHTING) +# define SKYLIGHTING_SHADOW_VIS +# endif +# endif + # include "Common/LightingCommon.hlsli" # if defined(WATER_EFFECTS) @@ -845,9 +851,6 @@ float GetSnowParameterY(float texProjTmp, float alpha) # endif # if defined(SKYLIGHTING) -# if defined(RIM_LIGHTING) || defined(SOFT_LIGHTING) || defined(LOAD_SOFT_LIGHTING) || defined(BACK_LIGHTING) -# define SKYLIGHTING_SHADOW_VIS -# endif # include "Skylighting/Skylighting.hlsli" # endif From b3c48ef42ceb087b3b5fffc09ab2f4b9aa735a70 Mon Sep 17 00:00:00 2001 From: doodlum <15017472+doodlum@users.noreply.github.com> Date: Thu, 11 Jun 2026 18:15:32 +0100 Subject: [PATCH 12/20] refactor(skylighting): optimise dirSoftShadow path and clean up Sample - Skip VSM dirSoftShadow assignment when SKYLIGHTING_SHADOW_VIS handles it - Gate VSM fallback (dirVSMDetailedShadow) on !DEFERRED - Remove per-pixel pcg3d jitter blur from Sample - Remove screenPosition parameter from Sample and all call sites - Remove HasDirectionalShadows/InInterior guard from UpdateProbesCS - Restore original comments (Receiver normal bias, irradiance probes URL) Co-Authored-By: Claude Opus 4.6 --- .../Shaders/Skylighting/Skylighting.hlsli | 12 +--- .../Shaders/Skylighting/UpdateProbesCS.hlsl | 62 ++++++++----------- package/Shaders/DeferredCompositeCS.hlsl | 4 +- package/Shaders/Lighting.hlsl | 18 ++++-- package/Shaders/RunGrass.hlsl | 4 +- 5 files changed, 46 insertions(+), 54 deletions(-) diff --git a/features/Skylighting/Shaders/Skylighting/Skylighting.hlsli b/features/Skylighting/Shaders/Skylighting/Skylighting.hlsli index 5ebc840fbd..601494766f 100644 --- a/features/Skylighting/Shaders/Skylighting/Skylighting.hlsli +++ b/features/Skylighting/Shaders/Skylighting/Skylighting.hlsli @@ -2,7 +2,6 @@ #define __SKYLIGHTING_DEPENDENCY_HLSL__ #include "Common/Math.hlsli" -#include "Common/Random.hlsli" #include "Common/Shading.hlsli" #include "Common/SharedData.hlsli" #include "Common/Spherical Harmonics/SphericalHarmonics.hlsli" @@ -80,7 +79,7 @@ namespace Skylighting #endif #if defined(PSHADER) || defined(SKYLIGHTING_PROBE_REGISTER) - sh2 Sample(float3 positionMS, float3 normalWS, float2 screenPosition + sh2 Sample(float3 positionMS, float3 normalWS #if defined(SKYLIGHTING_SHADOW_VIS) , out float shadowVisibility #endif @@ -95,12 +94,7 @@ namespace Skylighting if (SharedData::InInterior) return scaledUnitSH; - positionMS.xyz += normalWS * CELL_SIZE * 0.5; - - if (SharedData::FrameCount) { - float3 offset = float3(Random::pcg3d(uint3(screenPosition.xy, SharedData::FrameCount))) / 4294967295.0 * 2.0 - 1.0; - positionMS.xyz += offset * CELL_SIZE * 0.5; - } + positionMS.xyz += normalWS * CELL_SIZE * 0.5; // Receiver normal bias float3 positionMSAdjusted = positionMS - SharedData::skylightingSettings.PosOffset.xyz; float3 uvw = positionMSAdjusted / ARRAY_SIZE + .5; @@ -135,7 +129,7 @@ namespace Skylighting uint3 cellTexID = (cellID + SharedData::skylightingSettings.ArrayOrigin.xyz) % ARRAY_DIM; - // SH probe: tangent-weighted interpolation + // https://handmade.network/p/75/monter/blog/p/7288-engine_work__global_illumination_with_irradiance_probes float tangentWeight = dot(normalize(cellCentreMS - positionMSAdjusted), normalWS) * 0.5 + 0.5; float shW = triW * tangentWeight; shSum = SphericalHarmonics::Add(shSum, SphericalHarmonics::Scale(SkylightingProbeArray[cellTexID], shW)); diff --git a/features/Skylighting/Shaders/Skylighting/UpdateProbesCS.hlsl b/features/Skylighting/Shaders/Skylighting/UpdateProbesCS.hlsl index a05324b85e..0764508136 100644 --- a/features/Skylighting/Shaders/Skylighting/UpdateProbesCS.hlsl +++ b/features/Skylighting/Shaders/Skylighting/UpdateProbesCS.hlsl @@ -88,52 +88,44 @@ static const float3 noise3D[32] = { outProbeArray[dtid] = occlusionSH; outAccumFramesArray[dtid] = accumFrames; - // Shadow cascade sampling with bitmask accumulation and Gaussian spatial blur + // Shadow cascade sampling with bitmask accumulation { float shadowSample = 1.0; + DirectionalShadowLightData shadowData = DirectionalShadowLights[0]; - if (SharedData::HasDirectionalShadows && !SharedData::InInterior) { - DirectionalShadowLightData shadowData = DirectionalShadowLights[0]; + uint bitIndex = (accumFrames - 1) % 32; + float3 jitteredMS = cellCentreMS + noise3D[bitIndex] * Skylighting::CELL_SIZE; - uint bitIndex = (accumFrames - 1) % 32; - float3 jitteredMS = cellCentreMS + noise3D[bitIndex] * Skylighting::CELL_SIZE; + float ndcDepth = FrameBuffer::GetShadowDepth(jitteredMS); + float linearDepth = SharedData::GetScreenDepth(ndcDepth); - float ndcDepth = FrameBuffer::GetShadowDepth(jitteredMS); - float linearDepth = SharedData::GetScreenDepth(ndcDepth); + if (linearDepth > 0 && linearDepth < shadowData.EndSplitDistances.y) { + float3 positionWS = jitteredMS + FrameBuffer::CameraPosAdjust.xyz; - if (linearDepth > 0 && linearDepth < shadowData.EndSplitDistances.y) { - float3 positionWS = jitteredMS + FrameBuffer::CameraPosAdjust.xyz; + uint cascadeIndex = (linearDepth > shadowData.EndSplitDistances.x) ? 1u : 0u; - uint cascadeIndex = (linearDepth > shadowData.EndSplitDistances.x) ? 1u : 0u; + float3 positionLS = mul(shadowData.ShadowProj[cascadeIndex], float4(positionWS, 1)).xyz; - float3 positionLS = mul(shadowData.ShadowProj[cascadeIndex], float4(positionWS, 1)).xyz; - - if (all(positionLS.xy >= 0) && all(positionLS.xy <= 1)) { - shadowSample = ShadowCascadeMap.SampleCmpLevelZero(comparisonSampler, float3(positionLS.xy, cascadeIndex), positionLS.z); - } - - float fade = saturate(linearDepth / shadowData.EndSplitDistances.y); - float fadeFactor = 1.0 - pow(fade * fade, 8); - shadowSample = lerp(1.0, shadowSample, fadeFactor); + if (all(positionLS.xy >= 0) && all(positionLS.xy <= 1)) { + shadowSample = ShadowCascadeMap.SampleCmpLevelZero(comparisonSampler, float3(positionLS.xy, cascadeIndex), positionLS.z); } - uint bitmask = isValid ? outShadowBitmask[dtid] : 0; - bitmask &= ~(1u << bitIndex); - if (shadowSample > 0.5) - bitmask |= (1u << bitIndex); - - outShadowBitmask[dtid] = bitmask; - - uint validBits = min(accumFrames, 32u); - float shadow = float(countbits(bitmask)) / float(validBits); - shadow = lerp(1.0, shadow, min(fadeInThreshold, accumFrames) / fadeInThreshold); - outShadowVisibility[dtid] = shadow; - } else { - if (!isValid) { - outShadowBitmask[dtid] = 0; - outShadowVisibility[dtid] = 1.0; - } + float fade = saturate(linearDepth / shadowData.EndSplitDistances.y); + float fadeFactor = 1.0 - pow(fade * fade, 8); + shadowSample = lerp(1.0, shadowSample, fadeFactor); } + + uint bitmask = isValid ? outShadowBitmask[dtid] : 0; + bitmask &= ~(1u << bitIndex); + if (shadowSample > 0.5) + bitmask |= (1u << bitIndex); + + outShadowBitmask[dtid] = bitmask; + + uint validBits = min(accumFrames, 32u); + float shadow = float(countbits(bitmask)) / float(validBits); + shadow = lerp(1.0, shadow, min(fadeInThreshold, accumFrames) / fadeInThreshold); + outShadowVisibility[dtid] = shadow; } } else if (!isValid) { outProbeArray[dtid] = unitSH; diff --git a/package/Shaders/DeferredCompositeCS.hlsl b/package/Shaders/DeferredCompositeCS.hlsl index 4ce3ec0ccf..9075bfc0ba 100644 --- a/package/Shaders/DeferredCompositeCS.hlsl +++ b/package/Shaders/DeferredCompositeCS.hlsl @@ -136,7 +136,7 @@ void SampleSSGISpecular(uint2 pixCoord, sh2 lobe, inout float ao, out float3 il, # if defined(SKYLIGHTING) float3 positionMS = positionWS.xyz; - sh2 skylightingSH = Skylighting::Sample(positionMS.xyz, normalWS, float2(dispatchID.xy)); + sh2 skylightingSH = Skylighting::Sample(positionMS.xyz, normalWS); float skylightingDiffuse = Skylighting::EvaluateDiffuse(skylightingSH, normalWS); directionalAmbientColor = ImageBasedLighting::GetDiffuseIBLOccluded(vanillaDALC, -normalWS, skylightingDiffuse) * albedo; # else @@ -202,7 +202,7 @@ void SampleSSGISpecular(uint2 pixCoord, sh2 lobe, inout float ao, out float3 il, # if defined(SKYLIGHTING) float3 positionMS = positionWS.xyz; - sh2 skylightingSH = Skylighting::Sample(positionMS.xyz, R, float2(dispatchID.xy)); + sh2 skylightingSH = Skylighting::Sample(positionMS.xyz, R); float skylightingSpecular = Skylighting::EvaluateSpecular(skylightingSH, specularLobe); # endif diff --git a/package/Shaders/Lighting.hlsl b/package/Shaders/Lighting.hlsl index c72bd41983..6da8e99f8b 100644 --- a/package/Shaders/Lighting.hlsl +++ b/package/Shaders/Lighting.hlsl @@ -2418,13 +2418,13 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) float skylightingShadowVisibility = 1.0; # endif # if defined(DEFERRED) - sh2 skylightingSH = Skylighting::Sample(positionMSSkylight, worldNormal, input.Position.xy + sh2 skylightingSH = Skylighting::Sample(positionMSSkylight, worldNormal # if defined(SKYLIGHTING_SHADOW_VIS) , skylightingShadowVisibility # endif ); # else - sh2 skylightingSH = inWorld ? Skylighting::Sample(positionMSSkylight, worldNormal, input.Position.xy + sh2 skylightingSH = inWorld ? Skylighting::Sample(positionMSSkylight, worldNormal # if defined(SKYLIGHTING_SHADOW_VIS) , skylightingShadowVisibility # endif @@ -2577,9 +2577,12 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) float dirSoftShadow = 1.0; float dirVSMDetailedShadow = 1.0; -# if defined(VOLUMETRIC_SHADOWS) +# if defined(VOLUMETRIC_SHADOWS) && !(defined(DEFERRED) && defined(SKYLIGHTING_SHADOW_VIS)) if (inWorld && !inReflection && ShadowSampling::HasDirectionalShadows()) - dirSoftShadow = ShadowSampling::GetLightingShadow(input.WorldPosition.xyz, dirVSMDetailedShadow); +# if !defined(SKYLIGHTING_SHADOW_VIS) + dirSoftShadow = +# endif + ShadowSampling::GetLightingShadow(input.WorldPosition.xyz, dirVSMDetailedShadow); # endif float dirDetailedShadow = 1.0; @@ -2587,12 +2590,15 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) if ((Permutation::PixelShaderDescriptor & Permutation::LightingFlags::DefShadow) && (Permutation::PixelShaderDescriptor & Permutation::LightingFlags::ShadowDir)) { dirDetailedShadow *= shadowColor.x; -# if !defined(VOLUMETRIC_SHADOWS) +# if !defined(VOLUMETRIC_SHADOWS) && !defined(SKYLIGHTING_SHADOW_VIS) dirSoftShadow = dirDetailedShadow; # endif - } else { + } +# if !defined(DEFERRED) + else { dirDetailedShadow = dirVSMDetailedShadow; } +# endif # if defined(SCREEN_SPACE_SHADOWS) && defined(DEFERRED) if (!SharedData::InInterior && dirLightAngle >= 0.0) diff --git a/package/Shaders/RunGrass.hlsl b/package/Shaders/RunGrass.hlsl index 77c042a415..7220729536 100644 --- a/package/Shaders/RunGrass.hlsl +++ b/package/Shaders/RunGrass.hlsl @@ -559,7 +559,7 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) # if defined(SKYLIGHTING) float3 positionMSSkylight = input.WorldPosition.xyz; - sh2 skylightingSH = Skylighting::Sample(positionMSSkylight, normal, input.HPosition.xy); + sh2 skylightingSH = Skylighting::Sample(positionMSSkylight, normal); float skylightingDiffuse = Skylighting::GetSkylightingDiffuse(skylightingSH, positionMSSkylight, normal, vertexAO); # endif // SKYLIGHTING @@ -821,7 +821,7 @@ PS_OUTPUT main(PS_INPUT input) # if defined(SKYLIGHTING) float3 positionMSSkylight = input.WorldPosition.xyz; - sh2 skylightingSH = Skylighting::Sample(positionMSSkylight, normal, input.HPosition.xy); + sh2 skylightingSH = Skylighting::Sample(positionMSSkylight, normal); float skylightingDiffuse = Skylighting::GetSkylightingDiffuse(skylightingSH, positionMSSkylight, normal, vertexAO); # endif // SKYLIGHTING From 67843d5bd67b195a54849148aa05a694f44fc1d2 Mon Sep 17 00:00:00 2001 From: doodlum <15017472+doodlum@users.noreply.github.com> Date: Thu, 11 Jun 2026 18:15:58 +0100 Subject: [PATCH 13/20] fix(skylighting): restore basic tangent checks comment Co-Authored-By: Claude Opus 4.6 --- features/Skylighting/Shaders/Skylighting/Skylighting.hlsli | 1 + 1 file changed, 1 insertion(+) diff --git a/features/Skylighting/Shaders/Skylighting/Skylighting.hlsli b/features/Skylighting/Shaders/Skylighting/Skylighting.hlsli index 601494766f..3c9af9b90e 100644 --- a/features/Skylighting/Shaders/Skylighting/Skylighting.hlsli +++ b/features/Skylighting/Shaders/Skylighting/Skylighting.hlsli @@ -130,6 +130,7 @@ namespace Skylighting uint3 cellTexID = (cellID + SharedData::skylightingSettings.ArrayOrigin.xyz) % ARRAY_DIM; // https://handmade.network/p/75/monter/blog/p/7288-engine_work__global_illumination_with_irradiance_probes + // basic tangent checks float tangentWeight = dot(normalize(cellCentreMS - positionMSAdjusted), normalWS) * 0.5 + 0.5; float shW = triW * tangentWeight; shSum = SphericalHarmonics::Add(shSum, SphericalHarmonics::Scale(SkylightingProbeArray[cellTexID], shW)); From e257767b72b696f2fa9bec33c25077860f965e28 Mon Sep 17 00:00:00 2001 From: doodlum <15017472+doodlum@users.noreply.github.com> Date: Thu, 11 Jun 2026 18:27:57 +0100 Subject: [PATCH 14/20] fix(skylighting): prevent division by zero on accumFrames wrap accumFrames is R8_UINT (0-255). On wrap to 0, bitIndex underflows and validBits becomes 0 causing NaN. Clamp shadow frame count with max(accumFrames, 1u) for the bitmask logic. Co-Authored-By: Claude Opus 4.6 --- .../Skylighting/Shaders/Skylighting/UpdateProbesCS.hlsl | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/features/Skylighting/Shaders/Skylighting/UpdateProbesCS.hlsl b/features/Skylighting/Shaders/Skylighting/UpdateProbesCS.hlsl index 0764508136..9a07594db6 100644 --- a/features/Skylighting/Shaders/Skylighting/UpdateProbesCS.hlsl +++ b/features/Skylighting/Shaders/Skylighting/UpdateProbesCS.hlsl @@ -93,7 +93,8 @@ static const float3 noise3D[32] = { float shadowSample = 1.0; DirectionalShadowLightData shadowData = DirectionalShadowLights[0]; - uint bitIndex = (accumFrames - 1) % 32; + uint shadowFrames = max(accumFrames, 1u); + uint bitIndex = (shadowFrames - 1) % 32; float3 jitteredMS = cellCentreMS + noise3D[bitIndex] * Skylighting::CELL_SIZE; float ndcDepth = FrameBuffer::GetShadowDepth(jitteredMS); @@ -122,9 +123,9 @@ static const float3 noise3D[32] = { outShadowBitmask[dtid] = bitmask; - uint validBits = min(accumFrames, 32u); + uint validBits = min(shadowFrames, 32u); float shadow = float(countbits(bitmask)) / float(validBits); - shadow = lerp(1.0, shadow, min(fadeInThreshold, accumFrames) / fadeInThreshold); + shadow = lerp(1.0, shadow, min(fadeInThreshold, shadowFrames) / fadeInThreshold); outShadowVisibility[dtid] = shadow; } } else if (!isValid) { From 848525bb80a0030c1d856d39bc1489d730083315 Mon Sep 17 00:00:00 2001 From: doodlum <15017472+doodlum@users.noreply.github.com> Date: Fri, 12 Jun 2026 17:42:08 +0100 Subject: [PATCH 15/20] fix(skylighting): tune shadow cascade sampling - Use exact comparison (== 1.0) for shadow bitmask - Apply sqrt to shadow visibility output - Remove confidence fade from shadow calculation - Remove dead LOAD_SOFT_LIGHTING from SKYLIGHTING_SHADOW_VIS gate Co-Authored-By: Claude Opus 4.6 --- .../Skylighting/Shaders/Skylighting/UpdateProbesCS.hlsl | 8 +++----- package/Shaders/Lighting.hlsl | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/features/Skylighting/Shaders/Skylighting/UpdateProbesCS.hlsl b/features/Skylighting/Shaders/Skylighting/UpdateProbesCS.hlsl index 9a07594db6..32de764e29 100644 --- a/features/Skylighting/Shaders/Skylighting/UpdateProbesCS.hlsl +++ b/features/Skylighting/Shaders/Skylighting/UpdateProbesCS.hlsl @@ -107,9 +107,8 @@ static const float3 noise3D[32] = { float3 positionLS = mul(shadowData.ShadowProj[cascadeIndex], float4(positionWS, 1)).xyz; - if (all(positionLS.xy >= 0) && all(positionLS.xy <= 1)) { + if (all(positionLS.xy >= 0) && all(positionLS.xy <= 1)) shadowSample = ShadowCascadeMap.SampleCmpLevelZero(comparisonSampler, float3(positionLS.xy, cascadeIndex), positionLS.z); - } float fade = saturate(linearDepth / shadowData.EndSplitDistances.y); float fadeFactor = 1.0 - pow(fade * fade, 8); @@ -118,15 +117,14 @@ static const float3 noise3D[32] = { uint bitmask = isValid ? outShadowBitmask[dtid] : 0; bitmask &= ~(1u << bitIndex); - if (shadowSample > 0.5) + if (shadowSample == 1.0) bitmask |= (1u << bitIndex); outShadowBitmask[dtid] = bitmask; uint validBits = min(shadowFrames, 32u); float shadow = float(countbits(bitmask)) / float(validBits); - shadow = lerp(1.0, shadow, min(fadeInThreshold, shadowFrames) / fadeInThreshold); - outShadowVisibility[dtid] = shadow; + outShadowVisibility[dtid] = sqrt(shadow); } } else if (!isValid) { outProbeArray[dtid] = unitSH; diff --git a/package/Shaders/Lighting.hlsl b/package/Shaders/Lighting.hlsl index 6da8e99f8b..6d83a959e2 100644 --- a/package/Shaders/Lighting.hlsl +++ b/package/Shaders/Lighting.hlsl @@ -787,7 +787,7 @@ float GetSnowParameterY(float texProjTmp, float alpha) # endif # if defined(SKYLIGHTING) -# if defined(RIM_LIGHTING) || defined(SOFT_LIGHTING) || defined(LOAD_SOFT_LIGHTING) || defined(BACK_LIGHTING) +# if defined(RIM_LIGHTING) || defined(SOFT_LIGHTING) || defined(BACK_LIGHTING) # define SKYLIGHTING_SHADOW_VIS # endif # endif From 325eae0c713d105ae896d7bb5cf2abdc2761d844 Mon Sep 17 00:00:00 2001 From: doodlum <15017472+doodlum@users.noreply.github.com> Date: Fri, 12 Jun 2026 18:14:45 +0100 Subject: [PATCH 16/20] feat(skylighting): add soft lighting ambient and eSRAM shadow sampling - Add directional ambient soft lighting before ApplySkylighting - Sample eSRAM shadow map alongside cascade map using min() - Remove dead LOAD_SOFT_LIGHTING define from all files Co-Authored-By: Claude Opus 4.6 --- .../Shaders/Skylighting/UpdateProbesCS.hlsl | 8 +++- package/Shaders/Common/LightingCommon.hlsli | 2 +- package/Shaders/Lighting.hlsl | 44 +++++++++---------- src/Features/Skylighting.cpp | 8 +++- 4 files changed, 34 insertions(+), 28 deletions(-) diff --git a/features/Skylighting/Shaders/Skylighting/UpdateProbesCS.hlsl b/features/Skylighting/Shaders/Skylighting/UpdateProbesCS.hlsl index 32de764e29..1873afbdc9 100644 --- a/features/Skylighting/Shaders/Skylighting/UpdateProbesCS.hlsl +++ b/features/Skylighting/Shaders/Skylighting/UpdateProbesCS.hlsl @@ -4,6 +4,7 @@ Texture2D srcOcclusionDepth : register(t0); Texture2DArray ShadowCascadeMap : register(t1); +Texture2DArray ESRAMShadow : register(t3); struct DirectionalShadowLightData { @@ -107,8 +108,11 @@ static const float3 noise3D[32] = { float3 positionLS = mul(shadowData.ShadowProj[cascadeIndex], float4(positionWS, 1)).xyz; - if (all(positionLS.xy >= 0) && all(positionLS.xy <= 1)) - shadowSample = ShadowCascadeMap.SampleCmpLevelZero(comparisonSampler, float3(positionLS.xy, cascadeIndex), positionLS.z); + if (all(positionLS.xy >= 0) && all(positionLS.xy <= 1)) { + float cascadeShadow = ShadowCascadeMap.SampleCmpLevelZero(comparisonSampler, float3(positionLS.xy, cascadeIndex), positionLS.z); + float esramShadow = ESRAMShadow.SampleCmpLevelZero(comparisonSampler, float3(positionLS.xy, cascadeIndex), positionLS.z); + shadowSample = min(cascadeShadow, esramShadow); + } float fade = saturate(linearDepth / shadowData.EndSplitDistances.y); float fadeFactor = 1.0 - pow(fade * fade, 8); diff --git a/package/Shaders/Common/LightingCommon.hlsli b/package/Shaders/Common/LightingCommon.hlsli index aa50b02c95..b464a34ee4 100644 --- a/package/Shaders/Common/LightingCommon.hlsli +++ b/package/Shaders/Common/LightingCommon.hlsli @@ -63,7 +63,7 @@ struct MaterialProperties float Shininess; float Glossiness; float3 SpecularColor; -# if (defined(RIM_LIGHTING) || defined(SOFT_LIGHTING) || defined(LOAD_SOFT_LIGHTING)) +# if (defined(RIM_LIGHTING) || defined(SOFT_LIGHTING)) float3 rimSoftLightColor; # endif # if defined(BACK_LIGHTING) diff --git a/package/Shaders/Lighting.hlsl b/package/Shaders/Lighting.hlsl index 6d83a959e2..76d2f569e6 100644 --- a/package/Shaders/Lighting.hlsl +++ b/package/Shaders/Lighting.hlsl @@ -1972,7 +1972,7 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) # endif # endif // BACK_LIGHTING -# if (defined(RIM_LIGHTING) || defined(SOFT_LIGHTING) || defined(LOAD_SOFT_LIGHTING)) +# if (defined(RIM_LIGHTING) || defined(SOFT_LIGHTING)) float4 rimSoftLightColor = TexRimSoftLightWorldMapOverlaySampler.Sample(SampRimSoftLightWorldMapOverlaySampler, uv); # endif // RIM_LIGHTING || SOFT_LIGHTING @@ -2277,7 +2277,7 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) material.Glossiness = 0; material.SpecularColor = 0; # endif -# if (defined(RIM_LIGHTING) || defined(SOFT_LIGHTING) || defined(LOAD_SOFT_LIGHTING)) +# if (defined(RIM_LIGHTING) || defined(SOFT_LIGHTING)) material.rimSoftLightColor = rimSoftLightColor.xyz; # endif # if defined(BACK_LIGHTING) @@ -2938,6 +2938,23 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) IndirectContext indirectContext = (IndirectContext)0; IndirectLobeWeights indirectLobeWeights; +# if defined(MULTI_LAYER_PARALLAX) + float layerValue = MultiLayerParallaxData.x * TexLayerSampler.Sample(SampLayerSampler, uv).w; + float3 tangentViewDirection = mul(viewDirection, tbn); + float3 layerNormal = MultiLayerParallaxData.yyy * (normalColor.xyz * 2.0.xxx + float3(-1, -1, -2)) + float3(0, 0, 1); + float layerViewAngle = dot(-tangentViewDirection.xyz, layerNormal.xyz) * 2; + float3 layerViewProjection = -layerNormal.xyz * layerViewAngle.xxx - tangentViewDirection.xyz; + float2 layerUv = uv * MultiLayerParallaxData.zw + (0.0009765625 * (layerValue / abs(layerViewProjection.z))).xx * layerViewProjection.xy; + + float3 layerColor = TexLayerSampler.Sample(SampLayerSampler, layerUv).xyz; + + float mlpBlendFactor = saturate(viewNormalAngle) * (1.0 - baseColor.w); + + material.BaseColor = lerp(material.BaseColor, layerColor, mlpBlendFactor); + + indirectLobeWeights.diffuse *= 1.0 - mlpBlendFactor; +# endif // MULTI_LAYER_PARALLAX + float3 ambientNormal = worldNormal.xyz; # if defined(HAIR) && defined(CS_HAIR) if (SharedData::hairSpecularSettings.Enabled) { @@ -3087,27 +3104,6 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) color.xyz *= vertexColor; -# if defined(MULTI_LAYER_PARALLAX) - float layerValue = MultiLayerParallaxData.x * TexLayerSampler.Sample(SampLayerSampler, uv).w; - float3 tangentViewDirection = mul(viewDirection, tbn); - float3 layerNormal = MultiLayerParallaxData.yyy * (normalColor.xyz * 2.0.xxx + float3(-1, -1, -2)) + float3(0, 0, 1); - float layerViewAngle = dot(-tangentViewDirection.xyz, layerNormal.xyz) * 2; - float3 layerViewProjection = -layerNormal.xyz * layerViewAngle.xxx - tangentViewDirection.xyz; - float2 layerUv = uv * MultiLayerParallaxData.zw + (0.0009765625 * (layerValue / abs(layerViewProjection.z))).xx * layerViewProjection.xy; - - float3 layerColor = TexLayerSampler.Sample(SampLayerSampler, layerUv).xyz; - - float mlpBlendFactor = saturate(viewNormalAngle) * (1.0 - baseColor.w); - -# if defined(SKYLIGHTING) - color.xyz = lerp(color.xyz, (diffuseColor + directionalAmbientColor * skylightingDiffuse) * vertexColor * layerColor, mlpBlendFactor); -# else - color.xyz = lerp(color.xyz, (diffuseColor + directionalAmbientColor) * vertexColor * layerColor, mlpBlendFactor); -# endif - - indirectLobeWeights.diffuse *= 1.0 - mlpBlendFactor; -# endif // MULTI_LAYER_PARALLAX - # if defined(SNOW) if (useSnowSpecular) specularColor = 0; @@ -3232,6 +3228,8 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) color.xyz = 0; # endif + color.xyz = dirSoftShadow; + # if defined(LANDSCAPE) && !defined(LOD_LAND_BLEND) psout.Diffuse.w = 0; # else diff --git a/src/Features/Skylighting.cpp b/src/Features/Skylighting.cpp index daf5a881bd..ab4a333432 100644 --- a/src/Features/Skylighting.cpp +++ b/src/Features/Skylighting.cpp @@ -238,10 +238,14 @@ void Skylighting::Prepass() auto context = globals::d3d::context; { - std::array srvs = { + auto renderer = globals::game::renderer; + auto& esramDepthStencil = renderer->GetDepthStencilData().depthStencils[RE::RENDER_TARGETS_DEPTHSTENCIL::kVOLUMETRIC_LIGHTING_SHADOWMAPS_ESRAM]; + + std::array srvs = { texOcclusion->srv.get(), shadowCascadeSRV ? shadowCascadeSRV : nullptr, - shadowCascadeSRV ? globals::deferred->directionalShadowLights->srv.get() : nullptr + shadowCascadeSRV ? globals::deferred->directionalShadowLights->srv.get() : nullptr, + shadowCascadeSRV ? esramDepthStencil.depthSRV : nullptr }; std::array uavs = { texProbeArray->uav.get(), From def09bf3a9f56545014df90fc7c528e5872f2cbf Mon Sep 17 00:00:00 2001 From: doodlum <15017472+doodlum@users.noreply.github.com> Date: Fri, 12 Jun 2026 18:35:43 +0100 Subject: [PATCH 17/20] fix(skylighting): use FrameCount for shadow bitmask index - Use SharedData::FrameCount instead of accumFrames for bit index - Revert to > 0.5 threshold for shadow bitmask - Always divide by 32.0 since bitmask is now frame-aligned Co-Authored-By: Claude Opus 4.6 --- .../Skylighting/Shaders/Skylighting/UpdateProbesCS.hlsl | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/features/Skylighting/Shaders/Skylighting/UpdateProbesCS.hlsl b/features/Skylighting/Shaders/Skylighting/UpdateProbesCS.hlsl index 1873afbdc9..d9dee158e2 100644 --- a/features/Skylighting/Shaders/Skylighting/UpdateProbesCS.hlsl +++ b/features/Skylighting/Shaders/Skylighting/UpdateProbesCS.hlsl @@ -94,8 +94,7 @@ static const float3 noise3D[32] = { float shadowSample = 1.0; DirectionalShadowLightData shadowData = DirectionalShadowLights[0]; - uint shadowFrames = max(accumFrames, 1u); - uint bitIndex = (shadowFrames - 1) % 32; + uint bitIndex = SharedData::FrameCount % 32; float3 jitteredMS = cellCentreMS + noise3D[bitIndex] * Skylighting::CELL_SIZE; float ndcDepth = FrameBuffer::GetShadowDepth(jitteredMS); @@ -121,13 +120,12 @@ static const float3 noise3D[32] = { uint bitmask = isValid ? outShadowBitmask[dtid] : 0; bitmask &= ~(1u << bitIndex); - if (shadowSample == 1.0) + if (shadowSample > 0.5) bitmask |= (1u << bitIndex); outShadowBitmask[dtid] = bitmask; - uint validBits = min(shadowFrames, 32u); - float shadow = float(countbits(bitmask)) / float(validBits); + float shadow = float(countbits(bitmask)) / 32.0; outShadowVisibility[dtid] = sqrt(shadow); } } else if (!isValid) { From aaac2e544fef4f6178b9820a4f78ecb49ea652fb Mon Sep 17 00:00:00 2001 From: doodlum <15017472+doodlum@users.noreply.github.com> Date: Fri, 12 Jun 2026 18:46:09 +0100 Subject: [PATCH 18/20] fix(skylighting): decouple shadow sampling from occlusion UV gate - Move shadow cascade sampling outside occlusionUV check so probes outside the occlusion map still get shadow visibility updates - Remove debug dirSoftShadow output from Lighting.hlsl - Remove sqrt from shadow visibility output Co-Authored-By: Claude Opus 4.6 --- .../Shaders/Skylighting/UpdateProbesCS.hlsl | 68 ++++++++++--------- package/Shaders/Lighting.hlsl | 2 - 2 files changed, 35 insertions(+), 35 deletions(-) diff --git a/features/Skylighting/Shaders/Skylighting/UpdateProbesCS.hlsl b/features/Skylighting/Shaders/Skylighting/UpdateProbesCS.hlsl index d9dee158e2..b6b1ba0614 100644 --- a/features/Skylighting/Shaders/Skylighting/UpdateProbesCS.hlsl +++ b/features/Skylighting/Shaders/Skylighting/UpdateProbesCS.hlsl @@ -88,50 +88,52 @@ static const float3 noise3D[32] = { outProbeArray[dtid] = occlusionSH; outAccumFramesArray[dtid] = accumFrames; + } else if (!isValid) { + outProbeArray[dtid] = unitSH; + outAccumFramesArray[dtid] = 0; + } + if (!isValid){ + outShadowBitmask[dtid] = 0; + outShadowVisibility[dtid] = 1.0; + } else { // Shadow cascade sampling with bitmask accumulation - { - float shadowSample = 1.0; - DirectionalShadowLightData shadowData = DirectionalShadowLights[0]; + float shadowSample = 1.0; + DirectionalShadowLightData shadowData = DirectionalShadowLights[0]; - uint bitIndex = SharedData::FrameCount % 32; - float3 jitteredMS = cellCentreMS + noise3D[bitIndex] * Skylighting::CELL_SIZE; + uint bitIndex = SharedData::FrameCount % 32; + float3 jitteredMS = cellCentreMS + noise3D[bitIndex] * Skylighting::CELL_SIZE; - float ndcDepth = FrameBuffer::GetShadowDepth(jitteredMS); - float linearDepth = SharedData::GetScreenDepth(ndcDepth); + float ndcDepth = FrameBuffer::GetShadowDepth(jitteredMS); + float linearDepth = SharedData::GetScreenDepth(ndcDepth); - if (linearDepth > 0 && linearDepth < shadowData.EndSplitDistances.y) { - float3 positionWS = jitteredMS + FrameBuffer::CameraPosAdjust.xyz; + if (linearDepth > 0 && linearDepth < shadowData.EndSplitDistances.y) { + float3 positionWS = jitteredMS + FrameBuffer::CameraPosAdjust.xyz; - uint cascadeIndex = (linearDepth > shadowData.EndSplitDistances.x) ? 1u : 0u; + uint cascadeIndex = (linearDepth > shadowData.EndSplitDistances.x) ? 1u : 0u; - float3 positionLS = mul(shadowData.ShadowProj[cascadeIndex], float4(positionWS, 1)).xyz; + float3 positionLS = mul(shadowData.ShadowProj[cascadeIndex], float4(positionWS, 1)).xyz; - if (all(positionLS.xy >= 0) && all(positionLS.xy <= 1)) { - float cascadeShadow = ShadowCascadeMap.SampleCmpLevelZero(comparisonSampler, float3(positionLS.xy, cascadeIndex), positionLS.z); - float esramShadow = ESRAMShadow.SampleCmpLevelZero(comparisonSampler, float3(positionLS.xy, cascadeIndex), positionLS.z); - shadowSample = min(cascadeShadow, esramShadow); - } - - float fade = saturate(linearDepth / shadowData.EndSplitDistances.y); - float fadeFactor = 1.0 - pow(fade * fade, 8); - shadowSample = lerp(1.0, shadowSample, fadeFactor); + if (all(positionLS.xy >= 0) && all(positionLS.xy <= 1)) { + float cascadeShadow = ShadowCascadeMap.SampleCmpLevelZero(comparisonSampler, float3(positionLS.xy, cascadeIndex), positionLS.z); + float esramShadow = ESRAMShadow.SampleCmpLevelZero(comparisonSampler, float3(positionLS.xy, cascadeIndex), positionLS.z); + shadowSample = min(cascadeShadow, esramShadow); } - uint bitmask = isValid ? outShadowBitmask[dtid] : 0; - bitmask &= ~(1u << bitIndex); - if (shadowSample > 0.5) - bitmask |= (1u << bitIndex); + float fade = saturate(linearDepth / shadowData.EndSplitDistances.y); + float fadeFactor = 1.0 - pow(fade * fade, 8); + shadowSample = lerp(1.0, shadowSample, fadeFactor); + } - outShadowBitmask[dtid] = bitmask; + uint bitmask = isValid ? outShadowBitmask[dtid] : 0; + bitmask &= ~(1u << bitIndex); + if (shadowSample > 0.5) + bitmask |= (1u << bitIndex); - float shadow = float(countbits(bitmask)) / 32.0; - outShadowVisibility[dtid] = sqrt(shadow); - } - } else if (!isValid) { - outProbeArray[dtid] = unitSH; - outAccumFramesArray[dtid] = 0; - outShadowBitmask[dtid] = 0; - outShadowVisibility[dtid] = 1.0; + outShadowBitmask[dtid] = bitmask; + + float shadow = float(countbits(bitmask)) / 32.0; + outShadowVisibility[dtid] = shadow; + } } diff --git a/package/Shaders/Lighting.hlsl b/package/Shaders/Lighting.hlsl index 76d2f569e6..33de810e47 100644 --- a/package/Shaders/Lighting.hlsl +++ b/package/Shaders/Lighting.hlsl @@ -3228,8 +3228,6 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) color.xyz = 0; # endif - color.xyz = dirSoftShadow; - # if defined(LANDSCAPE) && !defined(LOD_LAND_BLEND) psout.Diffuse.w = 0; # else From dcfdf53e47fdfe4b84ba67268f9bd171f7a0c70a Mon Sep 17 00:00:00 2001 From: doodlum <15017472+doodlum@users.noreply.github.com> Date: Fri, 12 Jun 2026 18:51:28 +0100 Subject: [PATCH 19/20] fix(skylighting): run shadow sampling unconditionally - Remove isValid guard so all probes get shadow updates - Increase jitter radius to 128 for better spatial coverage - Flatten shadow sampling out of conditional block Co-Authored-By: Claude Opus 4.6 --- .../Shaders/Skylighting/UpdateProbesCS.hlsl | 60 +++++++++---------- 1 file changed, 27 insertions(+), 33 deletions(-) diff --git a/features/Skylighting/Shaders/Skylighting/UpdateProbesCS.hlsl b/features/Skylighting/Shaders/Skylighting/UpdateProbesCS.hlsl index b6b1ba0614..71d293d7fd 100644 --- a/features/Skylighting/Shaders/Skylighting/UpdateProbesCS.hlsl +++ b/features/Skylighting/Shaders/Skylighting/UpdateProbesCS.hlsl @@ -93,47 +93,41 @@ static const float3 noise3D[32] = { outAccumFramesArray[dtid] = 0; } - if (!isValid){ - outShadowBitmask[dtid] = 0; - outShadowVisibility[dtid] = 1.0; - } else { - // Shadow cascade sampling with bitmask accumulation - float shadowSample = 1.0; - DirectionalShadowLightData shadowData = DirectionalShadowLights[0]; + // Shadow cascade sampling with bitmask accumulation + float shadowSample = 1.0; + DirectionalShadowLightData shadowData = DirectionalShadowLights[0]; - uint bitIndex = SharedData::FrameCount % 32; - float3 jitteredMS = cellCentreMS + noise3D[bitIndex] * Skylighting::CELL_SIZE; + uint bitIndex = SharedData::FrameCount % 32; + float3 jitteredMS = cellCentreMS + noise3D[bitIndex] * 128; - float ndcDepth = FrameBuffer::GetShadowDepth(jitteredMS); - float linearDepth = SharedData::GetScreenDepth(ndcDepth); + float ndcDepth = FrameBuffer::GetShadowDepth(jitteredMS); + float linearDepth = SharedData::GetScreenDepth(ndcDepth); - if (linearDepth > 0 && linearDepth < shadowData.EndSplitDistances.y) { - float3 positionWS = jitteredMS + FrameBuffer::CameraPosAdjust.xyz; + if (linearDepth > 0 && linearDepth < shadowData.EndSplitDistances.y) { + float3 positionWS = jitteredMS + FrameBuffer::CameraPosAdjust.xyz; - uint cascadeIndex = (linearDepth > shadowData.EndSplitDistances.x) ? 1u : 0u; + uint cascadeIndex = (linearDepth > shadowData.EndSplitDistances.x) ? 1u : 0u; - float3 positionLS = mul(shadowData.ShadowProj[cascadeIndex], float4(positionWS, 1)).xyz; + float3 positionLS = mul(shadowData.ShadowProj[cascadeIndex], float4(positionWS, 1)).xyz; - if (all(positionLS.xy >= 0) && all(positionLS.xy <= 1)) { - float cascadeShadow = ShadowCascadeMap.SampleCmpLevelZero(comparisonSampler, float3(positionLS.xy, cascadeIndex), positionLS.z); - float esramShadow = ESRAMShadow.SampleCmpLevelZero(comparisonSampler, float3(positionLS.xy, cascadeIndex), positionLS.z); - shadowSample = min(cascadeShadow, esramShadow); - } - - float fade = saturate(linearDepth / shadowData.EndSplitDistances.y); - float fadeFactor = 1.0 - pow(fade * fade, 8); - shadowSample = lerp(1.0, shadowSample, fadeFactor); + if (all(positionLS.xy >= 0) && all(positionLS.xy <= 1)) { + float cascadeShadow = ShadowCascadeMap.SampleCmpLevelZero(comparisonSampler, float3(positionLS.xy, cascadeIndex), positionLS.z); + float esramShadow = ESRAMShadow.SampleCmpLevelZero(comparisonSampler, float3(positionLS.xy, cascadeIndex), positionLS.z); + shadowSample = min(cascadeShadow, esramShadow); } - uint bitmask = isValid ? outShadowBitmask[dtid] : 0; - bitmask &= ~(1u << bitIndex); - if (shadowSample > 0.5) - bitmask |= (1u << bitIndex); + float fade = saturate(linearDepth / shadowData.EndSplitDistances.y); + float fadeFactor = 1.0 - pow(fade * fade, 8); + shadowSample = lerp(1.0, shadowSample, fadeFactor); + } + + uint bitmask = isValid ? outShadowBitmask[dtid] : 0; + bitmask &= ~(1u << bitIndex); + if (shadowSample > 0.5) + bitmask |= (1u << bitIndex); - outShadowBitmask[dtid] = bitmask; + outShadowBitmask[dtid] = bitmask; - float shadow = float(countbits(bitmask)) / 32.0; - outShadowVisibility[dtid] = shadow; - - } + float shadow = float(countbits(bitmask)) / 32.0; + outShadowVisibility[dtid] = shadow; } From 63a54e32e4435c817b1bf9fb7eb03f6a965554a8 Mon Sep 17 00:00:00 2001 From: doodlum <15017472+doodlum@users.noreply.github.com> Date: Fri, 12 Jun 2026 18:56:32 +0100 Subject: [PATCH 20/20] fix(skylighting): skip shadow updates for off-screen probes Project cell centre to screen UV and only update shadow bitmask for probes visible on screen. Co-Authored-By: Claude Opus 4.6 --- .../Shaders/Skylighting/UpdateProbesCS.hlsl | 55 +++++++++++-------- 1 file changed, 32 insertions(+), 23 deletions(-) diff --git a/features/Skylighting/Shaders/Skylighting/UpdateProbesCS.hlsl b/features/Skylighting/Shaders/Skylighting/UpdateProbesCS.hlsl index 71d293d7fd..6bfd917549 100644 --- a/features/Skylighting/Shaders/Skylighting/UpdateProbesCS.hlsl +++ b/features/Skylighting/Shaders/Skylighting/UpdateProbesCS.hlsl @@ -94,40 +94,49 @@ static const float3 noise3D[32] = { } // Shadow cascade sampling with bitmask accumulation - float shadowSample = 1.0; - DirectionalShadowLightData shadowData = DirectionalShadowLights[0]; + float4 cellCentreCS = mul(FrameBuffer::CameraViewProj, float4(cellCentreMS, 1)); + float2 screenUV = (cellCentreCS.xy / cellCentreCS.w) * float2(0.5, -0.5) + 0.5; + bool onScreen = cellCentreCS.w > 0 && all(screenUV > 0) && all(screenUV < 1); - uint bitIndex = SharedData::FrameCount % 32; - float3 jitteredMS = cellCentreMS + noise3D[bitIndex] * 128; + if (onScreen) { + float shadowSample = 1.0; + DirectionalShadowLightData shadowData = DirectionalShadowLights[0]; - float ndcDepth = FrameBuffer::GetShadowDepth(jitteredMS); - float linearDepth = SharedData::GetScreenDepth(ndcDepth); + uint bitIndex = SharedData::FrameCount % 32; + float3 jitteredMS = cellCentreMS + noise3D[bitIndex] * 128; - if (linearDepth > 0 && linearDepth < shadowData.EndSplitDistances.y) { - float3 positionWS = jitteredMS + FrameBuffer::CameraPosAdjust.xyz; + float ndcDepth = FrameBuffer::GetShadowDepth(jitteredMS); + float linearDepth = SharedData::GetScreenDepth(ndcDepth); - uint cascadeIndex = (linearDepth > shadowData.EndSplitDistances.x) ? 1u : 0u; + if (linearDepth > 0 && linearDepth < shadowData.EndSplitDistances.y) { + float3 positionWS = jitteredMS + FrameBuffer::CameraPosAdjust.xyz; - float3 positionLS = mul(shadowData.ShadowProj[cascadeIndex], float4(positionWS, 1)).xyz; + uint cascadeIndex = (linearDepth > shadowData.EndSplitDistances.x) ? 1u : 0u; + + float3 positionLS = mul(shadowData.ShadowProj[cascadeIndex], float4(positionWS, 1)).xyz; + + positionLS.xy = saturate(positionLS.xy); - if (all(positionLS.xy >= 0) && all(positionLS.xy <= 1)) { float cascadeShadow = ShadowCascadeMap.SampleCmpLevelZero(comparisonSampler, float3(positionLS.xy, cascadeIndex), positionLS.z); float esramShadow = ESRAMShadow.SampleCmpLevelZero(comparisonSampler, float3(positionLS.xy, cascadeIndex), positionLS.z); shadowSample = min(cascadeShadow, esramShadow); - } - float fade = saturate(linearDepth / shadowData.EndSplitDistances.y); - float fadeFactor = 1.0 - pow(fade * fade, 8); - shadowSample = lerp(1.0, shadowSample, fadeFactor); - } + float fade = saturate(linearDepth / shadowData.EndSplitDistances.y); + float fadeFactor = 1.0 - pow(fade * fade, 8); + shadowSample = lerp(1.0, shadowSample, fadeFactor); + } - uint bitmask = isValid ? outShadowBitmask[dtid] : 0; - bitmask &= ~(1u << bitIndex); - if (shadowSample > 0.5) - bitmask |= (1u << bitIndex); + uint bitmask = isValid ? outShadowBitmask[dtid] : 0; + bitmask &= ~(1u << bitIndex); + if (shadowSample > 0.5) + bitmask |= (1u << bitIndex); - outShadowBitmask[dtid] = bitmask; + outShadowBitmask[dtid] = bitmask; - float shadow = float(countbits(bitmask)) / 32.0; - outShadowVisibility[dtid] = shadow; + float shadow = float(countbits(bitmask)) / 32.0; + outShadowVisibility[dtid] = shadow; + } else if (!isValid) { + outShadowBitmask[dtid] = 0; + outShadowVisibility[dtid] = 1.0; + } }