From b3b810b1c764bd006ddb2cbf08ed158cb09e8754 Mon Sep 17 00:00:00 2001 From: doodlum <15017472+doodlum@users.noreply.github.com> Date: Mon, 26 Jan 2026 17:25:20 +0000 Subject: [PATCH 01/63] feat: simpler effect shadows --- package/Shaders/Common/Permutation.hlsli | 5 +- package/Shaders/Common/ShadowSampling.hlsli | 73 +++++++--- package/Shaders/Effect.hlsl | 152 +++++++------------- package/Shaders/Water.hlsl | 124 ++++++++-------- src/Hooks.cpp | 23 --- src/State.h | 5 +- 6 files changed, 169 insertions(+), 213 deletions(-) diff --git a/package/Shaders/Common/Permutation.hlsli b/package/Shaders/Common/Permutation.hlsli index 5cd4babe9c..3ad7dcea91 100644 --- a/package/Shaders/Common/Permutation.hlsli +++ b/package/Shaders/Common/Permutation.hlsli @@ -59,9 +59,8 @@ namespace Permutation static const uint InWorld = (1 << 0); static const uint InReflection = (1 << 1); static const uint IsBeastRace = (1 << 2); - static const uint EffectShadows = (1 << 3); - static const uint IsTree = (1 << 4); - static const uint GrassSphereNormal = (1 << 5); + static const uint IsTree = (1 << 3); + static const uint GrassSphereNormal = (1 << 4); } namespace ExtraFeatureFlags diff --git a/package/Shaders/Common/ShadowSampling.hlsli b/package/Shaders/Common/ShadowSampling.hlsli index 000bfdb5cf..cc5f586dcf 100644 --- a/package/Shaders/Common/ShadowSampling.hlsli +++ b/package/Shaders/Common/ShadowSampling.hlsli @@ -4,10 +4,14 @@ #include "Common/Math.hlsli" #include "Common/Random.hlsli" #include "Common/SharedData.hlsli" +#include "Common/Color.hlsli" + +#if defined(IBL) +# include "IBL/IBL.hlsli" +#endif namespace ShadowSampling { - Texture2DArray SharedShadowMap : register(t18); struct ShadowData @@ -40,7 +44,7 @@ namespace ShadowSampling ShadowData sD = SharedShadowData[0]; float fadeFactor = 1.0 - pow(saturate(dot(positionWS, positionWS) / sD.ShadowLightParam.z), 8); - uint sampleCount = ceil(8.0 * (1.0 - saturate(length(positionWS) / sqrt(sD.ShadowLightParam.z)))); + uint sampleCount = ceil(16.0 * (1.0 - saturate(length(positionWS) / sqrt(sD.ShadowLightParam.z)))); if (sampleCount == 0) return 1.0; @@ -50,25 +54,35 @@ namespace ShadowSampling uint3 seed = Random::pcg3d(uint3(screenPosition.xy, screenPosition.x * Math::PI)); float2 compareValue; - compareValue.x = mul(transpose(sD.ShadowMapProj[eyeIndex][0]), float4(positionWS, 1)).z - 0.01; - compareValue.y = mul(transpose(sD.ShadowMapProj[eyeIndex][1]), float4(positionWS, 1)).z - 0.01; + compareValue.x = mul(transpose(sD.ShadowMapProj[eyeIndex][0]), float4(positionWS, 1)).z - sD.AlphaTestRef.y; + compareValue.y = mul(transpose(sD.ShadowMapProj[eyeIndex][1]), float4(positionWS, 1)).z - sD.AlphaTestRef.z; float shadow = 0.0; if (sD.EndSplitDistances.z >= GetShadowDepth(positionWS, eyeIndex)) { for (uint i = 0; i < sampleCount; i++) { - float3 rnd = Random::R3Modified(i + SharedData::FrameCount * sampleCount, seed / 4294967295.f); + // Stratified jitter instead of pure random + float3 stratified = (float3(i, i * 2, i * 3) + 0.5) * rcpSampleCount; + float3 rnd = frac(Random::R3Modified(i, seed / 4294967295.f) + stratified); - // https://stats.stackexchange.com/questions/8021/how-to-generate-uniformly-distributed-points-in-the-3-d-unit-ball float phi = rnd.x * Math::TAU; float cos_theta = rnd.y * 2 - 1; - float sin_theta = sqrt(1 - cos_theta); - float r = rnd.z; + float u = rnd.z; + + // Proper uniform sphere distribution: r = u^(1/3) for volume + float r = pow(u, 1.0 / 3.0); + + float sin_theta = sqrt(max(0, 1 - cos_theta * cos_theta)); float4 sincos_phi; sincos(phi, sincos_phi.y, sincos_phi.x); - float3 sampleOffset = viewDirection * (float(i) - float(sampleCount) * 0.5) * 64 * rcpSampleCount; - sampleOffset += float3(r * sin_theta * sincos_phi.x, r * sin_theta * sincos_phi.y, r * cos_theta) * 64; + + // March along view direction with stratified steps + float marchStep = (float(i) + 0.5) * rcpSampleCount - 0.5; + float3 sampleOffset = viewDirection * marchStep * 16; + sampleOffset += float3(r * sin_theta * sincos_phi.x, + r * sin_theta * sincos_phi.y, + r * cos_theta) * 16; - uint cascadeIndex = sD.EndSplitDistances.x < GetShadowDepth(positionWS.xyz + viewDirection * (sampleOffset.x + sampleOffset.y), eyeIndex); // Stochastic cascade sampling + uint cascadeIndex = sD.EndSplitDistances.x < GetShadowDepth(positionWS.xyz + viewDirection * (sampleOffset.x + sampleOffset.y), eyeIndex); float3 positionLS = mul(transpose(sD.ShadowMapProj[eyeIndex][cascadeIndex]), float4(positionWS + sampleOffset, 1)); @@ -160,16 +174,13 @@ namespace ShadowSampling return worldShadow; } - float GetEffectShadow(float3 worldPosition, float3 viewDirection, float2 screenPosition, uint eyeIndex, out bool isWorldShadow) + float GetEffectShadow(float3 worldPosition, float3 viewDirection, float2 screenPosition, uint eyeIndex) { - isWorldShadow = false; float worldShadow = GetWorldShadow(worldPosition, FrameBuffer::CameraPosAdjust[eyeIndex].xyz, eyeIndex); if (worldShadow != 0.0) { float shadow = Get3DFilteredShadow(worldPosition, viewDirection, screenPosition, eyeIndex); - isWorldShadow = shadow >= worldShadow; - return min(worldShadow, shadow); + return worldShadow * shadow; } - isWorldShadow = true; return worldShadow; } @@ -194,6 +205,36 @@ namespace ShadowSampling return worldShadow; } + + void ExtractLighting(float3 inputColor, out float3 dirColor, out float3 ambientColor, float skylightingDiffuse = 1.0) + { + float3 ambientColorAmb = max(0, mul(SharedData::DirectionalAmbient, float4(0, 0, 1, 1))); + + # if defined(IBL) + if (SharedData::iblSettings.EnableDiffuseIBL && (!SharedData::InInterior || SharedData::iblSettings.EnableInterior)) { + ambientColorAmb *= SharedData::iblSettings.DALCAmount; + # if defined(SKYLIGHTING) && !defined(INTERIOR) + float3 iblColor = Color::Saturation(ImageBasedLighting::GetIBLColor(float3(0, 0, -1), skylightingDiffuse), SharedData::iblSettings.IBLSaturation) * SharedData::iblSettings.DiffuseIBLScale; + # else + float3 iblColor = Color::Saturation(ImageBasedLighting::GetIBLColor(float3(0, 0, -1)), SharedData::iblSettings.IBLSaturation) * SharedData::iblSettings.DiffuseIBLScale; + # endif + ambientColorAmb += Color::IrradianceToGamma(iblColor); + } + # endif + + float llDirLightMult = (SharedData::linearLightingSettings.enableLinearLighting && !SharedData::linearLightingSettings.isDirLightLinear) + ? SharedData::linearLightingSettings.dirLightMult : 1.0f; + float3 dirLightColorDir = Color::DirectionalLight(SharedData::DirLightColor.xyz / max(llDirLightMult, 1e-5), + SharedData::linearLightingSettings.isDirLightLinear) * llDirLightMult * Color::EffectLightingMult(); + + // Calculate total expected lighting and find scale to match input + float3 totalLight = ambientColorAmb + dirLightColorDir; + float3 scale = totalLight > 0.0 ? inputColor / totalLight : 1.0; + + // Distribute proportionally + ambientColor = ambientColorAmb * scale; + dirColor = dirLightColorDir * scale; + } } #endif // __SHADOW_SAMPLING_DEPENDENCY_HLSL__ \ No newline at end of file diff --git a/package/Shaders/Effect.hlsl b/package/Shaders/Effect.hlsl index 980cf084f7..62d882344d 100644 --- a/package/Shaders/Effect.hlsl +++ b/package/Shaders/Effect.hlsl @@ -532,106 +532,54 @@ cbuffer PerGeometry : register(b2) # include "Common/ShadowSampling.hlsli" -float ComputeShadowVariance(float shadow) -{ - // Measure local gradient magnitude; classify "no variation" using a small threshold. - const float2 grad = float2(ddx(shadow), ddy(shadow)); - const float v = abs(grad.x) + abs(grad.y) + fwidth(shadow); - const float epsilon = 1e-4; - return (v < epsilon) ? 1.0 : 0.0; -} - # if defined(LIGHTING) float3 GetLightingColor(float3 msPosition, float3 worldPosition, float4 screenPosition, uint eyeIndex, inout float shadowVariance) { - float4 lightDistanceSquared = (PLightPositionX[eyeIndex] - msPosition.xxxx) * (PLightPositionX[eyeIndex] - msPosition.xxxx) + (PLightPositionY[eyeIndex] - msPosition.yyyy) * (PLightPositionY[eyeIndex] - msPosition.yyyy) + (PLightPositionZ[eyeIndex] - msPosition.zzzz) * (PLightPositionZ[eyeIndex] - msPosition.zzzz); - float4 lightFadeMul = 1.0.xxxx - saturate(PLightingRadiusInverseSquared * lightDistanceSquared); - float3 color = DLightColor.xyz * Color::EffectLightingMult(); - if ((Permutation::ExtraShaderDescriptor & Permutation::ExtraFlags::EffectShadows)) { - float llDirLightMult = (SharedData::linearLightingSettings.enableLinearLighting && !SharedData::linearLightingSettings.isDirLightLinear) ? SharedData::linearLightingSettings.dirLightMult : 1.0f; - float3 dirLightColor = Color::DirectionalLight(SharedData::DirLightColor.xyz / max(llDirLightMult, 1e-5), SharedData::linearLightingSettings.isDirLightLinear) * llDirLightMult * 0.5 * Color::EffectLightingMult(); - float3 ambientColor = max(0, mul(SharedData::DirectionalAmbient, float4(0, 0, 1, 1))); - -# if defined(IBL) - if (SharedData::iblSettings.EnableDiffuseIBL && (!SharedData::InInterior || SharedData::iblSettings.EnableInterior)) { - ambientColor *= SharedData::iblSettings.DALCAmount; - } -# endif - - color = ambientColor; - # if defined(SKYLIGHTING) # if defined(VR) - float3 positionMSSkylight = worldPosition + FrameBuffer::CameraPosAdjust[eyeIndex].xyz - FrameBuffer::CameraPosAdjust[0].xyz; + float3 positionMSSkylight = worldPosition + FrameBuffer::CameraPosAdjust[eyeIndex].xyz - FrameBuffer::CameraPosAdjust[0].xyz; # else - float3 positionMSSkylight = worldPosition; + float3 positionMSSkylight = worldPosition; # endif - sh2 skylightingSH = Skylighting::sampleNoBias(SharedData::skylightingSettings, Skylighting::SkylightingProbeArray, positionMSSkylight); - float skylightingDiffuse = SphericalHarmonics::FuncProductIntegral(skylightingSH, SphericalHarmonics::EvaluateCosineLobe(float3(0, 0, 1))) / Math::PI; - skylightingDiffuse = saturate(skylightingDiffuse); - skylightingDiffuse = lerp(1.0, skylightingDiffuse, Skylighting::getFadeOutFactor(worldPosition)); - skylightingDiffuse = Skylighting::mixDiffuse(SharedData::skylightingSettings, skylightingDiffuse); + sh2 skylightingSH = Skylighting::sampleNoBias(SharedData::skylightingSettings, Skylighting::SkylightingProbeArray, positionMSSkylight); - color = Color::IrradianceToLinear(color); - color *= skylightingDiffuse; - color = Color::IrradianceToGamma(color); -# endif + float skylightingDiffuse = SphericalHarmonics::FuncProductIntegral(skylightingSH, SphericalHarmonics::EvaluateCosineLobe(float3(0, 0, 1))) / Math::PI; + skylightingDiffuse = saturate(skylightingDiffuse); + skylightingDiffuse = lerp(1.0, skylightingDiffuse, Skylighting::getFadeOutFactor(worldPosition)); + skylightingDiffuse = Skylighting::mixDiffuse(SharedData::skylightingSettings, skylightingDiffuse); +# endif -# if defined(IBL) - float3 iblColor = 0; - if (SharedData::iblSettings.EnableDiffuseIBL) { - if (!SharedData::InInterior || SharedData::iblSettings.EnableInterior) - { -# if defined(SKYLIGHTING) - iblColor += Color::Saturation(ImageBasedLighting::GetIBLColor(float3(0, 0, -1), skylightingDiffuse), SharedData::iblSettings.IBLSaturation) * SharedData::iblSettings.DiffuseIBLScale; -# else - iblColor += Color::Saturation(ImageBasedLighting::GetIBLColor(float3(0, 0, -1)), SharedData::iblSettings.IBLSaturation) * SharedData::iblSettings.DiffuseIBLScale; -# endif - color += Color::IrradianceToGamma(iblColor); - } - } + float3 dirColor; + float3 ambientColor; +# if defined(SKYLIGHTING) && !defined(INTERIOR) + ShadowSampling::ExtractLighting(color, dirColor, ambientColor, skylightingDiffuse); +# else + ShadowSampling::ExtractLighting(color, dirColor, ambientColor); # endif - if (!SharedData::InInterior){ - bool isWorldShadow = false; - float shadow = ShadowSampling::GetEffectShadow(worldPosition.xyz, normalize(worldPosition.xyz), screenPosition.xy, eyeIndex, isWorldShadow); - color += dirLightColor * shadow; - // Do not denoise world shadows - if (!isWorldShadow) - shadowVariance = ComputeShadowVariance(shadow); - } else { - color += dirLightColor; - } - } else { -# if defined(SKYLIGHTING) -# if defined(VR) - float3 positionMSSkylight = worldPosition + FrameBuffer::CameraPosAdjust[eyeIndex].xyz - FrameBuffer::CameraPosAdjust[0].xyz; -# else - float3 positionMSSkylight = worldPosition; -# endif + float shadow = ShadowSampling::GetEffectShadow(worldPosition.xyz, normalize(worldPosition.xyz), screenPosition, eyeIndex); - sh2 skylightingSH = Skylighting::sampleNoBias(SharedData::skylightingSettings, Skylighting::SkylightingProbeArray, positionMSSkylight); + shadowVariance = 1.0 - saturate(fwidth(shadow)); - if (!SharedData::InInterior) { - float skylightingDiffuse = SphericalHarmonics::FuncProductIntegral(skylightingSH, SphericalHarmonics::EvaluateCosineLobe(float3(0, 0, 1))) / Math::PI; - skylightingDiffuse = saturate(skylightingDiffuse); - skylightingDiffuse = lerp(1.0, skylightingDiffuse, Skylighting::getFadeOutFactor(worldPosition)); - skylightingDiffuse = Skylighting::mixDiffuse(SharedData::skylightingSettings, skylightingDiffuse); + dirColor *= shadow; - color = Color::IrradianceToLinear(color); - color *= skylightingDiffuse; - color = Color::IrradianceToGamma(color); - } +# if defined(SKYLIGHTING) + ambientColor = Color::IrradianceToLinear(ambientColor); + ambientColor *= skylightingDiffuse; + ambientColor = Color::IrradianceToGamma(ambientColor); # endif - } + + color = dirColor + ambientColor; # if defined(LIGHT_LIMIT_FIX) if (!(Permutation::ExtraShaderDescriptor & Permutation::ExtraFlags::InWorld)) # endif { + float4 lightDistanceSquared = (PLightPositionX[eyeIndex] - msPosition.xxxx) * (PLightPositionX[eyeIndex] - msPosition.xxxx) + (PLightPositionY[eyeIndex] - msPosition.yyyy) * (PLightPositionY[eyeIndex] - msPosition.yyyy) + (PLightPositionZ[eyeIndex] - msPosition.zzzz) * (PLightPositionZ[eyeIndex] - msPosition.zzzz); + float4 lightFadeMul = 1.0.xxxx - saturate(PLightingRadiusInverseSquared * lightDistanceSquared); color.x += dot(Color::PointLight(PLightColorR.xxx).x * lightFadeMul * Color::EffectLightingMult(), 1.0.xxxx); color.y += dot(Color::PointLight(PLightColorG.xxx).x * lightFadeMul * Color::EffectLightingMult(), 1.0.xxxx); color.z += dot(Color::PointLight(PLightColorB.xxx).x * lightFadeMul * Color::EffectLightingMult(), 1.0.xxxx); @@ -703,38 +651,38 @@ PS_OUTPUT main(PS_INPUT input) # if defined(LIGHT_LIMIT_FIX) uint lightCount = 0; - if (LightingInfluence.x > 0.0) { - float3 viewPosition = mul(FrameBuffer::CameraView[eyeIndex], float4(input.WorldPosition.xyz, 1)).xyz; - float2 screenUV = FrameBuffer::ViewToUV(viewPosition, true, eyeIndex); - bool inWorld = Permutation::ExtraShaderDescriptor & Permutation::ExtraFlags::InWorld; - - uint clusterIndex = 0; - if (inWorld && LightLimitFix::GetClusterIndex(screenUV, viewPosition.z, clusterIndex)) { - lightCount = LightLimitFix::lightGrid[clusterIndex].lightCount; - uint lightOffset = LightLimitFix::lightGrid[clusterIndex].offset; - [loop] for (uint i = 0; i < lightCount; i++) - { - uint clusteredLightIndex = LightLimitFix::lightList[lightOffset + i]; - LightLimitFix::Light light = LightLimitFix::lights[clusteredLightIndex]; - if (LightLimitFix::IsLightIgnored(light) || light.lightFlags & LightLimitFix::LightFlags::Shadow) { - continue; - } - float3 lightDirection = light.positionWS[eyeIndex].xyz - input.WorldPosition.xyz; - float lightDist = length(lightDirection); + + float3 viewPosition = mul(FrameBuffer::CameraView[eyeIndex], float4(input.WorldPosition.xyz, 1)).xyz; + float2 screenUV = FrameBuffer::ViewToUV(viewPosition, true, eyeIndex); + bool inWorld = Permutation::ExtraShaderDescriptor & Permutation::ExtraFlags::InWorld; + + uint clusterIndex = 0; + if (inWorld && LightLimitFix::GetClusterIndex(screenUV, viewPosition.z, clusterIndex)) { + lightCount = LightLimitFix::lightGrid[clusterIndex].lightCount; + uint lightOffset = LightLimitFix::lightGrid[clusterIndex].offset; + [loop] for (uint i = 0; i < lightCount; i++) + { + uint clusteredLightIndex = LightLimitFix::lightList[lightOffset + i]; + LightLimitFix::Light light = LightLimitFix::lights[clusteredLightIndex]; + if (LightLimitFix::IsLightIgnored(light) || light.lightFlags & LightLimitFix::LightFlags::Shadow) { + continue; + } + float3 lightDirection = light.positionWS[eyeIndex].xyz - input.WorldPosition.xyz; + float lightDist = length(lightDirection); # if defined(ISL) - float intensityMultiplier = InverseSquareLighting::GetAttenuation(lightDist, light); + float intensityMultiplier = InverseSquareLighting::GetAttenuation(lightDist, light); # else - float intensityFactor = saturate(lightDist / light.radius); - float intensityMultiplier = 1 - intensityFactor * intensityFactor; + float intensityFactor = saturate(lightDist / light.radius); + float intensityMultiplier = 1 - intensityFactor * intensityFactor; # endif - const bool isPointLightLinear = light.lightFlags & LightLimitFix::LightFlags::Linear; - float3 lightColor = Color::PointLight(light.color.xyz, isPointLightLinear) * intensityMultiplier * 0.5 * light.fade * Color::EffectLightingMult(); - propertyColor += lightColor; - } + const bool isPointLightLinear = light.lightFlags & LightLimitFix::LightFlags::Linear; + float3 lightColor = Color::PointLight(light.color.xyz, isPointLightLinear) * intensityMultiplier * 0.5 * light.fade * Color::EffectLightingMult(); + propertyColor += lightColor; } } + # endif # elif defined(MEMBRANE) propertyColor *= 0; diff --git a/package/Shaders/Water.hlsl b/package/Shaders/Water.hlsl index a2c7895bed..cbd380c0cd 100644 --- a/package/Shaders/Water.hlsl +++ b/package/Shaders/Water.hlsl @@ -684,10 +684,11 @@ struct WaterNormalData float4 rippleInfo; // xyz = scaled ripple normal (normalized normal * intensity), w = splash effect intensity }; -WaterNormalData GetWaterNormal(PS_INPUT input, float distanceFactor, float normalsDepthFactor, float3 viewDirection, float depth, uint eyeIndex) +WaterNormalData GetWaterNormal(PS_INPUT input, float distanceFactor, float normalsDepthFactor, float3 viewDirection, float depth, uint eyeIndex, float wetnessOcclusion) { WaterNormalData result; result.rippleInfo = float4(0, 0, 0, 0); + float3 normalScalesRcp = rcp(input.NormalsScale.xyz); # if defined(WATER_PARALLAX) @@ -802,21 +803,6 @@ WaterNormalData GetWaterNormal(PS_INPUT input, float distanceFactor, float norma // Wetness Effects Debug System: // DEBUG_WETNESS_EFFECTS Color Legend: // - BRIGHT MAGENTA: Ripples, BRIGHT GREEN: Splashes, CYAN: Both effects - const bool inWorld = (Permutation::ExtraShaderDescriptor & Permutation::ExtraFlags::InWorld); -# if defined(SKYLIGHTING) -# if defined(VR) - float3 positionMSSkylight = input.WPosition.xyz + FrameBuffer::CameraPosAdjust[eyeIndex].xyz - FrameBuffer::CameraPosAdjust[0].xyz; -# else - float3 positionMSSkylight = input.WPosition.xyz; -# endif - sh2 skylightingSH = Skylighting::sample(SharedData::skylightingSettings, Skylighting::SkylightingProbeArray, Skylighting::stbn_vec3_2Dx1D_128x128x64, input.HPosition.xy, positionMSSkylight, float3(0, 0, 1)); - float skylighting = SphericalHarmonics::Unproject(skylightingSH, float3(0, 0, 1)); - - float wetnessOcclusion = inWorld ? pow(saturate(skylighting), 2) : 0; -# else - float wetnessOcclusion = inWorld; -# endif - float4 raindropInfo = float4(0, 0, 1, 0); float maxRainDropDistance = SharedData::wetnessEffectsSettings.RaindropFxRange * SharedData::wetnessEffectsSettings.RaindropFxRange * 3; float rainDropDistance = dot(input.WPosition, input.WPosition); @@ -864,7 +850,7 @@ WaterNormalData GetWaterNormal(PS_INPUT input, float distanceFactor, float norma } float3 GetWaterSpecularColor(PS_INPUT input, float3 normal, float3 viewDirection, - float distanceFactor, float refractionsDepthFactor, uint eyeIndex = 0) + float distanceFactor, float refractionsDepthFactor, uint eyeIndex, float skylightingSpecular) { if (Permutation::PixelShaderDescriptor & Permutation::WaterFlags::Reflections) { float3 finalSsrReflectionColor = 0.0.xxx; @@ -874,25 +860,10 @@ float3 GetWaterSpecularColor(PS_INPUT input, float3 normal, float3 viewDirection if (Permutation::PixelShaderDescriptor & Permutation::WaterFlags::Cubemap) { # if defined(DYNAMIC_CUBEMAPS) -# if defined(SKYLIGHTING) - float3 dynamicCubemap; if (SharedData::InInterior) { dynamicCubemap = DynamicCubemaps::EnvTexture.SampleLevel(CubeMapSampler, R, 0).xyz; } else { -# if defined(VR) - float3 positionMSSkylight = input.WPosition.xyz + FrameBuffer::CameraPosAdjust[eyeIndex].xyz - FrameBuffer::CameraPosAdjust[0].xyz; -# else - float3 positionMSSkylight = input.WPosition.xyz; -# endif - - sh2 skylighting = Skylighting::sample(SharedData::skylightingSettings, Skylighting::SkylightingProbeArray, Skylighting::stbn_vec3_2Dx1D_128x128x64, input.HPosition.xy, positionMSSkylight, R); - sh2 specularLobe = SphericalHarmonics::FauxSpecularLobe(normal, -viewDirection, 0.0); - - float skylightingSpecular = SphericalHarmonics::FuncProductIntegral(skylighting, specularLobe); - skylightingSpecular = lerp(1.0, skylightingSpecular, Skylighting::getFadeOutFactor(input.WPosition.xyz)); - skylightingSpecular = Skylighting::mixSpecular(SharedData::skylightingSettings, skylightingSpecular); - float3 specularIrradiance = 1; if (skylightingSpecular < 1.0) { @@ -907,9 +878,6 @@ float3 GetWaterSpecularColor(PS_INPUT input, float3 normal, float3 viewDirection dynamicCubemap = Color::IrradianceToGamma(lerp(specularIrradiance, specularIrradianceReflections, skylightingSpecular)); } -# else - float3 dynamicCubemap = DynamicCubemaps::EnvReflectionsTexture.SampleLevel(CubeMapSampler, R, 0); -# endif # if defined(VR) // Reflection cubemap is incorrect for interiors in VR, ignore it @@ -1001,7 +969,7 @@ struct DiffuseOutput float refractionMul; }; -DiffuseOutput GetWaterDiffuseColor(PS_INPUT input, float3 normal, float3 viewDirection, inout float4 distanceMul, float refractionsDepthFactor, float fresnel, uint eyeIndex, float3 viewPosition, float noise, float depth) +DiffuseOutput GetWaterDiffuseColor(PS_INPUT input, float3 normal, float3 viewDirection, inout float4 distanceMul, float refractionsDepthFactor, float fresnel, uint eyeIndex, float3 viewPosition, float depth) { # if defined(REFRACTIONS) float4 refractionNormal = mul(transpose(TextureProj[eyeIndex]), float4((VarAmounts.w * refractionsDepthFactor * normal.xy) + input.MPosition.xy, input.MPosition.z, 1)); @@ -1050,26 +1018,6 @@ DiffuseOutput GetWaterDiffuseColor(PS_INPUT input, float3 normal, float3 viewDir float3 refractionColor = RefractionTex.Sample(RefractionSampler, refractionUV).xyz; float3 refractionDiffuseColor = lerp(Color::Water(ShallowColor.xyz), Color::Water(DeepColor.xyz), distanceMul.y); - if (!(Permutation::PixelShaderDescriptor & Permutation::WaterFlags::Interior)) { -# if defined(SKYLIGHTING) - float3 skylightingPosition = lerp(input.WPosition.xyz, refractionWorldPosition.xyz, noise); - -# if defined(VR) - float3 positionMSSkylight = skylightingPosition + FrameBuffer::CameraPosAdjust[eyeIndex].xyz - FrameBuffer::CameraPosAdjust[0].xyz; -# else - float3 positionMSSkylight = skylightingPosition; -# endif - - sh2 skylightingSH = Skylighting::sampleNoBias(SharedData::skylightingSettings, Skylighting::SkylightingProbeArray, positionMSSkylight); - float skylightingDiffuse = SphericalHarmonics::FuncProductIntegral(skylightingSH, SphericalHarmonics::EvaluateCosineLobe(float3(0, 0, 1))) / Math::PI; - skylightingDiffuse = saturate(skylightingDiffuse); - skylightingDiffuse = lerp(1.0, skylightingDiffuse, Skylighting::getFadeOutFactor(input.WPosition.xyz)); - - float3 refractionDiffuseColorSkylight = Skylighting::mixDiffuse(SharedData::skylightingSettings, skylightingDiffuse); - refractionDiffuseColor = Color::LinearToGamma(Color::GammaToLinear(refractionDiffuseColor) * refractionDiffuseColorSkylight); -# endif - } - # if defined(UNDERWATER) float refractionMul = 0; # else @@ -1180,7 +1128,39 @@ PS_OUTPUT main(PS_INPUT input) float3 viewPosition = mul(FrameBuffer::CameraView[eyeIndex], float4(input.WPosition.xyz, 1)).xyz; float2 screenUV = FrameBuffer::ViewToUV(viewPosition, true, eyeIndex); - WaterNormalData waterData = GetWaterNormal(input, distanceBlendFactor, depthControl.z, viewDirection, depth, eyeIndex); + float dirShadow = ShadowSampling::GetEffectShadow(input.WPosition.xyz, normalize(input.WPosition.xyz), input.HPosition.xy, eyeIndex); + + float skylightingDiffuse = 1.0; + float skylightingSpecular = 1.0; + float wetnessOcclusion = 1.0; + +# if defined(SKYLIGHTING) + { + const bool inWorld = (Permutation::ExtraShaderDescriptor & Permutation::ExtraFlags::InWorld); +# if defined(VR) + float3 positionMSSkylight = input.WPosition.xyz + FrameBuffer::CameraPosAdjust[eyeIndex].xyz - FrameBuffer::CameraPosAdjust[0].xyz; +# else + float3 positionMSSkylight = input.WPosition.xyz; +# endif + + sh2 skylightingSH = Skylighting::sampleNoBias(SharedData::skylightingSettings, Skylighting::SkylightingProbeArray, positionMSSkylight); + float skylighting = SphericalHarmonics::Unproject(skylightingSH, float3(0, 0, 1)); + + skylightingDiffuse = SphericalHarmonics::FuncProductIntegral(skylightingSH, SphericalHarmonics::EvaluateCosineLobe(float3(0, 0, 1))) / Math::PI; + skylightingDiffuse = saturate(skylightingDiffuse); + skylightingDiffuse = lerp(1.0, skylightingDiffuse, Skylighting::getFadeOutFactor(input.WPosition.xyz)); + + skylightingSpecular = skylightingDiffuse; + + skylightingDiffuse = Skylighting::mixDiffuse(SharedData::skylightingSettings, skylightingDiffuse); + skylightingSpecular = Skylighting::mixSpecular(SharedData::skylightingSettings, skylightingSpecular); + + wetnessOcclusion = inWorld ? pow(saturate(skylighting), 2) : 0; + } +# endif + + WaterNormalData waterData = GetWaterNormal(input, distanceFactor, depthControl.z, viewDirection, depth, eyeIndex, wetnessOcclusion); + float3 normal = waterData.normal; float fresnel = GetFresnelValue(normal, viewDirection); @@ -1210,12 +1190,28 @@ PS_OUTPUT main(PS_INPUT input) isSpecular = true; # else - float shadow = 1; + float3 specularColor = GetWaterSpecularColor(input, normal, viewDirection, distanceFactor, depthControl.y, eyeIndex, skylightingSpecular); + DiffuseOutput diffuseOutput = GetWaterDiffuseColor(input, normal, viewDirection, distanceMul, depthControl.y, fresnel, eyeIndex, viewPosition, depth); + + float3 waterColor = diffuseOutput.refractionDiffuseColor; + + float3 dirColor; + float3 ambientColor; +# if defined(SKYLIGHTING) && !defined(INTERIOR) + ShadowSampling::ExtractLighting(diffuseOutput.refractionDiffuseColor, dirColor, ambientColor, skylightingDiffuse); +# else + ShadowSampling::ExtractLighting(diffuseOutput.refractionDiffuseColor, dirColor, ambientColor); +# endif + + dirColor *= dirShadow; - float screenNoise = Random::InterleavedGradientNoise(input.HPosition.xy, SharedData::FrameCount); +# if defined(SKYLIGHTING) + ambientColor = Color::IrradianceToLinear(ambientColor); + ambientColor *= skylightingDiffuse; + ambientColor = Color::IrradianceToGamma(ambientColor); +# endif - float3 specularColor = GetWaterSpecularColor(input, normal, viewDirection, distanceFactor, depthControl.y, eyeIndex); - DiffuseOutput diffuseOutput = GetWaterDiffuseColor(input, normal, viewDirection, distanceMul, depthControl.y, fresnel, eyeIndex, viewPosition, screenNoise, depth); + diffuseOutput.refractionDiffuseColor = dirColor + ambientColor; float3 diffuseColor = lerp(diffuseOutput.refractionColor, diffuseOutput.refractionDiffuseColor, diffuseOutput.refractionMul); @@ -1273,11 +1269,7 @@ PS_OUTPUT main(PS_INPUT input) } # endif # else - float3 sunColor = GetSunColor(normal, viewDirection); - - if (!(Permutation::PixelShaderDescriptor & Permutation::WaterFlags::Interior) && any(sunColor > 0.0)) { - sunColor *= ShadowSampling::GetWaterShadow(screenNoise, input.WPosition.xyz, eyeIndex); - } + float3 sunColor = GetSunColor(normal, viewDirection) * dirShadow; # if defined(VC) float specularFraction = lerp(1, fresnel * diffuseOutput.refractionMul, distanceBlendFactor); diff --git a/src/Hooks.cpp b/src/Hooks.cpp index d29a3f04ad..9c52601ab3 100644 --- a/src/Hooks.cpp +++ b/src/Hooks.cpp @@ -162,28 +162,6 @@ bool Hooks::BSShader_BeginTechnique::thunk(RE::BSShader* shader, uint32_t vertex return shaderFound; } -namespace EffectExtensions -{ - struct BSEffectShader_SetupGeometry - { - static void thunk(RE::BSShader* shader, RE::BSRenderPass* pass, uint32_t renderFlags) - { - func(shader, pass, renderFlags); - - auto state = globals::state; - - state->permutationData.ExtraShaderDescriptor &= ~static_cast(State::ExtraShaderDescriptors::EffectShadows); - - if (auto* shaderProperty = static_cast(pass->geometry->GetGeometryRuntimeData().properties[1].get())) { - if (shaderProperty->flags.any(RE::BSShaderProperty::EShaderPropertyFlag::kUniformScale)) { - state->permutationData.ExtraShaderDescriptor |= static_cast(State::ExtraShaderDescriptors::EffectShadows); - } - } - } - static inline REL::Relocation func; - }; -} - namespace LightingExtensions { struct BSLightingShader_SetupGeometry @@ -905,7 +883,6 @@ namespace Hooks stl::write_thunk_call(REL::RelocationID(31373, 32160).address() + REL::Relocate(0x1AD, 0x1CA, 0x1ed)); logger::info("Installing SetupGeometry hooks"); - stl::write_vfunc<0x6, EffectExtensions::BSEffectShader_SetupGeometry>(RE::VTABLE_BSEffectShader[0]); stl::write_vfunc<0x6, LightingExtensions::BSLightingShader_SetupGeometry>(RE::VTABLE_BSLightingShader[0]); stl::write_thunk_call(REL::RelocationID(15214, 15383).address() + REL::Relocate(0x45B, 0x4F5)); stl::write_vfunc<0x6, GrassExtensions::BSGrassShader_SetupGeometry>(RE::VTABLE_BSGrassShader[0]); diff --git a/src/State.h b/src/State.h index e8a9de3477..971b31de73 100644 --- a/src/State.h +++ b/src/State.h @@ -153,9 +153,8 @@ class State InWorld = 1 << 0, IsReflections = 1 << 1, IsBeastRace = 1 << 2, - EffectShadows = 1 << 3, - IsTree = 1 << 4, - GrassSphereNormal = 1 << 5 + IsTree = 1 << 3, + GrassSphereNormal = 1 << 4 }; enum class ExtraFeatureDescriptors : uint32_t From d03c94993fbb7da56cb15582e7f8a86cfa5c3cf1 Mon Sep 17 00:00:00 2001 From: doodlum <15017472+doodlum@users.noreply.github.com> Date: Mon, 26 Jan 2026 17:26:27 +0000 Subject: [PATCH 02/63] chore: remove getwatershadow --- package/Shaders/Common/ShadowSampling.hlsli | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/package/Shaders/Common/ShadowSampling.hlsli b/package/Shaders/Common/ShadowSampling.hlsli index cc5f586dcf..4189743679 100644 --- a/package/Shaders/Common/ShadowSampling.hlsli +++ b/package/Shaders/Common/ShadowSampling.hlsli @@ -192,20 +192,6 @@ namespace ShadowSampling return Get2DFilteredShadow(noise, rotationMatrix, worldPosition, eyeIndex); } - float GetWaterShadow(float noise, float3 worldPosition, uint eyeIndex) - { - float worldShadow = GetWorldShadow(worldPosition, FrameBuffer::CameraPosAdjust[eyeIndex].xyz, eyeIndex); - if (worldShadow != 0.0) { - float2 rotation; - sincos(Math::TAU * noise, rotation.y, rotation.x); - float2x2 rotationMatrix = float2x2(rotation.x, rotation.y, -rotation.y, rotation.x); - float shadow = Get2DFilteredShadow(noise, rotationMatrix, worldPosition, eyeIndex); - return worldShadow * shadow; - } - - return worldShadow; - } - void ExtractLighting(float3 inputColor, out float3 dirColor, out float3 ambientColor, float skylightingDiffuse = 1.0) { float3 ambientColorAmb = max(0, mul(SharedData::DirectionalAmbient, float4(0, 0, 1, 1))); From 015c01671a7082cc10120185f82e63f8aa808ac3 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 26 Jan 2026 17:33:20 +0000 Subject: [PATCH 03/63] =?UTF-8?q?style:=20=F0=9F=8E=A8=20apply=20pre-commi?= =?UTF-8?q?t.ci=20formatting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Automated formatting by clang-format, prettier, and other hooks. See https://pre-commit.ci for details. --- package/Shaders/Common/ShadowSampling.hlsli | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/package/Shaders/Common/ShadowSampling.hlsli b/package/Shaders/Common/ShadowSampling.hlsli index 4189743679..e7b96d2725 100644 --- a/package/Shaders/Common/ShadowSampling.hlsli +++ b/package/Shaders/Common/ShadowSampling.hlsli @@ -67,19 +67,19 @@ namespace ShadowSampling float phi = rnd.x * Math::TAU; float cos_theta = rnd.y * 2 - 1; float u = rnd.z; - + // Proper uniform sphere distribution: r = u^(1/3) for volume float r = pow(u, 1.0 / 3.0); - + float sin_theta = sqrt(max(0, 1 - cos_theta * cos_theta)); float4 sincos_phi; sincos(phi, sincos_phi.y, sincos_phi.x); - + // March along view direction with stratified steps float marchStep = (float(i) + 0.5) * rcpSampleCount - 0.5; float3 sampleOffset = viewDirection * marchStep * 16; - sampleOffset += float3(r * sin_theta * sincos_phi.x, - r * sin_theta * sincos_phi.y, + sampleOffset += float3(r * sin_theta * sincos_phi.x, + r * sin_theta * sincos_phi.y, r * cos_theta) * 16; uint cascadeIndex = sD.EndSplitDistances.x < GetShadowDepth(positionWS.xyz + viewDirection * (sampleOffset.x + sampleOffset.y), eyeIndex); From 3dd8cbf7526034f4c450855a246621c06510ecc4 Mon Sep 17 00:00:00 2001 From: doodlum <15017472+doodlum@users.noreply.github.com> Date: Tue, 27 Jan 2026 16:11:35 +0000 Subject: [PATCH 04/63] fix: fix sampling --- package/Shaders/Common/ShadowSampling.hlsli | 22 ++++++--------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/package/Shaders/Common/ShadowSampling.hlsli b/package/Shaders/Common/ShadowSampling.hlsli index e7b96d2725..2de181d5d8 100644 --- a/package/Shaders/Common/ShadowSampling.hlsli +++ b/package/Shaders/Common/ShadowSampling.hlsli @@ -60,27 +60,17 @@ namespace ShadowSampling float shadow = 0.0; if (sD.EndSplitDistances.z >= GetShadowDepth(positionWS, eyeIndex)) { for (uint i = 0; i < sampleCount; i++) { - // Stratified jitter instead of pure random - float3 stratified = (float3(i, i * 2, i * 3) + 0.5) * rcpSampleCount; - float3 rnd = frac(Random::R3Modified(i, seed / 4294967295.f) + stratified); + float3 rnd = Random::R3Modified(i + SharedData::FrameCount * sampleCount, seed / 4294967295.f); + // https://stats.stackexchange.com/questions/8021/how-to-generate-uniformly-distributed-points-in-the-3-d-unit-ball float phi = rnd.x * Math::TAU; float cos_theta = rnd.y * 2 - 1; - float u = rnd.z; - - // Proper uniform sphere distribution: r = u^(1/3) for volume - float r = pow(u, 1.0 / 3.0); - - float sin_theta = sqrt(max(0, 1 - cos_theta * cos_theta)); + float sin_theta = sqrt(1 - cos_theta); + float r = rnd.z; float4 sincos_phi; sincos(phi, sincos_phi.y, sincos_phi.x); - - // March along view direction with stratified steps - float marchStep = (float(i) + 0.5) * rcpSampleCount - 0.5; - float3 sampleOffset = viewDirection * marchStep * 16; - sampleOffset += float3(r * sin_theta * sincos_phi.x, - r * sin_theta * sincos_phi.y, - r * cos_theta) * 16; + float3 sampleOffset = viewDirection * (float(i) - float(sampleCount) * 0.5) * 16 * rcpSampleCount; + sampleOffset += float3(r * sin_theta * sincos_phi.x, r * sin_theta * sincos_phi.y, r * cos_theta) * 16; uint cascadeIndex = sD.EndSplitDistances.x < GetShadowDepth(positionWS.xyz + viewDirection * (sampleOffset.x + sampleOffset.y), eyeIndex); From b9b04ef711d1296e026b01fadf17f9087b392a56 Mon Sep 17 00:00:00 2001 From: doodlum <15017472+doodlum@users.noreply.github.com> Date: Wed, 28 Jan 2026 01:28:57 +0000 Subject: [PATCH 05/63] feat: optimise shadow samples further --- package/Shaders/Common/ShadowSampling.hlsli | 62 ++++++------- package/Shaders/DownsampleShadowCS.hlsl | 38 ++++++++ src/Deferred.cpp | 96 ++++++++++++++++++++- src/Deferred.h | 8 ++ 4 files changed, 169 insertions(+), 35 deletions(-) create mode 100644 package/Shaders/DownsampleShadowCS.hlsl diff --git a/package/Shaders/Common/ShadowSampling.hlsli b/package/Shaders/Common/ShadowSampling.hlsli index 2de181d5d8..913a353fd2 100644 --- a/package/Shaders/Common/ShadowSampling.hlsli +++ b/package/Shaders/Common/ShadowSampling.hlsli @@ -43,46 +43,42 @@ namespace ShadowSampling { ShadowData sD = SharedShadowData[0]; - float fadeFactor = 1.0 - pow(saturate(dot(positionWS, positionWS) / sD.ShadowLightParam.z), 8); - uint sampleCount = ceil(16.0 * (1.0 - saturate(length(positionWS) / sqrt(sD.ShadowLightParam.z)))); - - if (sampleCount == 0) - return 1.0; + static const uint sampleCount = 16; + static const float rcpSampleCount = 1.0 / float(sampleCount); - float rcpSampleCount = rcp((float)sampleCount); - - uint3 seed = Random::pcg3d(uint3(screenPosition.xy, screenPosition.x * Math::PI)); + float noise = Random::InterleavedGradientNoise(screenPosition, SharedData::FrameCount); + float2 rotation; + sincos(Math::TAU * (noise * 2.0 - 1.0), rotation.y, rotation.x); + float2x2 rotationMatrix = float2x2(rotation.x, rotation.y, -rotation.y, rotation.x); - float2 compareValue; - compareValue.x = mul(transpose(sD.ShadowMapProj[eyeIndex][0]), float4(positionWS, 1)).z - sD.AlphaTestRef.y; - compareValue.y = mul(transpose(sD.ShadowMapProj[eyeIndex][1]), float4(positionWS, 1)).z - sD.AlphaTestRef.z; + float shadowMapDepth = GetShadowDepth(positionWS, eyeIndex); float shadow = 0.0; - if (sD.EndSplitDistances.z >= GetShadowDepth(positionWS, eyeIndex)) { - for (uint i = 0; i < sampleCount; i++) { - float3 rnd = Random::R3Modified(i + SharedData::FrameCount * sampleCount, seed / 4294967295.f); - - // https://stats.stackexchange.com/questions/8021/how-to-generate-uniformly-distributed-points-in-the-3-d-unit-ball - float phi = rnd.x * Math::TAU; - float cos_theta = rnd.y * 2 - 1; - float sin_theta = sqrt(1 - cos_theta); - float r = rnd.z; - float4 sincos_phi; - sincos(phi, sincos_phi.y, sincos_phi.x); - float3 sampleOffset = viewDirection * (float(i) - float(sampleCount) * 0.5) * 16 * rcpSampleCount; - sampleOffset += float3(r * sin_theta * sincos_phi.x, r * sin_theta * sincos_phi.y, r * cos_theta) * 16; - - uint cascadeIndex = sD.EndSplitDistances.x < GetShadowDepth(positionWS.xyz + viewDirection * (sampleOffset.x + sampleOffset.y), eyeIndex); - - float3 positionLS = mul(transpose(sD.ShadowMapProj[eyeIndex][cascadeIndex]), float4(positionWS + sampleOffset, 1)); - - float4 depths = SharedShadowMap.GatherRed(LinearSampler, float3(saturate(positionLS.xy), cascadeIndex), 0); - shadow += dot(depths > compareValue[cascadeIndex], 0.25); + if (sD.EndSplitDistances.z >= shadowMapDepth) { + float cascade1BlendFactor = smoothstep(0, 1, (shadowMapDepth - sD.StartSplitDistances.y) / (sD.EndSplitDistances.x - sD.StartSplitDistances.y)); + uint cascadeIndex = noise < cascade1BlendFactor; + + float compareValue = mul(transpose(sD.ShadowMapProj[eyeIndex][cascadeIndex]), float4(positionWS, 1)).z - sD.AlphaTestRef[1 + cascadeIndex]; + float sampleRadius = sD.ShadowSampleParam.z * rcp(1 + cascadeIndex) * 4.0; + + float3 positionLS = mul(transpose(sD.ShadowMapProj[eyeIndex][cascadeIndex]), float4(positionWS, 1)); + float3 viewOffsetLS = mul(transpose(sD.ShadowMapProj[eyeIndex][cascadeIndex]), float4(positionWS + viewDirection * 128, 1)); + + for (uint i = 0; i < sampleCount; i++) { + uint noisyIndex = (i + uint(noise * sampleCount)) % sampleCount; + float t = (float(noisyIndex) - float(sampleCount) * 0.5) * rcpSampleCount + 0.5; + float3 sampledPositionLS = lerp(positionLS, viewOffsetLS, t); + + sampledPositionLS.xy += mul(Random::PoissonSampleOffsets16[i], rotationMatrix) * sampleRadius; + + float4 depths = SharedShadowMap.GatherRed(LinearSampler, float3(saturate(sampledPositionLS.xy), cascadeIndex), 0); + shadow += dot(depths > compareValue, 0.25); } } else { shadow = 1.0; } + float fadeFactor = 1.0 - pow(saturate(dot(positionWS, positionWS) / sD.ShadowLightParam.z), 8); return lerp(1.0, shadow * rcpSampleCount, fadeFactor); } @@ -94,9 +90,7 @@ namespace ShadowSampling float visibility = 0.0; -#if defined(WATER) - sampleOffsetScale *= 2.0; -#endif + sampleOffsetScale *= 4.0; for (uint sampleIndex = 0; sampleIndex < sampleCount; ++sampleIndex) { float2 sampleOffset = mul(Random::PoissonSampleOffsets16[sampleIndex], rotationMatrix); diff --git a/package/Shaders/DownsampleShadowCS.hlsl b/package/Shaders/DownsampleShadowCS.hlsl new file mode 100644 index 0000000000..609e3a5774 --- /dev/null +++ b/package/Shaders/DownsampleShadowCS.hlsl @@ -0,0 +1,38 @@ +Texture2DArray InputTexture : register(t0); +RWTexture2DArray OutputTexture : register(u0); + +groupshared float g_scratchDepths[8][8]; + +[numthreads(8, 8, 1)] void main(uint3 dispatchThreadID + : SV_DispatchThreadID, uint2 groupThreadID + : SV_GroupThreadID) { + // MIP 0 -> 1: each thread loads a 2x2 block and computes max + uint2 pixCoord = dispatchThreadID.xy * 2; + float depth0 = InputTexture.Load(int4(pixCoord + uint2(0, 0), dispatchThreadID.z, 0)); + float depth1 = InputTexture.Load(int4(pixCoord + uint2(1, 0), dispatchThreadID.z, 0)); + float depth2 = InputTexture.Load(int4(pixCoord + uint2(0, 1), dispatchThreadID.z, 0)); + float depth3 = InputTexture.Load(int4(pixCoord + uint2(1, 1), dispatchThreadID.z, 0)); + + float dm1 = max(max(depth0, depth1), max(depth2, depth3)); + g_scratchDepths[groupThreadID.x][groupThreadID.y] = dm1; + + GroupMemoryBarrierWithGroupSync(); + + // MIP 1 -> 2: 2x2 reduction in shared memory (4x4 total) + [branch] + if (all((groupThreadID.xy % 2) == 0)) { + float inTL = g_scratchDepths[groupThreadID.x + 0][groupThreadID.y + 0]; + float inTR = g_scratchDepths[groupThreadID.x + 1][groupThreadID.y + 0]; + float inBL = g_scratchDepths[groupThreadID.x + 0][groupThreadID.y + 1]; + float inBR = g_scratchDepths[groupThreadID.x + 1][groupThreadID.y + 1]; + + float dm2 = max(max(inTL, inTR), max(inBL, inBR)); + g_scratchDepths[groupThreadID.x][groupThreadID.y] = dm2; + + uint3 outCoord = uint3(dispatchThreadID.xy / 2, dispatchThreadID.z); + uint w, h, slices; + OutputTexture.GetDimensions(w, h, slices); + if (outCoord.x < w && outCoord.y < h) + OutputTexture[outCoord] = dm2; + } +} \ No newline at end of file diff --git a/src/Deferred.cpp b/src/Deferred.cpp index 686ac8d78e..e4bb1c396b 100644 --- a/src/Deferred.cpp +++ b/src/Deferred.cpp @@ -156,6 +156,7 @@ void Deferred::SetupResources() perShadow->CreateUAV(uavDesc); copyShadowCS = static_cast(Util::CompileShader(L"Data\\Shaders\\CopyShadowDataCS.hlsl", {}, "cs_5_0")); + downsampleShadowCS = static_cast(Util::CompileShader(L"Data\\Shaders\\DownsampleShadowCS.hlsl", {}, "cs_5_0")); } { @@ -212,8 +213,101 @@ void Deferred::CopyShadowData() { context->PSGetShaderResources(4, 1, &shadowView); + // Downsample shadow texture array to 4x smaller resolution + if (shadowView) { + ID3D11Resource* shadowResource = nullptr; + shadowView->GetResource(&shadowResource); + + if (shadowResource) { + ID3D11Texture2D* shadowTexture = nullptr; + shadowResource->QueryInterface(__uuidof(ID3D11Texture2D), reinterpret_cast(&shadowTexture)); + + if (shadowTexture) { + D3D11_TEXTURE2D_DESC srcDesc; + shadowTexture->GetDesc(&srcDesc); + + uint32_t newWidth = srcDesc.Width / 4; + uint32_t newHeight = srcDesc.Height / 4; + + // Lazily create or recreate downscaled texture if dimensions changed + if (!shadowCopyTexture || shadowCopyWidth != newWidth || shadowCopyHeight != newHeight || shadowCopyArraySize != srcDesc.ArraySize) { + if (shadowCopySRV) { + shadowCopySRV->Release(); + shadowCopySRV = nullptr; + } + if (shadowCopyUAV) { + shadowCopyUAV->Release(); + shadowCopyUAV = nullptr; + } + if (shadowCopyTexture) { + shadowCopyTexture->Release(); + shadowCopyTexture = nullptr; + } + + shadowCopyWidth = newWidth; + shadowCopyHeight = newHeight; + shadowCopyArraySize = srcDesc.ArraySize; + + D3D11_TEXTURE2D_DESC copyDesc{}; + copyDesc.Width = newWidth; + copyDesc.Height = newHeight; + copyDesc.MipLevels = 1; + copyDesc.ArraySize = srcDesc.ArraySize; + copyDesc.Format = DXGI_FORMAT_R16_UNORM; + copyDesc.SampleDesc.Count = 1; + copyDesc.SampleDesc.Quality = 0; + copyDesc.Usage = D3D11_USAGE_DEFAULT; + copyDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_UNORDERED_ACCESS; + + auto device = globals::d3d::device; + DX::ThrowIfFailed(device->CreateTexture2D(©Desc, nullptr, &shadowCopyTexture)); + + D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc{}; + srvDesc.Format = DXGI_FORMAT_R16_UNORM; + srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2DARRAY; + srvDesc.Texture2DArray.MostDetailedMip = 0; + srvDesc.Texture2DArray.MipLevels = 1; + srvDesc.Texture2DArray.FirstArraySlice = 0; + srvDesc.Texture2DArray.ArraySize = srcDesc.ArraySize; + DX::ThrowIfFailed(device->CreateShaderResourceView(shadowCopyTexture, &srvDesc, &shadowCopySRV)); + + D3D11_UNORDERED_ACCESS_VIEW_DESC uavDesc{}; + uavDesc.Format = DXGI_FORMAT_R16_UNORM; + uavDesc.ViewDimension = D3D11_UAV_DIMENSION_TEXTURE2DARRAY; + uavDesc.Texture2DArray.MipSlice = 0; + uavDesc.Texture2DArray.FirstArraySlice = 0; + uavDesc.Texture2DArray.ArraySize = srcDesc.ArraySize; + DX::ThrowIfFailed(device->CreateUnorderedAccessView(shadowCopyTexture, &uavDesc, &shadowCopyUAV)); + } + + // Dispatch downsample compute shader + ID3D11ShaderResourceView* csSrvs[1]{ shadowView }; + context->CSSetShaderResources(0, 1, csSrvs); + + ID3D11UnorderedAccessView* csUavs[1]{ shadowCopyUAV }; + context->CSSetUnorderedAccessViews(0, 1, csUavs, nullptr); + + context->CSSetShader(downsampleShadowCS, nullptr, 0); + + uint fullShadowSize = shadowCopyWidth * 4; + + context->Dispatch((fullShadowSize + 15) >> 4, (fullShadowSize + 15) >> 4, shadowCopyArraySize); + + // Cleanup CS state + csSrvs[0] = nullptr; + context->CSSetShaderResources(0, 1, csSrvs); + csUavs[0] = nullptr; + context->CSSetUnorderedAccessViews(0, 1, csUavs, nullptr); + context->CSSetShader(nullptr, nullptr, 0); + + shadowTexture->Release(); + } + shadowResource->Release(); + } + } + ID3D11ShaderResourceView* srvs[2]{ - shadowView, + shadowCopySRV ? shadowCopySRV : shadowView, perShadow->srv.get(), }; diff --git a/src/Deferred.h b/src/Deferred.h index 8c920f3593..27d627b58d 100644 --- a/src/Deferred.h +++ b/src/Deferred.h @@ -67,9 +67,17 @@ class Deferred STATIC_ASSERT_ALIGNAS_16(PerGeometry); ID3D11ComputeShader* copyShadowCS = nullptr; + ID3D11ComputeShader* downsampleShadowCS = nullptr; Buffer* perShadow = nullptr; ID3D11ShaderResourceView* shadowView = nullptr; + ID3D11Texture2D* shadowCopyTexture = nullptr; + ID3D11ShaderResourceView* shadowCopySRV = nullptr; + ID3D11UnorderedAccessView* shadowCopyUAV = nullptr; + uint32_t shadowCopyWidth = 0; + uint32_t shadowCopyHeight = 0; + uint32_t shadowCopyArraySize = 0; + struct Hooks { struct Main_RenderShadowMaps From 429754c219dd3c5fa267e2a04caeb578753ded74 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 28 Jan 2026 01:30:13 +0000 Subject: [PATCH 06/63] =?UTF-8?q?style:=20=F0=9F=8E=A8=20apply=20pre-commi?= =?UTF-8?q?t.ci=20formatting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Automated formatting by clang-format, prettier, and other hooks. See https://pre-commit.ci for details. --- package/Shaders/Common/ShadowSampling.hlsli | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/package/Shaders/Common/ShadowSampling.hlsli b/package/Shaders/Common/ShadowSampling.hlsli index 913a353fd2..4838e4d172 100644 --- a/package/Shaders/Common/ShadowSampling.hlsli +++ b/package/Shaders/Common/ShadowSampling.hlsli @@ -57,15 +57,15 @@ namespace ShadowSampling if (sD.EndSplitDistances.z >= shadowMapDepth) { float cascade1BlendFactor = smoothstep(0, 1, (shadowMapDepth - sD.StartSplitDistances.y) / (sD.EndSplitDistances.x - sD.StartSplitDistances.y)); uint cascadeIndex = noise < cascade1BlendFactor; - + float compareValue = mul(transpose(sD.ShadowMapProj[eyeIndex][cascadeIndex]), float4(positionWS, 1)).z - sD.AlphaTestRef[1 + cascadeIndex]; float sampleRadius = sD.ShadowSampleParam.z * rcp(1 + cascadeIndex) * 4.0; - float3 positionLS = mul(transpose(sD.ShadowMapProj[eyeIndex][cascadeIndex]), float4(positionWS, 1)); - float3 viewOffsetLS = mul(transpose(sD.ShadowMapProj[eyeIndex][cascadeIndex]), float4(positionWS + viewDirection * 128, 1)); - - for (uint i = 0; i < sampleCount; i++) { - uint noisyIndex = (i + uint(noise * sampleCount)) % sampleCount; + float3 positionLS = mul(transpose(sD.ShadowMapProj[eyeIndex][cascadeIndex]), float4(positionWS, 1)); + float3 viewOffsetLS = mul(transpose(sD.ShadowMapProj[eyeIndex][cascadeIndex]), float4(positionWS + viewDirection * 128, 1)); + + for (uint i = 0; i < sampleCount; i++) { + uint noisyIndex = (i + uint(noise * sampleCount)) % sampleCount; float t = (float(noisyIndex) - float(sampleCount) * 0.5) * rcpSampleCount + 0.5; float3 sampledPositionLS = lerp(positionLS, viewOffsetLS, t); From fdb4987ac9a64ebca9c92ff5d872acd829772a97 Mon Sep 17 00:00:00 2001 From: doodlum <15017472+doodlum@users.noreply.github.com> Date: Wed, 28 Jan 2026 01:45:11 +0000 Subject: [PATCH 07/63] chore: remove lighting mult --- package/Shaders/Common/ShadowSampling.hlsli | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/Shaders/Common/ShadowSampling.hlsli b/package/Shaders/Common/ShadowSampling.hlsli index 4838e4d172..1b50f8b818 100644 --- a/package/Shaders/Common/ShadowSampling.hlsli +++ b/package/Shaders/Common/ShadowSampling.hlsli @@ -195,7 +195,7 @@ namespace ShadowSampling float llDirLightMult = (SharedData::linearLightingSettings.enableLinearLighting && !SharedData::linearLightingSettings.isDirLightLinear) ? SharedData::linearLightingSettings.dirLightMult : 1.0f; float3 dirLightColorDir = Color::DirectionalLight(SharedData::DirLightColor.xyz / max(llDirLightMult, 1e-5), - SharedData::linearLightingSettings.isDirLightLinear) * llDirLightMult * Color::EffectLightingMult(); + SharedData::linearLightingSettings.isDirLightLinear) * llDirLightMult; // Calculate total expected lighting and find scale to match input float3 totalLight = ambientColorAmb + dirLightColorDir; From 2d71569e610661800c1e5fd632c2c06a75eaa94c Mon Sep 17 00:00:00 2001 From: doodlum <15017472+doodlum@users.noreply.github.com> Date: Wed, 28 Jan 2026 01:52:15 +0000 Subject: [PATCH 08/63] fix: make ai happy --- package/Shaders/Common/ShadowSampling.hlsli | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package/Shaders/Common/ShadowSampling.hlsli b/package/Shaders/Common/ShadowSampling.hlsli index 1b50f8b818..dd606781cc 100644 --- a/package/Shaders/Common/ShadowSampling.hlsli +++ b/package/Shaders/Common/ShadowSampling.hlsli @@ -199,7 +199,8 @@ namespace ShadowSampling // Calculate total expected lighting and find scale to match input float3 totalLight = ambientColorAmb + dirLightColorDir; - float3 scale = totalLight > 0.0 ? inputColor / totalLight : 1.0; + float3 safeTotal = max(totalLight, 1e-5); + float3 scale = inputColor / safeTotal; // Distribute proportionally ambientColor = ambientColorAmb * scale; From 8ff91625707884706326721f4ffcd05dac2569f7 Mon Sep 17 00:00:00 2001 From: doodlum <15017472+doodlum@users.noreply.github.com> Date: Wed, 28 Jan 2026 01:54:12 +0000 Subject: [PATCH 09/63] fix: use gatherred --- package/Shaders/DownsampleShadowCS.hlsl | 18 +++++++++++++----- src/Deferred.cpp | 3 +++ 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/package/Shaders/DownsampleShadowCS.hlsl b/package/Shaders/DownsampleShadowCS.hlsl index 609e3a5774..182d775f00 100644 --- a/package/Shaders/DownsampleShadowCS.hlsl +++ b/package/Shaders/DownsampleShadowCS.hlsl @@ -1,17 +1,25 @@ Texture2DArray InputTexture : register(t0); RWTexture2DArray OutputTexture : register(u0); +SamplerState PointSampler : register(s0); + groupshared float g_scratchDepths[8][8]; [numthreads(8, 8, 1)] void main(uint3 dispatchThreadID : SV_DispatchThreadID, uint2 groupThreadID : SV_GroupThreadID) { - // MIP 0 -> 1: each thread loads a 2x2 block and computes max + // MIP 0 -> 1: each thread gathers a 2x2 block and computes max uint2 pixCoord = dispatchThreadID.xy * 2; - float depth0 = InputTexture.Load(int4(pixCoord + uint2(0, 0), dispatchThreadID.z, 0)); - float depth1 = InputTexture.Load(int4(pixCoord + uint2(1, 0), dispatchThreadID.z, 0)); - float depth2 = InputTexture.Load(int4(pixCoord + uint2(0, 1), dispatchThreadID.z, 0)); - float depth3 = InputTexture.Load(int4(pixCoord + uint2(1, 1), dispatchThreadID.z, 0)); + + uint inputW, inputH, inputSlices; + InputTexture.GetDimensions(inputW, inputH, inputSlices); + float2 uv = (pixCoord + 1.0) / float2(inputW, inputH); + + float4 depths4 = InputTexture.GatherRed(PointSampler, float3(uv, dispatchThreadID.z)); + float depth0 = depths4.w; + float depth1 = depths4.z; + float depth2 = depths4.x; + float depth3 = depths4.y; float dm1 = max(max(depth0, depth1), max(depth2, depth3)); g_scratchDepths[groupThreadID.x][groupThreadID.y] = dm1; diff --git a/src/Deferred.cpp b/src/Deferred.cpp index e4bb1c396b..87035c424b 100644 --- a/src/Deferred.cpp +++ b/src/Deferred.cpp @@ -287,6 +287,7 @@ void Deferred::CopyShadowData() ID3D11UnorderedAccessView* csUavs[1]{ shadowCopyUAV }; context->CSSetUnorderedAccessViews(0, 1, csUavs, nullptr); + context->CSSetSamplers(0, 1, &pointSampler); context->CSSetShader(downsampleShadowCS, nullptr, 0); uint fullShadowSize = shadowCopyWidth * 4; @@ -298,6 +299,8 @@ void Deferred::CopyShadowData() context->CSSetShaderResources(0, 1, csSrvs); csUavs[0] = nullptr; context->CSSetUnorderedAccessViews(0, 1, csUavs, nullptr); + ID3D11SamplerState* nullSampler = nullptr; + context->CSSetSamplers(0, 1, &nullSampler); context->CSSetShader(nullptr, nullptr, 0); shadowTexture->Release(); From 57aa59c82b779a1de3a0e622aba8b44c8f7a0916 Mon Sep 17 00:00:00 2001 From: doodlum <15017472+doodlum@users.noreply.github.com> Date: Wed, 28 Jan 2026 01:55:10 +0000 Subject: [PATCH 10/63] chore: remove watercolor variable --- package/Shaders/Water.hlsl | 2 -- 1 file changed, 2 deletions(-) diff --git a/package/Shaders/Water.hlsl b/package/Shaders/Water.hlsl index b39d28b946..6a8fd8a681 100644 --- a/package/Shaders/Water.hlsl +++ b/package/Shaders/Water.hlsl @@ -1193,8 +1193,6 @@ PS_OUTPUT main(PS_INPUT input) float3 specularColor = GetWaterSpecularColor(input, normal, viewDirection, distanceFactor, depthControl.y, eyeIndex, skylightingSpecular); DiffuseOutput diffuseOutput = GetWaterDiffuseColor(input, normal, viewDirection, distanceMul, depthControl.y, fresnel, eyeIndex, viewPosition, depth); - float3 waterColor = diffuseOutput.refractionDiffuseColor; - float3 dirColor; float3 ambientColor; # if defined(SKYLIGHTING) && !defined(INTERIOR) From 8f00c7f098abb32460d641a9ae161b10a8725849 Mon Sep 17 00:00:00 2001 From: doodlum <15017472+doodlum@users.noreply.github.com> Date: Wed, 28 Jan 2026 02:04:09 +0000 Subject: [PATCH 11/63] chore: use skylighting specular in water --- package/Shaders/Water.hlsl | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/package/Shaders/Water.hlsl b/package/Shaders/Water.hlsl index 6a8fd8a681..9ba1dff296 100644 --- a/package/Shaders/Water.hlsl +++ b/package/Shaders/Water.hlsl @@ -1135,34 +1135,35 @@ PS_OUTPUT main(PS_INPUT input) float wetnessOcclusion = 1.0; # if defined(SKYLIGHTING) - { - const bool inWorld = (Permutation::ExtraShaderDescriptor & Permutation::ExtraFlags::InWorld); + const bool inWorld = (Permutation::ExtraShaderDescriptor & Permutation::ExtraFlags::InWorld); # if defined(VR) - float3 positionMSSkylight = input.WPosition.xyz + FrameBuffer::CameraPosAdjust[eyeIndex].xyz - FrameBuffer::CameraPosAdjust[0].xyz; + float3 positionMSSkylight = input.WPosition.xyz + FrameBuffer::CameraPosAdjust[eyeIndex].xyz - FrameBuffer::CameraPosAdjust[0].xyz; # else - float3 positionMSSkylight = input.WPosition.xyz; + float3 positionMSSkylight = input.WPosition.xyz; # endif - sh2 skylightingSH = Skylighting::sampleNoBias(SharedData::skylightingSettings, Skylighting::SkylightingProbeArray, positionMSSkylight); - float skylighting = SphericalHarmonics::Unproject(skylightingSH, float3(0, 0, 1)); + sh2 skylightingSH = Skylighting::sampleNoBias(SharedData::skylightingSettings, Skylighting::SkylightingProbeArray, positionMSSkylight); + float skylighting = SphericalHarmonics::Unproject(skylightingSH, float3(0, 0, 1)); - skylightingDiffuse = SphericalHarmonics::FuncProductIntegral(skylightingSH, SphericalHarmonics::EvaluateCosineLobe(float3(0, 0, 1))) / Math::PI; - skylightingDiffuse = saturate(skylightingDiffuse); - skylightingDiffuse = lerp(1.0, skylightingDiffuse, Skylighting::getFadeOutFactor(input.WPosition.xyz)); + skylightingDiffuse = SphericalHarmonics::FuncProductIntegral(skylightingSH, SphericalHarmonics::EvaluateCosineLobe(float3(0, 0, 1))) / Math::PI; + skylightingDiffuse = saturate(skylightingDiffuse); + skylightingDiffuse = lerp(1.0, skylightingDiffuse, Skylighting::getFadeOutFactor(input.WPosition.xyz)); + skylightingDiffuse = Skylighting::mixDiffuse(SharedData::skylightingSettings, skylightingDiffuse); - skylightingSpecular = skylightingDiffuse; - - skylightingDiffuse = Skylighting::mixDiffuse(SharedData::skylightingSettings, skylightingDiffuse); - skylightingSpecular = Skylighting::mixSpecular(SharedData::skylightingSettings, skylightingSpecular); - - wetnessOcclusion = inWorld ? pow(saturate(skylighting), 2) : 0; - } + wetnessOcclusion = inWorld ? pow(saturate(skylighting), 2) : 0; # endif WaterNormalData waterData = GetWaterNormal(input, distanceFactor, depthControl.z, viewDirection, depth, eyeIndex, wetnessOcclusion); float3 normal = waterData.normal; +# if defined(SKYLIGHTING) + sh2 specularLobe = SphericalHarmonics::FauxSpecularLobe(normal, -viewDirection, 0.0); + skylightingSpecular = SphericalHarmonics::FuncProductIntegral(skylighting, specularLobe); + skylightingSpecular = saturate(skylightingSpecular); + skylightingSpecular = Skylighting::mixSpecular(SharedData::skylightingSettings, skylightingSpecular); +# endif + float fresnel = GetFresnelValue(normal, viewDirection); # if defined(SPECULAR) && (NUM_SPECULAR_LIGHTS != 0) From 20ffbb16b3f52f52d0b5aba03907331e7f3bd195 Mon Sep 17 00:00:00 2001 From: doodlum <15017472+doodlum@users.noreply.github.com> Date: Wed, 28 Jan 2026 02:26:21 +0000 Subject: [PATCH 12/63] chore: simplify math --- package/Shaders/Common/ShadowSampling.hlsli | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/package/Shaders/Common/ShadowSampling.hlsli b/package/Shaders/Common/ShadowSampling.hlsli index dd606781cc..5b225dabb9 100644 --- a/package/Shaders/Common/ShadowSampling.hlsli +++ b/package/Shaders/Common/ShadowSampling.hlsli @@ -61,12 +61,11 @@ namespace ShadowSampling float compareValue = mul(transpose(sD.ShadowMapProj[eyeIndex][cascadeIndex]), float4(positionWS, 1)).z - sD.AlphaTestRef[1 + cascadeIndex]; float sampleRadius = sD.ShadowSampleParam.z * rcp(1 + cascadeIndex) * 4.0; - float3 positionLS = mul(transpose(sD.ShadowMapProj[eyeIndex][cascadeIndex]), float4(positionWS, 1)); - float3 viewOffsetLS = mul(transpose(sD.ShadowMapProj[eyeIndex][cascadeIndex]), float4(positionWS + viewDirection * 128, 1)); - - for (uint i = 0; i < sampleCount; i++) { - uint noisyIndex = (i + uint(noise * sampleCount)) % sampleCount; - float t = (float(noisyIndex) - float(sampleCount) * 0.5) * rcpSampleCount + 0.5; + float3 positionLS = mul(transpose(sD.ShadowMapProj[eyeIndex][cascadeIndex]), float4(positionWS, 1)); + float3 viewOffsetLS = mul(transpose(sD.ShadowMapProj[eyeIndex][cascadeIndex]), float4(positionWS + viewDirection * 128, 1)); + + for (uint i = 0; i < sampleCount; i++) { + float t = (float(i) - float(sampleCount) * 0.5) * rcpSampleCount + 0.5; float3 sampledPositionLS = lerp(positionLS, viewOffsetLS, t); sampledPositionLS.xy += mul(Random::PoissonSampleOffsets16[i], rotationMatrix) * sampleRadius; From 9a45f8b56c4b7d1fc61a2d7796adaa644b6fc65e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 28 Jan 2026 02:28:10 +0000 Subject: [PATCH 13/63] =?UTF-8?q?style:=20=F0=9F=8E=A8=20apply=20pre-commi?= =?UTF-8?q?t.ci=20formatting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Automated formatting by clang-format, prettier, and other hooks. See https://pre-commit.ci for details. --- package/Shaders/Common/ShadowSampling.hlsli | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/package/Shaders/Common/ShadowSampling.hlsli b/package/Shaders/Common/ShadowSampling.hlsli index 5b225dabb9..8e71825337 100644 --- a/package/Shaders/Common/ShadowSampling.hlsli +++ b/package/Shaders/Common/ShadowSampling.hlsli @@ -61,10 +61,10 @@ namespace ShadowSampling float compareValue = mul(transpose(sD.ShadowMapProj[eyeIndex][cascadeIndex]), float4(positionWS, 1)).z - sD.AlphaTestRef[1 + cascadeIndex]; float sampleRadius = sD.ShadowSampleParam.z * rcp(1 + cascadeIndex) * 4.0; - float3 positionLS = mul(transpose(sD.ShadowMapProj[eyeIndex][cascadeIndex]), float4(positionWS, 1)); - float3 viewOffsetLS = mul(transpose(sD.ShadowMapProj[eyeIndex][cascadeIndex]), float4(positionWS + viewDirection * 128, 1)); - - for (uint i = 0; i < sampleCount; i++) { + float3 positionLS = mul(transpose(sD.ShadowMapProj[eyeIndex][cascadeIndex]), float4(positionWS, 1)); + float3 viewOffsetLS = mul(transpose(sD.ShadowMapProj[eyeIndex][cascadeIndex]), float4(positionWS + viewDirection * 128, 1)); + + for (uint i = 0; i < sampleCount; i++) { float t = (float(i) - float(sampleCount) * 0.5) * rcpSampleCount + 0.5; float3 sampledPositionLS = lerp(positionLS, viewOffsetLS, t); From 17465aed4d6cbd6ef1359c430d69132293bd9c5d Mon Sep 17 00:00:00 2001 From: doodlum <15017472+doodlum@users.noreply.github.com> Date: Wed, 28 Jan 2026 15:33:30 +0000 Subject: [PATCH 14/63] fix: fix skylighting lobe --- package/Shaders/Water.hlsl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/Shaders/Water.hlsl b/package/Shaders/Water.hlsl index 9ba1dff296..02bfd62727 100644 --- a/package/Shaders/Water.hlsl +++ b/package/Shaders/Water.hlsl @@ -1159,7 +1159,7 @@ PS_OUTPUT main(PS_INPUT input) # if defined(SKYLIGHTING) sh2 specularLobe = SphericalHarmonics::FauxSpecularLobe(normal, -viewDirection, 0.0); - skylightingSpecular = SphericalHarmonics::FuncProductIntegral(skylighting, specularLobe); ++ skylightingSpecular = SphericalHarmonics::FuncProductIntegral(skylightingSH, specularLobe); skylightingSpecular = saturate(skylightingSpecular); skylightingSpecular = Skylighting::mixSpecular(SharedData::skylightingSettings, skylightingSpecular); # endif From 38042a68de2cf4676f9714297bf3004cc8db42d4 Mon Sep 17 00:00:00 2001 From: doodlum <15017472+doodlum@users.noreply.github.com> Date: Wed, 28 Jan 2026 15:38:45 +0000 Subject: [PATCH 15/63] chore: minor tweaks --- package/Shaders/Effect.hlsl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package/Shaders/Effect.hlsl b/package/Shaders/Effect.hlsl index 62d882344d..4ab4724e7c 100644 --- a/package/Shaders/Effect.hlsl +++ b/package/Shaders/Effect.hlsl @@ -533,7 +533,7 @@ cbuffer PerGeometry : register(b2) # include "Common/ShadowSampling.hlsli" # if defined(LIGHTING) -float3 GetLightingColor(float3 msPosition, float3 worldPosition, float4 screenPosition, uint eyeIndex, inout float shadowVariance) +float3 GetLightingColor(float3 msPosition, float3 worldPosition, float2 screenPosition, uint eyeIndex, inout float shadowVariance) { float3 color = DLightColor.xyz * Color::EffectLightingMult(); @@ -554,7 +554,7 @@ float3 GetLightingColor(float3 msPosition, float3 worldPosition, float4 screenPo float3 dirColor; float3 ambientColor; -# if defined(SKYLIGHTING) && !defined(INTERIOR) +# if defined(SKYLIGHTING) ShadowSampling::ExtractLighting(color, dirColor, ambientColor, skylightingDiffuse); # else ShadowSampling::ExtractLighting(color, dirColor, ambientColor); @@ -647,7 +647,7 @@ PS_OUTPUT main(PS_INPUT input) float shadowVariance = 1.0; # if defined(LIGHTING) - propertyColor = GetLightingColor(input.MSPosition.xyz, input.WorldPosition.xyz, input.Position.xyzw, eyeIndex, shadowVariance); + propertyColor = GetLightingColor(input.MSPosition.xyz, input.WorldPosition.xyz, input.Position.xy, eyeIndex, shadowVariance); # if defined(LIGHT_LIMIT_FIX) uint lightCount = 0; From 3149c7b171b93982d5396194b14deacaf3e2c5a4 Mon Sep 17 00:00:00 2001 From: doodlum <15017472+doodlum@users.noreply.github.com> Date: Wed, 28 Jan 2026 15:41:02 +0000 Subject: [PATCH 16/63] chore: minor cleanup --- package/Shaders/Common/ShadowSampling.hlsli | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package/Shaders/Common/ShadowSampling.hlsli b/package/Shaders/Common/ShadowSampling.hlsli index 8e71825337..3d1feda98c 100644 --- a/package/Shaders/Common/ShadowSampling.hlsli +++ b/package/Shaders/Common/ShadowSampling.hlsli @@ -160,11 +160,11 @@ namespace ShadowSampling float GetEffectShadow(float3 worldPosition, float3 viewDirection, float2 screenPosition, uint eyeIndex) { float worldShadow = GetWorldShadow(worldPosition, FrameBuffer::CameraPosAdjust[eyeIndex].xyz, eyeIndex); - if (worldShadow != 0.0) { + [branch] if (worldShadow > 0.0) { float shadow = Get3DFilteredShadow(worldPosition, viewDirection, screenPosition, eyeIndex); return worldShadow * shadow; } - return worldShadow; + return 0.0; } float GetLightingShadow(float noise, float3 worldPosition, uint eyeIndex) From b2de122843235f8c2ab524faa87cb725bf21bb3d Mon Sep 17 00:00:00 2001 From: doodlum <15017472+doodlum@users.noreply.github.com> Date: Wed, 28 Jan 2026 15:42:40 +0000 Subject: [PATCH 17/63] fix: make rabbit happy --- package/Shaders/Common/ShadowSampling.hlsli | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/package/Shaders/Common/ShadowSampling.hlsli b/package/Shaders/Common/ShadowSampling.hlsli index 3d1feda98c..d0a92b01f0 100644 --- a/package/Shaders/Common/ShadowSampling.hlsli +++ b/package/Shaders/Common/ShadowSampling.hlsli @@ -175,7 +175,11 @@ namespace ShadowSampling return Get2DFilteredShadow(noise, rotationMatrix, worldPosition, eyeIndex); } - void ExtractLighting(float3 inputColor, out float3 dirColor, out float3 ambientColor, float skylightingDiffuse = 1.0) +#if defined(SKYLIGHTING) && !defined(INTERIOR) + void ExtractLighting(float3 inputColor, out float3 dirColor, out float3 ambientColor, float skylightingDiffuse) +#else + void ExtractLighting(float3 inputColor, out float3 dirColor, out float3 ambientColor) +#endif { float3 ambientColorAmb = max(0, mul(SharedData::DirectionalAmbient, float4(0, 0, 1, 1))); From fb6c66a86387ee990b01f3d7096cb20e945e3b66 Mon Sep 17 00:00:00 2001 From: doodlum <15017472+doodlum@users.noreply.github.com> Date: Wed, 28 Jan 2026 16:46:23 +0000 Subject: [PATCH 18/63] chore: cleaner code --- package/Shaders/Common/ShadowSampling.hlsli | 28 +++++++++++++++------ package/Shaders/Effect.hlsl | 2 +- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/package/Shaders/Common/ShadowSampling.hlsli b/package/Shaders/Common/ShadowSampling.hlsli index d0a92b01f0..53c05d4556 100644 --- a/package/Shaders/Common/ShadowSampling.hlsli +++ b/package/Shaders/Common/ShadowSampling.hlsli @@ -47,29 +47,43 @@ namespace ShadowSampling static const float rcpSampleCount = 1.0 / float(sampleCount); float noise = Random::InterleavedGradientNoise(screenPosition, SharedData::FrameCount); + float noiseTransform = noise * 2.0 - 1.0; float2 rotation; - sincos(Math::TAU * (noise * 2.0 - 1.0), rotation.y, rotation.x); + sincos(Math::TAU * noiseTransform, rotation.y, rotation.x); float2x2 rotationMatrix = float2x2(rotation.x, rotation.y, -rotation.y, rotation.x); float shadowMapDepth = GetShadowDepth(positionWS, eyeIndex); float shadow = 0.0; if (sD.EndSplitDistances.z >= shadowMapDepth) { - float cascade1BlendFactor = smoothstep(0, 1, (shadowMapDepth - sD.StartSplitDistances.y) / (sD.EndSplitDistances.x - sD.StartSplitDistances.y)); - uint cascadeIndex = noise < cascade1BlendFactor; + float cascade1Probability = saturate((shadowMapDepth - sD.StartSplitDistances.y) / (sD.EndSplitDistances.x - sD.StartSplitDistances.y)); + uint cascadeIndex = noise < cascade1Probability; // Stochastic cascade selection float compareValue = mul(transpose(sD.ShadowMapProj[eyeIndex][cascadeIndex]), float4(positionWS, 1)).z - sD.AlphaTestRef[1 + cascadeIndex]; - float sampleRadius = sD.ShadowSampleParam.z * rcp(1 + cascadeIndex) * 4.0; + float sampleRadius = sD.ShadowSampleParam.z * rcp(1 + cascadeIndex); + float viewRayLength = 128; + + // Minimum and maximum positions along view ray +#if defined(EFFECT) + // Go both forwards and backwards due to billboards + viewRayLength *= 0.5; + float3 positionLS = mul(transpose(sD.ShadowMapProj[eyeIndex][cascadeIndex]), float4(positionWS - viewDirection * viewRayLength, 1)); + float3 viewOffsetLS = mul(transpose(sD.ShadowMapProj[eyeIndex][cascadeIndex]), float4(positionWS + viewDirection * viewRayLength, 1)); +#else float3 positionLS = mul(transpose(sD.ShadowMapProj[eyeIndex][cascadeIndex]), float4(positionWS, 1)); - float3 viewOffsetLS = mul(transpose(sD.ShadowMapProj[eyeIndex][cascadeIndex]), float4(positionWS + viewDirection * 128, 1)); + float3 viewOffsetLS = mul(transpose(sD.ShadowMapProj[eyeIndex][cascadeIndex]), float4(positionWS + viewDirection * viewRayLength, 1)); +#endif for (uint i = 0; i < sampleCount; i++) { - float t = (float(i) - float(sampleCount) * 0.5) * rcpSampleCount + 0.5; - float3 sampledPositionLS = lerp(positionLS, viewOffsetLS, t); + // Random offset along view ray + float t = (float(i) - float(sampleCount)) * rcpSampleCount; + float3 sampledPositionLS = lerp(positionLS, viewOffsetLS, frac(t + noiseTransform)); + // Blur shadow with poisson disc sampledPositionLS.xy += mul(Random::PoissonSampleOffsets16[i], rotationMatrix) * sampleRadius; + // Average 4 shadow samples for improved quality float4 depths = SharedShadowMap.GatherRed(LinearSampler, float3(saturate(sampledPositionLS.xy), cascadeIndex), 0); shadow += dot(depths > compareValue, 0.25); } diff --git a/package/Shaders/Effect.hlsl b/package/Shaders/Effect.hlsl index 4ab4724e7c..3a65383693 100644 --- a/package/Shaders/Effect.hlsl +++ b/package/Shaders/Effect.hlsl @@ -562,7 +562,7 @@ float3 GetLightingColor(float3 msPosition, float3 worldPosition, float2 screenPo float shadow = ShadowSampling::GetEffectShadow(worldPosition.xyz, normalize(worldPosition.xyz), screenPosition, eyeIndex); - shadowVariance = 1.0 - saturate(fwidth(shadow)); + shadowVariance = 1.0 - sqrt(saturate(fwidth(shadow))); dirColor *= shadow; From b3d117d01526da3bcfad208938588291f5890946 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 28 Jan 2026 16:48:38 +0000 Subject: [PATCH 19/63] =?UTF-8?q?style:=20=F0=9F=8E=A8=20apply=20pre-commi?= =?UTF-8?q?t.ci=20formatting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Automated formatting by clang-format, prettier, and other hooks. See https://pre-commit.ci for details. --- package/Shaders/Common/ShadowSampling.hlsli | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/Shaders/Common/ShadowSampling.hlsli b/package/Shaders/Common/ShadowSampling.hlsli index 53c05d4556..b33a91ffe7 100644 --- a/package/Shaders/Common/ShadowSampling.hlsli +++ b/package/Shaders/Common/ShadowSampling.hlsli @@ -73,7 +73,7 @@ namespace ShadowSampling #else float3 positionLS = mul(transpose(sD.ShadowMapProj[eyeIndex][cascadeIndex]), float4(positionWS, 1)); float3 viewOffsetLS = mul(transpose(sD.ShadowMapProj[eyeIndex][cascadeIndex]), float4(positionWS + viewDirection * viewRayLength, 1)); -#endif +#endif for (uint i = 0; i < sampleCount; i++) { // Random offset along view ray From 374a0c8a0dcd3bb51a2176ab63b5cf90f4bf2383 Mon Sep 17 00:00:00 2001 From: doodlum <15017472+doodlum@users.noreply.github.com> Date: Wed, 28 Jan 2026 17:45:31 +0000 Subject: [PATCH 20/63] fix: fix shader error --- package/Shaders/Water.hlsl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/Shaders/Water.hlsl b/package/Shaders/Water.hlsl index 02bfd62727..7cc84748d5 100644 --- a/package/Shaders/Water.hlsl +++ b/package/Shaders/Water.hlsl @@ -1159,7 +1159,7 @@ PS_OUTPUT main(PS_INPUT input) # if defined(SKYLIGHTING) sh2 specularLobe = SphericalHarmonics::FauxSpecularLobe(normal, -viewDirection, 0.0); -+ skylightingSpecular = SphericalHarmonics::FuncProductIntegral(skylightingSH, specularLobe); + skylightingSpecular = SphericalHarmonics::FuncProductIntegral(skylightingSH, specularLobe); skylightingSpecular = saturate(skylightingSpecular); skylightingSpecular = Skylighting::mixSpecular(SharedData::skylightingSettings, skylightingSpecular); # endif From 302872bfc4a0a969b145facb3a372cef7d8f3d0a Mon Sep 17 00:00:00 2001 From: doodlum <15017472+doodlum@users.noreply.github.com> Date: Wed, 28 Jan 2026 19:34:29 +0000 Subject: [PATCH 21/63] chore: optimise sampling --- package/Shaders/Common/Permutation.hlsli | 2 + package/Shaders/Common/ShadowSampling.hlsli | 110 +++++++++++++------- src/Hooks.cpp | 24 +++++ src/State.h | 5 +- 4 files changed, 101 insertions(+), 40 deletions(-) diff --git a/package/Shaders/Common/Permutation.hlsli b/package/Shaders/Common/Permutation.hlsli index 3ad7dcea91..ee3e4ea1c9 100644 --- a/package/Shaders/Common/Permutation.hlsli +++ b/package/Shaders/Common/Permutation.hlsli @@ -80,6 +80,8 @@ namespace Permutation uint PixelShaderDescriptor; uint ExtraShaderDescriptor; uint ExtraFeatureDescriptor; + + float BillboardRadius; }; } diff --git a/package/Shaders/Common/ShadowSampling.hlsli b/package/Shaders/Common/ShadowSampling.hlsli index b33a91ffe7..8acd12aeb5 100644 --- a/package/Shaders/Common/ShadowSampling.hlsli +++ b/package/Shaders/Common/ShadowSampling.hlsli @@ -49,7 +49,7 @@ namespace ShadowSampling float noise = Random::InterleavedGradientNoise(screenPosition, SharedData::FrameCount); float noiseTransform = noise * 2.0 - 1.0; float2 rotation; - sincos(Math::TAU * noiseTransform, rotation.y, rotation.x); + sincos(Math::TAU * noise, rotation.y, rotation.x); float2x2 rotationMatrix = float2x2(rotation.x, rotation.y, -rotation.y, rotation.x); float shadowMapDepth = GetShadowDepth(positionWS, eyeIndex); @@ -57,28 +57,43 @@ namespace ShadowSampling float shadow = 0.0; if (sD.EndSplitDistances.z >= shadowMapDepth) { float cascade1Probability = saturate((shadowMapDepth - sD.StartSplitDistances.y) / (sD.EndSplitDistances.x - sD.StartSplitDistances.y)); - uint cascadeIndex = noise < cascade1Probability; // Stochastic cascade selection - - float compareValue = mul(transpose(sD.ShadowMapProj[eyeIndex][cascadeIndex]), float4(positionWS, 1)).z - sD.AlphaTestRef[1 + cascadeIndex]; - float sampleRadius = sD.ShadowSampleParam.z * rcp(1 + cascadeIndex); - - float viewRayLength = 128; - - // Minimum and maximum positions along view ray + + // Precompute cascade data for both cascades + float compareValues[2]; + float sampleRadii[2]; + float3 positionsLS[2]; + float3 viewOffsetsLS[2]; + + [unroll] + for (uint cascadeIdx = 0; cascadeIdx < 2; cascadeIdx++) { + compareValues[cascadeIdx] = mul(transpose(sD.ShadowMapProj[eyeIndex][cascadeIdx]), float4(positionWS, 1)).z - sD.AlphaTestRef[1 + cascadeIdx]; + sampleRadii[cascadeIdx] = sD.ShadowSampleParam.z * rcp(1 + cascadeIdx) * 2.0; + #if defined(EFFECT) - // Go both forwards and backwards due to billboards - viewRayLength *= 0.5; - float3 positionLS = mul(transpose(sD.ShadowMapProj[eyeIndex][cascadeIndex]), float4(positionWS - viewDirection * viewRayLength, 1)); - float3 viewOffsetLS = mul(transpose(sD.ShadowMapProj[eyeIndex][cascadeIndex]), float4(positionWS + viewDirection * viewRayLength, 1)); + // Base fuzziness for non-billboards + enough for Sovngarde fog + float viewRayLength = 8.0 + Permutation::BillboardRadius * 0.1; + positionsLS[cascadeIdx] = mul(transpose(sD.ShadowMapProj[eyeIndex][cascadeIdx]), float4(positionWS - viewDirection * viewRayLength, 1)); + viewOffsetsLS[cascadeIdx] = mul(transpose(sD.ShadowMapProj[eyeIndex][cascadeIdx]), float4(positionWS + viewDirection * viewRayLength, 1)); #else - float3 positionLS = mul(transpose(sD.ShadowMapProj[eyeIndex][cascadeIndex]), float4(positionWS, 1)); - float3 viewOffsetLS = mul(transpose(sD.ShadowMapProj[eyeIndex][cascadeIndex]), float4(positionWS + viewDirection * viewRayLength, 1)); + // Enough for Eastmarch water + float viewRayLength = 128.0; + positionsLS[cascadeIdx] = mul(transpose(sD.ShadowMapProj[eyeIndex][cascadeIdx]), float4(positionWS, 1)); + viewOffsetsLS[cascadeIdx] = mul(transpose(sD.ShadowMapProj[eyeIndex][cascadeIdx]), float4(positionWS + viewDirection * viewRayLength, 1)); #endif + } for (uint i = 0; i < sampleCount; i++) { - // Random offset along view ray - float t = (float(i) - float(sampleCount)) * rcpSampleCount; - float3 sampledPositionLS = lerp(positionLS, viewOffsetLS, frac(t + noiseTransform)); + uint noisyIndex = uint((float(i) + sampleCount * noise) % sampleCount); + float t = (float(sampleCount) - float(noisyIndex + 1)) * rcpSampleCount; + uint cascadeIndex = frac(t + noiseTransform) < cascade1Probability; + + float compareValue = compareValues[cascadeIndex]; + float sampleRadius = sampleRadii[cascadeIndex]; + float3 positionLS = positionsLS[cascadeIndex]; + float3 viewOffsetLS = viewOffsetsLS[cascadeIndex]; + + // Offset along view ray with optimised sample pattern + float3 sampledPositionLS = lerp(positionLS, viewOffsetLS, t + noiseTransform * rcpSampleCount); // Blur shadow with poisson disc sampledPositionLS.xy += mul(Random::PoissonSampleOffsets16[i], rotationMatrix) * sampleRadius; @@ -197,31 +212,48 @@ namespace ShadowSampling { float3 ambientColorAmb = max(0, mul(SharedData::DirectionalAmbient, float4(0, 0, 1, 1))); - # if defined(IBL) - if (SharedData::iblSettings.EnableDiffuseIBL && (!SharedData::InInterior || SharedData::iblSettings.EnableInterior)) { - ambientColorAmb *= SharedData::iblSettings.DALCAmount; - # if defined(SKYLIGHTING) && !defined(INTERIOR) - float3 iblColor = Color::Saturation(ImageBasedLighting::GetIBLColor(float3(0, 0, -1), skylightingDiffuse), SharedData::iblSettings.IBLSaturation) * SharedData::iblSettings.DiffuseIBLScale; - # else - float3 iblColor = Color::Saturation(ImageBasedLighting::GetIBLColor(float3(0, 0, -1)), SharedData::iblSettings.IBLSaturation) * SharedData::iblSettings.DiffuseIBLScale; - # endif - ambientColorAmb += Color::IrradianceToGamma(iblColor); +# if defined(IBL) + if (SharedData::iblSettings.EnableDiffuseIBL && (!SharedData::InInterior || SharedData::iblSettings.EnableInterior)) { + ambientColorAmb *= SharedData::iblSettings.DALCAmount; +# if defined(SKYLIGHTING) && !defined(INTERIOR) + float3 iblColor = Color::Saturation(ImageBasedLighting::GetIBLColor(float3(0, 0, -1), skylightingDiffuse), SharedData::iblSettings.IBLSaturation) * SharedData::iblSettings.DiffuseIBLScale; +# else + float3 iblColor = Color::Saturation(ImageBasedLighting::GetIBLColor(float3(0, 0, -1)), SharedData::iblSettings.IBLSaturation) * SharedData::iblSettings.DiffuseIBLScale; +# endif + ambientColorAmb += Color::IrradianceToGamma(iblColor); + } +# endif + + float llDirLightMult = (SharedData::linearLightingSettings.enableLinearLighting && !SharedData::linearLightingSettings.isDirLightLinear) ? SharedData::linearLightingSettings.dirLightMult : 1.0f; + float3 dirLightColorDir = Color::DirectionalLight(SharedData::DirLightColor.xyz / max(llDirLightMult, 1e-5), SharedData::linearLightingSettings.isDirLightLinear) * llDirLightMult; + + { + float maxScale = 1.0; + if (ambientColorAmb.x > 0.0) + maxScale = min(maxScale, inputColor.x / ambientColorAmb.x); + if (ambientColorAmb.y > 0.0) + maxScale = min(maxScale, inputColor.y / ambientColorAmb.y); + if (ambientColorAmb.z > 0.0) + maxScale = min(maxScale, inputColor.z / ambientColorAmb.z); + ambientColorAmb *= maxScale; } - # endif - float llDirLightMult = (SharedData::linearLightingSettings.enableLinearLighting && !SharedData::linearLightingSettings.isDirLightLinear) - ? SharedData::linearLightingSettings.dirLightMult : 1.0f; - float3 dirLightColorDir = Color::DirectionalLight(SharedData::DirLightColor.xyz / max(llDirLightMult, 1e-5), - SharedData::linearLightingSettings.isDirLightLinear) * llDirLightMult; + { + float maxScale = 1.0; + if (dirLightColorDir.x > 0.0) + maxScale = min(maxScale, inputColor.x / dirLightColorDir.x); + if (dirLightColorDir.y > 0.0) + maxScale = min(maxScale, inputColor.y / dirLightColorDir.y); + if (dirLightColorDir.z > 0.0) + maxScale = min(maxScale, inputColor.z / dirLightColorDir.z); + dirLightColorDir *= maxScale; + } - // Calculate total expected lighting and find scale to match input - float3 totalLight = ambientColorAmb + dirLightColorDir; - float3 safeTotal = max(totalLight, 1e-5); - float3 scale = inputColor / safeTotal; + float3 dirLightColorAmb = max(0.0, inputColor - ambientColorAmb); + float3 ambientColorDir = max(0.0, inputColor - dirLightColorDir); - // Distribute proportionally - ambientColor = ambientColorAmb * scale; - dirColor = dirLightColorDir * scale; + dirColor = lerp(dirLightColorAmb, dirLightColorDir, 0.5); + ambientColor = lerp(ambientColorAmb, ambientColorDir, 0.5); } } diff --git a/src/Hooks.cpp b/src/Hooks.cpp index 9c52601ab3..c3289da9b0 100644 --- a/src/Hooks.cpp +++ b/src/Hooks.cpp @@ -162,6 +162,29 @@ bool Hooks::BSShader_BeginTechnique::thunk(RE::BSShader* shader, uint32_t vertex return shaderFound; } +namespace EffectExtensions +{ + struct BSEffectShader_SetupGeometry + { + static void thunk(RE::BSShader* shader, RE::BSRenderPass* pass, uint32_t renderFlags) + { + func(shader, pass, renderFlags); + + auto state = globals::state; + + state->permutationData.BillboardRadius = 0.0f; + + if (auto node = pass->geometry->parent) { + if (node->GetRTTI() == globals::rtti::NiBillboardNodeRTTI.get()) { + auto billboardNode = static_cast(node->parent); + state->permutationData.BillboardRadius = billboardNode->worldBound.radius; + } + } + } + static inline REL::Relocation func; + }; +} + namespace LightingExtensions { struct BSLightingShader_SetupGeometry @@ -883,6 +906,7 @@ namespace Hooks stl::write_thunk_call(REL::RelocationID(31373, 32160).address() + REL::Relocate(0x1AD, 0x1CA, 0x1ed)); logger::info("Installing SetupGeometry hooks"); + stl::write_vfunc<0x6, EffectExtensions::BSEffectShader_SetupGeometry>(RE::VTABLE_BSEffectShader[0]); stl::write_vfunc<0x6, LightingExtensions::BSLightingShader_SetupGeometry>(RE::VTABLE_BSLightingShader[0]); stl::write_thunk_call(REL::RelocationID(15214, 15383).address() + REL::Relocate(0x45B, 0x4F5)); stl::write_vfunc<0x6, GrassExtensions::BSGrassShader_SetupGeometry>(RE::VTABLE_BSGrassShader[0]); diff --git a/src/State.h b/src/State.h index 971b31de73..91acfd93b8 100644 --- a/src/State.h +++ b/src/State.h @@ -181,11 +181,14 @@ class State uint ExtraShaderDescriptor; uint ExtraFeatureDescriptor; + float BillboardRadius; + float3 pad0; + bool operator==(const PermutationCB& other) const { return PixelShaderDescriptor == other.PixelShaderDescriptor && ExtraShaderDescriptor == other.ExtraShaderDescriptor && - ExtraFeatureDescriptor == other.ExtraFeatureDescriptor; + ExtraFeatureDescriptor == other.ExtraFeatureDescriptor && BillboardRadius == other.BillboardRadius; } }; STATIC_ASSERT_ALIGNAS_16(PermutationCB); From 01fef2aa0bbd41aba545311b55f43541db4c0930 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 28 Jan 2026 19:35:58 +0000 Subject: [PATCH 22/63] =?UTF-8?q?style:=20=F0=9F=8E=A8=20apply=20pre-commi?= =?UTF-8?q?t.ci=20formatting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Automated formatting by clang-format, prettier, and other hooks. See https://pre-commit.ci for details. --- package/Shaders/Common/ShadowSampling.hlsli | 6 +++--- src/Hooks.cpp | 2 +- src/State.h | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package/Shaders/Common/ShadowSampling.hlsli b/package/Shaders/Common/ShadowSampling.hlsli index 8acd12aeb5..a74a00c301 100644 --- a/package/Shaders/Common/ShadowSampling.hlsli +++ b/package/Shaders/Common/ShadowSampling.hlsli @@ -57,18 +57,18 @@ namespace ShadowSampling float shadow = 0.0; if (sD.EndSplitDistances.z >= shadowMapDepth) { float cascade1Probability = saturate((shadowMapDepth - sD.StartSplitDistances.y) / (sD.EndSplitDistances.x - sD.StartSplitDistances.y)); - + // Precompute cascade data for both cascades float compareValues[2]; float sampleRadii[2]; float3 positionsLS[2]; float3 viewOffsetsLS[2]; - + [unroll] for (uint cascadeIdx = 0; cascadeIdx < 2; cascadeIdx++) { compareValues[cascadeIdx] = mul(transpose(sD.ShadowMapProj[eyeIndex][cascadeIdx]), float4(positionWS, 1)).z - sD.AlphaTestRef[1 + cascadeIdx]; sampleRadii[cascadeIdx] = sD.ShadowSampleParam.z * rcp(1 + cascadeIdx) * 2.0; - + #if defined(EFFECT) // Base fuzziness for non-billboards + enough for Sovngarde fog float viewRayLength = 8.0 + Permutation::BillboardRadius * 0.1; diff --git a/src/Hooks.cpp b/src/Hooks.cpp index c3289da9b0..543689e2a7 100644 --- a/src/Hooks.cpp +++ b/src/Hooks.cpp @@ -177,7 +177,7 @@ namespace EffectExtensions if (auto node = pass->geometry->parent) { if (node->GetRTTI() == globals::rtti::NiBillboardNodeRTTI.get()) { auto billboardNode = static_cast(node->parent); - state->permutationData.BillboardRadius = billboardNode->worldBound.radius; + state->permutationData.BillboardRadius = billboardNode->worldBound.radius; } } } diff --git a/src/State.h b/src/State.h index 91acfd93b8..bb7e3238d3 100644 --- a/src/State.h +++ b/src/State.h @@ -183,7 +183,7 @@ class State float BillboardRadius; float3 pad0; - + bool operator==(const PermutationCB& other) const { return PixelShaderDescriptor == other.PixelShaderDescriptor && From f8eac9838a3bc721cce7530e795c0dcced521c84 Mon Sep 17 00:00:00 2001 From: doodlum <15017472+doodlum@users.noreply.github.com> Date: Wed, 28 Jan 2026 20:22:47 +0000 Subject: [PATCH 23/63] Update src/Hooks.cpp Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- src/Hooks.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Hooks.cpp b/src/Hooks.cpp index 543689e2a7..7708335a75 100644 --- a/src/Hooks.cpp +++ b/src/Hooks.cpp @@ -176,10 +176,11 @@ namespace EffectExtensions if (auto node = pass->geometry->parent) { if (node->GetRTTI() == globals::rtti::NiBillboardNodeRTTI.get()) { - auto billboardNode = static_cast(node->parent); + auto billboardNode = static_cast(node); state->permutationData.BillboardRadius = billboardNode->worldBound.radius; } } + } } static inline REL::Relocation func; }; From 2160b5bbad331682898d9abeaa7defdea968680b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 28 Jan 2026 20:23:10 +0000 Subject: [PATCH 24/63] =?UTF-8?q?style:=20=F0=9F=8E=A8=20apply=20pre-commi?= =?UTF-8?q?t.ci=20formatting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Automated formatting by clang-format, prettier, and other hooks. See https://pre-commit.ci for details. --- src/Hooks.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Hooks.cpp b/src/Hooks.cpp index 7708335a75..01094e1275 100644 --- a/src/Hooks.cpp +++ b/src/Hooks.cpp @@ -180,10 +180,9 @@ namespace EffectExtensions state->permutationData.BillboardRadius = billboardNode->worldBound.radius; } } - } } - static inline REL::Relocation func; - }; + } static inline REL::Relocation func; +}; } namespace LightingExtensions From b5cade5c7f07032990b1658a0bc270555adca3bd Mon Sep 17 00:00:00 2001 From: doodlum <15017472+doodlum@users.noreply.github.com> Date: Wed, 28 Jan 2026 20:49:16 +0000 Subject: [PATCH 25/63] fix: fix compile issue --- package/Shaders/Water.hlsl | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/package/Shaders/Water.hlsl b/package/Shaders/Water.hlsl index 7cc84748d5..789b9d0ece 100644 --- a/package/Shaders/Water.hlsl +++ b/package/Shaders/Water.hlsl @@ -434,18 +434,10 @@ float CalculateDepthMultFromUV(float2 uv, float depth, uint eyeIndex = 0) # define SampColorSampler Normals01Sampler # define LinearSampler Normals01Sampler -# if defined(TERRAIN_SHADOWS) -# include "TerrainShadows/TerrainShadows.hlsli" -# endif - # if defined(SKYLIGHTING) # include "Skylighting/Skylighting.hlsli" # endif -# if defined(CLOUD_SHADOWS) -# include "CloudShadows/CloudShadows.hlsli" -# endif - # include "Common/ShadowSampling.hlsli" # if defined(SIMPLE) || defined(UNDERWATER) || defined(LOD) || defined(SPECULAR) @@ -1128,8 +1120,6 @@ PS_OUTPUT main(PS_INPUT input) float3 viewPosition = mul(FrameBuffer::CameraView[eyeIndex], float4(input.WPosition.xyz, 1)).xyz; float2 screenUV = FrameBuffer::ViewToUV(viewPosition, true, eyeIndex); - float dirShadow = ShadowSampling::GetEffectShadow(input.WPosition.xyz, normalize(input.WPosition.xyz), input.HPosition.xy, eyeIndex); - float skylightingDiffuse = 1.0; float skylightingSpecular = 1.0; float wetnessOcclusion = 1.0; @@ -1194,6 +1184,8 @@ PS_OUTPUT main(PS_INPUT input) float3 specularColor = GetWaterSpecularColor(input, normal, viewDirection, distanceFactor, depthControl.y, eyeIndex, skylightingSpecular); DiffuseOutput diffuseOutput = GetWaterDiffuseColor(input, normal, viewDirection, distanceMul, depthControl.y, fresnel, eyeIndex, viewPosition, depth); + float dirShadow = ShadowSampling::GetEffectShadow(input.WPosition.xyz, normalize(input.WPosition.xyz), input.HPosition.xy, eyeIndex); + float3 dirColor; float3 ambientColor; # if defined(SKYLIGHTING) && !defined(INTERIOR) From 7abdb94feba9c1d33a80aa3e5a8c56d7ec334910 Mon Sep 17 00:00:00 2001 From: doodlum <15017472+doodlum@users.noreply.github.com> Date: Wed, 28 Jan 2026 20:49:41 +0000 Subject: [PATCH 26/63] chore: minor optimisations --- package/Shaders/Common/ShadowSampling.hlsli | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/package/Shaders/Common/ShadowSampling.hlsli b/package/Shaders/Common/ShadowSampling.hlsli index a74a00c301..1159668f34 100644 --- a/package/Shaders/Common/ShadowSampling.hlsli +++ b/package/Shaders/Common/ShadowSampling.hlsli @@ -43,7 +43,7 @@ namespace ShadowSampling { ShadowData sD = SharedShadowData[0]; - static const uint sampleCount = 16; + static const uint sampleCount = 8; static const float rcpSampleCount = 1.0 / float(sampleCount); float noise = Random::InterleavedGradientNoise(screenPosition, SharedData::FrameCount); @@ -57,21 +57,21 @@ namespace ShadowSampling float shadow = 0.0; if (sD.EndSplitDistances.z >= shadowMapDepth) { float cascade1Probability = saturate((shadowMapDepth - sD.StartSplitDistances.y) / (sD.EndSplitDistances.x - sD.StartSplitDistances.y)); - + // Precompute cascade data for both cascades float compareValues[2]; float sampleRadii[2]; float3 positionsLS[2]; float3 viewOffsetsLS[2]; - + [unroll] for (uint cascadeIdx = 0; cascadeIdx < 2; cascadeIdx++) { compareValues[cascadeIdx] = mul(transpose(sD.ShadowMapProj[eyeIndex][cascadeIdx]), float4(positionWS, 1)).z - sD.AlphaTestRef[1 + cascadeIdx]; sampleRadii[cascadeIdx] = sD.ShadowSampleParam.z * rcp(1 + cascadeIdx) * 2.0; - + #if defined(EFFECT) - // Base fuzziness for non-billboards + enough for Sovngarde fog - float viewRayLength = 8.0 + Permutation::BillboardRadius * 0.1; + // Enough for non-billboards + enough for Sovngarde fog + float viewRayLength = 16.0 + Permutation::BillboardRadius * 0.1; positionsLS[cascadeIdx] = mul(transpose(sD.ShadowMapProj[eyeIndex][cascadeIdx]), float4(positionWS - viewDirection * viewRayLength, 1)); viewOffsetsLS[cascadeIdx] = mul(transpose(sD.ShadowMapProj[eyeIndex][cascadeIdx]), float4(positionWS + viewDirection * viewRayLength, 1)); #else @@ -85,7 +85,7 @@ namespace ShadowSampling for (uint i = 0; i < sampleCount; i++) { uint noisyIndex = uint((float(i) + sampleCount * noise) % sampleCount); float t = (float(sampleCount) - float(noisyIndex + 1)) * rcpSampleCount; - uint cascadeIndex = frac(t + noiseTransform) < cascade1Probability; + uint cascadeIndex = frac(t + noise) < cascade1Probability; float compareValue = compareValues[cascadeIndex]; float sampleRadius = sampleRadii[cascadeIndex]; @@ -96,7 +96,7 @@ namespace ShadowSampling float3 sampledPositionLS = lerp(positionLS, viewOffsetLS, t + noiseTransform * rcpSampleCount); // Blur shadow with poisson disc - sampledPositionLS.xy += mul(Random::PoissonSampleOffsets16[i], rotationMatrix) * sampleRadius; + sampledPositionLS.xy += mul(Random::SpiralSampleOffsets8[i], rotationMatrix) * sampleRadius; // Average 4 shadow samples for improved quality float4 depths = SharedShadowMap.GatherRed(LinearSampler, float3(saturate(sampledPositionLS.xy), cascadeIndex), 0); From 09ffee3b0231ae35f31bdf216d2834ac7a0729a3 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 28 Jan 2026 20:51:01 +0000 Subject: [PATCH 27/63] =?UTF-8?q?style:=20=F0=9F=8E=A8=20apply=20pre-commi?= =?UTF-8?q?t.ci=20formatting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Automated formatting by clang-format, prettier, and other hooks. See https://pre-commit.ci for details. --- package/Shaders/Common/ShadowSampling.hlsli | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package/Shaders/Common/ShadowSampling.hlsli b/package/Shaders/Common/ShadowSampling.hlsli index 1159668f34..9e3a0d9524 100644 --- a/package/Shaders/Common/ShadowSampling.hlsli +++ b/package/Shaders/Common/ShadowSampling.hlsli @@ -57,18 +57,18 @@ namespace ShadowSampling float shadow = 0.0; if (sD.EndSplitDistances.z >= shadowMapDepth) { float cascade1Probability = saturate((shadowMapDepth - sD.StartSplitDistances.y) / (sD.EndSplitDistances.x - sD.StartSplitDistances.y)); - + // Precompute cascade data for both cascades float compareValues[2]; float sampleRadii[2]; float3 positionsLS[2]; float3 viewOffsetsLS[2]; - + [unroll] for (uint cascadeIdx = 0; cascadeIdx < 2; cascadeIdx++) { compareValues[cascadeIdx] = mul(transpose(sD.ShadowMapProj[eyeIndex][cascadeIdx]), float4(positionWS, 1)).z - sD.AlphaTestRef[1 + cascadeIdx]; sampleRadii[cascadeIdx] = sD.ShadowSampleParam.z * rcp(1 + cascadeIdx) * 2.0; - + #if defined(EFFECT) // Enough for non-billboards + enough for Sovngarde fog float viewRayLength = 16.0 + Permutation::BillboardRadius * 0.1; From 31884799351d81ece3a57d3e6b732d4fc2868ea2 Mon Sep 17 00:00:00 2001 From: doodlum <15017472+doodlum@users.noreply.github.com> Date: Wed, 28 Jan 2026 21:21:46 +0000 Subject: [PATCH 28/63] chore: clean up macros --- package/Shaders/Common/ShadowSampling.hlsli | 8 ++++++++ package/Shaders/DistantTree.hlsl | 8 -------- package/Shaders/Effect.hlsl | 8 -------- package/Shaders/ISVolumetricLightingGenerateCS.hlsl | 8 -------- package/Shaders/Lighting.hlsl | 8 -------- package/Shaders/RunGrass.hlsl | 8 -------- 6 files changed, 8 insertions(+), 40 deletions(-) diff --git a/package/Shaders/Common/ShadowSampling.hlsli b/package/Shaders/Common/ShadowSampling.hlsli index 1159668f34..68ed259624 100644 --- a/package/Shaders/Common/ShadowSampling.hlsli +++ b/package/Shaders/Common/ShadowSampling.hlsli @@ -6,6 +6,14 @@ #include "Common/SharedData.hlsli" #include "Common/Color.hlsli" +# if defined(TERRAIN_SHADOWS) +# include "TerrainShadows/TerrainShadows.hlsli" +# endif + +# if defined(CLOUD_SHADOWS) +# include "CloudShadows/CloudShadows.hlsli" +# endif + #if defined(IBL) # include "IBL/IBL.hlsli" #endif diff --git a/package/Shaders/DistantTree.hlsl b/package/Shaders/DistantTree.hlsl index 82c173308d..712dc0f716 100644 --- a/package/Shaders/DistantTree.hlsl +++ b/package/Shaders/DistantTree.hlsl @@ -167,14 +167,6 @@ const static float DepthOffsets[16] = { # include "ScreenSpaceShadows/ScreenSpaceShadows.hlsli" # endif -# if defined(TERRAIN_SHADOWS) -# include "TerrainShadows/TerrainShadows.hlsli" -# endif - -# if defined(CLOUD_SHADOWS) -# include "CloudShadows/CloudShadows.hlsli" -# endif - # if defined(IBL) # include "IBL/IBL.hlsli" # endif diff --git a/package/Shaders/Effect.hlsl b/package/Shaders/Effect.hlsl index 3a65383693..0f132efa66 100644 --- a/package/Shaders/Effect.hlsl +++ b/package/Shaders/Effect.hlsl @@ -514,14 +514,6 @@ cbuffer PerGeometry : register(b2) # define LinearSampler SampBaseSampler -# if defined(TERRAIN_SHADOWS) -# include "TerrainShadows/TerrainShadows.hlsli" -# endif - -# if defined(CLOUD_SHADOWS) -# include "CloudShadows/CloudShadows.hlsli" -# endif - # if defined(SKYLIGHTING) # include "Skylighting/Skylighting.hlsli" # endif diff --git a/package/Shaders/ISVolumetricLightingGenerateCS.hlsl b/package/Shaders/ISVolumetricLightingGenerateCS.hlsl index 01372dedb4..23053313c4 100644 --- a/package/Shaders/ISVolumetricLightingGenerateCS.hlsl +++ b/package/Shaders/ISVolumetricLightingGenerateCS.hlsl @@ -20,14 +20,6 @@ RWTexture3D DensityRW : register(u0); # include "Common/Framebuffer.hlsli" # include "Common/SharedData.hlsli" -# if defined(TERRAIN_SHADOWS) -# include "TerrainShadows/TerrainShadows.hlsli" -# endif - -# if defined(CLOUD_SHADOWS) -# include "CloudShadows/CloudShadows.hlsli" -# endif - # include "Common/ShadowSampling.hlsli" cbuffer PerTechnique : register(b0) diff --git a/package/Shaders/Lighting.hlsl b/package/Shaders/Lighting.hlsl index 13c7ab4516..74b6bd480e 100644 --- a/package/Shaders/Lighting.hlsl +++ b/package/Shaders/Lighting.hlsl @@ -905,14 +905,6 @@ float GetSnowParameterY(float texProjTmp, float alpha) # undef SOFT_LIGHTING # endif -# if defined(TERRAIN_SHADOWS) -# include "TerrainShadows/TerrainShadows.hlsli" -# endif - -# if defined(CLOUD_SHADOWS) -# include "CloudShadows/CloudShadows.hlsli" -# endif - # if defined(SKYLIGHTING) # include "Skylighting/Skylighting.hlsli" # endif diff --git a/package/Shaders/RunGrass.hlsl b/package/Shaders/RunGrass.hlsl index 08f60479d1..f8488d1a3b 100644 --- a/package/Shaders/RunGrass.hlsl +++ b/package/Shaders/RunGrass.hlsl @@ -434,14 +434,6 @@ cbuffer AlphaTestRefCB : register(b11) # include "DynamicCubemaps/DynamicCubemaps.hlsli" # endif -# if defined(TERRAIN_SHADOWS) -# include "TerrainShadows/TerrainShadows.hlsli" -# endif - -# if defined(CLOUD_SHADOWS) -# include "CloudShadows/CloudShadows.hlsli" -# endif - # if defined(SKYLIGHTING) # include "Skylighting/Skylighting.hlsli" # endif From 50a9516ee68f3908a45b379589d3b45eb7c1e3a5 Mon Sep 17 00:00:00 2001 From: doodlum <15017472+doodlum@users.noreply.github.com> Date: Wed, 28 Jan 2026 21:49:14 +0000 Subject: [PATCH 29/63] chore: cleanup --- package/Shaders/Water.hlsl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/Shaders/Water.hlsl b/package/Shaders/Water.hlsl index 789b9d0ece..cd1a34e41d 100644 --- a/package/Shaders/Water.hlsl +++ b/package/Shaders/Water.hlsl @@ -1184,7 +1184,7 @@ PS_OUTPUT main(PS_INPUT input) float3 specularColor = GetWaterSpecularColor(input, normal, viewDirection, distanceFactor, depthControl.y, eyeIndex, skylightingSpecular); DiffuseOutput diffuseOutput = GetWaterDiffuseColor(input, normal, viewDirection, distanceMul, depthControl.y, fresnel, eyeIndex, viewPosition, depth); - float dirShadow = ShadowSampling::GetEffectShadow(input.WPosition.xyz, normalize(input.WPosition.xyz), input.HPosition.xy, eyeIndex); + float dirShadow = ShadowSampling::GetEffectShadow(input.WPosition.xyz, viewDirection, input.HPosition.xy, eyeIndex); float3 dirColor; float3 ambientColor; From d45b5fad384cdd4fde960fa574fa9126744ba195 Mon Sep 17 00:00:00 2001 From: doodlum <15017472+doodlum@users.noreply.github.com> Date: Wed, 28 Jan 2026 23:09:14 +0000 Subject: [PATCH 30/63] fix: fix water compiler errors --- package/Shaders/Common/ShadowSampling.hlsli | 20 ++- package/Shaders/Water.hlsl | 137 +++++++++----------- 2 files changed, 69 insertions(+), 88 deletions(-) diff --git a/package/Shaders/Common/ShadowSampling.hlsli b/package/Shaders/Common/ShadowSampling.hlsli index 2fc3a588d5..eba14ea8f9 100644 --- a/package/Shaders/Common/ShadowSampling.hlsli +++ b/package/Shaders/Common/ShadowSampling.hlsli @@ -6,13 +6,13 @@ #include "Common/SharedData.hlsli" #include "Common/Color.hlsli" -# if defined(TERRAIN_SHADOWS) -# include "TerrainShadows/TerrainShadows.hlsli" -# endif +#if defined(TERRAIN_SHADOWS) +# include "TerrainShadows/TerrainShadows.hlsli" +#endif -# if defined(CLOUD_SHADOWS) -# include "CloudShadows/CloudShadows.hlsli" -# endif +#if defined(CLOUD_SHADOWS) +# include "CloudShadows/CloudShadows.hlsli" +#endif #if defined(IBL) # include "IBL/IBL.hlsli" @@ -197,11 +197,9 @@ namespace ShadowSampling float GetEffectShadow(float3 worldPosition, float3 viewDirection, float2 screenPosition, uint eyeIndex) { float worldShadow = GetWorldShadow(worldPosition, FrameBuffer::CameraPosAdjust[eyeIndex].xyz, eyeIndex); - [branch] if (worldShadow > 0.0) { - float shadow = Get3DFilteredShadow(worldPosition, viewDirection, screenPosition, eyeIndex); - return worldShadow * shadow; - } - return 0.0; + if (worldShadow == 0.0) + return 0.0; + return worldShadow * Get3DFilteredShadow(worldPosition, viewDirection, screenPosition, eyeIndex); } float GetLightingShadow(float noise, float3 worldPosition, uint eyeIndex) diff --git a/package/Shaders/Water.hlsl b/package/Shaders/Water.hlsl index cd1a34e41d..a7fefe8031 100644 --- a/package/Shaders/Water.hlsl +++ b/package/Shaders/Water.hlsl @@ -841,84 +841,60 @@ WaterNormalData GetWaterNormal(PS_INPUT input, float distanceFactor, float norma return result; } -float3 GetWaterSpecularColor(PS_INPUT input, float3 normal, float3 viewDirection, - float distanceFactor, float refractionsDepthFactor, uint eyeIndex, float skylightingSpecular) +float3 GetWaterSpecularColor(PS_INPUT input, float3 normal, float3 viewDirection, float distanceFactor, float skylightingSpecular) { - if (Permutation::PixelShaderDescriptor & Permutation::WaterFlags::Reflections) { - float3 finalSsrReflectionColor = 0.0.xxx; - float ssrFraction = 0.0; - float3 reflectionColor = 0.0.xxx; - float3 R = reflect(viewDirection, WaterParams.y * normal + float3(0, 0, 1 - WaterParams.y)); + if (!(Permutation::PixelShaderDescriptor & Permutation::WaterFlags::Reflections)) + return ReflectionColor.xyz * VarAmounts.y; - if (Permutation::PixelShaderDescriptor & Permutation::WaterFlags::Cubemap) { -# if defined(DYNAMIC_CUBEMAPS) - float3 dynamicCubemap; - if (SharedData::InInterior) { - dynamicCubemap = DynamicCubemaps::EnvTexture.SampleLevel(CubeMapSampler, R, 0).xyz; - } else { - float3 specularIrradiance = 1; + float3 R = reflect(viewDirection, WaterParams.y * normal + float3(0, 0, 1 - WaterParams.y)); + float3 reflectionColor = CubeMapTex.SampleLevel(CubeMapSampler, R, 0).xyz; - if (skylightingSpecular < 1.0) { - specularIrradiance = Color::IrradianceToLinear(DynamicCubemaps::EnvTexture.SampleLevel(CubeMapSampler, R, 0).xyz); - } +# if defined(DYNAMIC_CUBEMAPS) + float3 dynamicCubemap; + if (SharedData::InInterior) { + dynamicCubemap = DynamicCubemaps::EnvTexture.SampleLevel(CubeMapSampler, R, 0).xyz; + } else { + float3 specularIrradiance = 1.0; + if (skylightingSpecular < 1.0) + specularIrradiance = Color::IrradianceToLinear(DynamicCubemaps::EnvTexture.SampleLevel(CubeMapSampler, R, 0).xyz); - float3 specularIrradianceReflections = 1.0; + float3 specularIrradianceReflections = 1.0; + if (skylightingSpecular > 0.0) + specularIrradianceReflections = Color::IrradianceToLinear(DynamicCubemaps::EnvReflectionsTexture.SampleLevel(CubeMapSampler, R, 0).xyz); - if (skylightingSpecular > 0.0) { - specularIrradianceReflections = Color::IrradianceToLinear(DynamicCubemaps::EnvReflectionsTexture.SampleLevel(CubeMapSampler, R, 0).xyz); - } + dynamicCubemap = Color::IrradianceToGamma(lerp(specularIrradiance, specularIrradianceReflections, skylightingSpecular)); + } - dynamicCubemap = Color::IrradianceToGamma(lerp(specularIrradiance, specularIrradianceReflections, skylightingSpecular)); - } + float reflectionAmount = saturate(length(input.WPosition.xyz) / 1024.0); -# if defined(VR) - // Reflection cubemap is incorrect for interiors in VR, ignore it - if (Permutation::PixelShaderDescriptor & Permutation::WaterFlags::Interior || SharedData::HideSky) - reflectionColor = dynamicCubemap.xyz; - else - reflectionColor = lerp(dynamicCubemap.xyz, CubeMapTex.SampleLevel(CubeMapSampler, R, 0).xyz, saturate(length(input.WPosition.xyz) / 1024.0)); -# else - if (SharedData::HideSky) - reflectionColor = dynamicCubemap.xyz; - else - reflectionColor = lerp(dynamicCubemap.xyz, CubeMapTex.SampleLevel(CubeMapSampler, R, 0).xyz, saturate(length(input.WPosition.xyz) / 1024.0)); -# endif +# if defined(VR) + // Reflection cubemap is incorrect for interiors in VR, ignore it + if (Permutation::PixelShaderDescriptor & Permutation::WaterFlags::Interior || SharedData::HideSky) + reflectionAmount = 0.0; # else - reflectionColor = CubeMapTex.SampleLevel(CubeMapSampler, R, 0).xyz; -# endif - } else { -# if !defined(LOD) && NUM_SPECULAR_LIGHTS == 0 - float4 reflectionNormalRaw = float4((VarAmounts.w * refractionsDepthFactor) * normal.xy + input.MPosition.xy, input.MPosition.z, 1); -# else - float4 reflectionNormalRaw = float4(VarAmounts.w * normal.xy, 0, 1); -# endif - - float4 reflectionNormal = mul(transpose(TextureProj[eyeIndex]), reflectionNormalRaw); - reflectionColor = ReflectionTex.SampleLevel(ReflectionSampler, reflectionNormal.xy / reflectionNormal.ww, 0).xyz; - } - -# if !defined(LOD) && NUM_SPECULAR_LIGHTS == 0 - if (Permutation::PixelShaderDescriptor & Permutation::WaterFlags::Cubemap) { - float pointingDirection = dot(viewDirection, R); - float pointingAlignment = dot(reflect(viewDirection, float3(0, 0, 1)), R); - float ssrAmount = min(pointingAlignment, pointingDirection); - if (SSRParams.x > 0.0 && ssrAmount > 0.0) { - float2 ssrReflectionUv = ((FrameBuffer::DynamicResolutionParams2.xy * input.HPosition.xy) * SSRParams.zw) + 0.05 * normal.xy; - float2 ssrReflectionUvDR = FrameBuffer::GetDynamicResolutionAdjustedScreenPosition(ssrReflectionUv); - float4 ssrReflectionColorBlurred = RawSSRReflectionTex.Sample(RawSSRReflectionSampler, ssrReflectionUvDR); - float4 ssrReflectionColorRaw = RawSSRReflectionTex.Sample(RawSSRReflectionSampler, ssrReflectionUvDR); - float4 ssrReflectionColor = lerp(ssrReflectionColorBlurred, ssrReflectionColorRaw, ssrAmount * 0.7); - - finalSsrReflectionColor = max(0, ssrReflectionColor.xyz); - ssrFraction = saturate(ssrReflectionColor.w * distanceFactor * ssrAmount); - } - } + if (SharedData::HideSky) + reflectionAmount = 0.0; # endif + reflectionColor = lerp(dynamicCubemap, reflectionColor, reflectionAmount); +# endif - float3 finalReflectionColor = Color::IrradianceToGamma(lerp(Color::IrradianceToLinear(reflectionColor), Color::IrradianceToLinear(finalSsrReflectionColor), ssrFraction)); - return finalReflectionColor; +# if !defined(LOD) && NUM_SPECULAR_LIGHTS == 0 + float pointingDirection = dot(viewDirection, R); + float pointingAlignment = dot(reflect(viewDirection, float3(0, 0, 1)), R); + float ssrAmount = min(pointingAlignment, pointingDirection); + if (SSRParams.x > 0.0 && ssrAmount > 0.0) { + float2 ssrReflectionUv = ((FrameBuffer::DynamicResolutionParams2.xy * input.HPosition.xy) * SSRParams.zw) + 0.05 * normal.xy; + float2 ssrReflectionUvDR = FrameBuffer::GetDynamicResolutionAdjustedScreenPosition(ssrReflectionUv); + float4 ssrReflectionColorBlurred = RawSSRReflectionTex.Sample(RawSSRReflectionSampler, ssrReflectionUvDR); + float4 ssrReflectionColorRaw = RawSSRReflectionTex.Sample(RawSSRReflectionSampler, ssrReflectionUvDR); + float4 ssrReflectionColor = lerp(ssrReflectionColorBlurred, ssrReflectionColorRaw, ssrAmount * 0.7); + float3 finalSsrReflectionColor = max(0, ssrReflectionColor.xyz); + float ssrFraction = saturate(ssrReflectionColor.w * distanceFactor * ssrAmount); + reflectionColor = lerp(Color::IrradianceToLinear(reflectionColor), Color::IrradianceToLinear(finalSsrReflectionColor), ssrFraction); } - return ReflectionColor.xyz * VarAmounts.y; +# endif + + return reflectionColor; } float GetScreenDepthWater(float2 screenPosition, uint a_useVR = 0) @@ -1119,13 +1095,11 @@ PS_OUTPUT main(PS_INPUT input) # endif float3 viewPosition = mul(FrameBuffer::CameraView[eyeIndex], float4(input.WPosition.xyz, 1)).xyz; float2 screenUV = FrameBuffer::ViewToUV(viewPosition, true, eyeIndex); + const bool inWorld = (Permutation::ExtraShaderDescriptor & Permutation::ExtraFlags::InWorld); - float skylightingDiffuse = 1.0; - float skylightingSpecular = 1.0; +# if defined(SKYLIGHTING) float wetnessOcclusion = 1.0; -# if defined(SKYLIGHTING) - const bool inWorld = (Permutation::ExtraShaderDescriptor & Permutation::ExtraFlags::InWorld); # if defined(VR) float3 positionMSSkylight = input.WPosition.xyz + FrameBuffer::CameraPosAdjust[eyeIndex].xyz - FrameBuffer::CameraPosAdjust[0].xyz; # else @@ -1135,7 +1109,7 @@ PS_OUTPUT main(PS_INPUT input) sh2 skylightingSH = Skylighting::sampleNoBias(SharedData::skylightingSettings, Skylighting::SkylightingProbeArray, positionMSSkylight); float skylighting = SphericalHarmonics::Unproject(skylightingSH, float3(0, 0, 1)); - skylightingDiffuse = SphericalHarmonics::FuncProductIntegral(skylightingSH, SphericalHarmonics::EvaluateCosineLobe(float3(0, 0, 1))) / Math::PI; + float skylightingDiffuse = SphericalHarmonics::FuncProductIntegral(skylightingSH, SphericalHarmonics::EvaluateCosineLobe(float3(0, 0, 1))) / Math::PI; skylightingDiffuse = saturate(skylightingDiffuse); skylightingDiffuse = lerp(1.0, skylightingDiffuse, Skylighting::getFadeOutFactor(input.WPosition.xyz)); skylightingDiffuse = Skylighting::mixDiffuse(SharedData::skylightingSettings, skylightingDiffuse); @@ -1143,13 +1117,17 @@ PS_OUTPUT main(PS_INPUT input) wetnessOcclusion = inWorld ? pow(saturate(skylighting), 2) : 0; # endif +#if defined(SKYLIGHTING) WaterNormalData waterData = GetWaterNormal(input, distanceFactor, depthControl.z, viewDirection, depth, eyeIndex, wetnessOcclusion); +#else + WaterNormalData waterData = GetWaterNormal(input, distanceFactor, depthControl.z, viewDirection, depth, eyeIndex, inWorld); +#endif float3 normal = waterData.normal; # if defined(SKYLIGHTING) sh2 specularLobe = SphericalHarmonics::FauxSpecularLobe(normal, -viewDirection, 0.0); - skylightingSpecular = SphericalHarmonics::FuncProductIntegral(skylightingSH, specularLobe); + float skylightingSpecular = SphericalHarmonics::FuncProductIntegral(skylightingSH, specularLobe); skylightingSpecular = saturate(skylightingSpecular); skylightingSpecular = Skylighting::mixSpecular(SharedData::skylightingSettings, skylightingSpecular); # endif @@ -1159,7 +1137,7 @@ PS_OUTPUT main(PS_INPUT input) # if defined(SPECULAR) && (NUM_SPECULAR_LIGHTS != 0) float3 finalColor = 0.0.xxx; - for (int lightIndex = 0; lightIndex < NUM_SPECULAR_LIGHTS; ++lightIndex) { + [unroll] for (int lightIndex = 0; lightIndex < NUM_SPECULAR_LIGHTS; ++lightIndex) { float3 lightVector = LightPos[lightIndex].xyz - (PosAdjust[eyeIndex].xyz + input.WPosition.xyz); float3 lightDirection = normalize(normalize(lightVector) - viewDirection); float lightFade = saturate(length(lightVector) / LightPos[lightIndex].w); @@ -1181,7 +1159,12 @@ PS_OUTPUT main(PS_INPUT input) isSpecular = true; # else - float3 specularColor = GetWaterSpecularColor(input, normal, viewDirection, distanceFactor, depthControl.y, eyeIndex, skylightingSpecular); +#if defined(SKYLIGHTING) + float3 specularColor = GetWaterSpecularColor(input, normal, viewDirection, distanceFactor, skylightingSpecular); +#else + float3 specularColor = GetWaterSpecularColor(input, normal, viewDirection, distanceFactor, 1.0); +#endif + DiffuseOutput diffuseOutput = GetWaterDiffuseColor(input, normal, viewDirection, distanceMul, depthControl.y, fresnel, eyeIndex, viewPosition, depth); float dirShadow = ShadowSampling::GetEffectShadow(input.WPosition.xyz, viewDirection, input.HPosition.xy, eyeIndex); @@ -1280,7 +1263,7 @@ PS_OUTPUT main(PS_INPUT input) } # endif -float3 finalColor = lerp(finalColorPreFog, fogColor * PosAdjust[eyeIndex].w, Color::FogAlpha(fogDistanceFactor)); + float3 finalColor = lerp(finalColorPreFog, fogColor * PosAdjust[eyeIndex].w, Color::FogAlpha(fogDistanceFactor)); # if defined(WETNESS_EFFECTS) && defined(DEBUG_WETNESS_EFFECTS) // DEBUG MODE: Override water color with debug visualization @@ -1308,7 +1291,7 @@ float3 finalColor = lerp(finalColorPreFog, fogColor * PosAdjust[eyeIndex].w, Col } # endif -finalColorPreFog = lerp(finalColorPreFog, preFogColor * PosAdjust[eyeIndex].w, Color::FogAlpha(fogDistanceFactor)); + finalColorPreFog = lerp(finalColorPreFog, preFogColor * PosAdjust[eyeIndex].w, Color::FogAlpha(fogDistanceFactor)); float3 refractionColor = diffuseOutput.refractionColor; From 5ea57155a4e25a846020ef281209cca602a80826 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 28 Jan 2026 23:10:46 +0000 Subject: [PATCH 31/63] =?UTF-8?q?style:=20=F0=9F=8E=A8=20apply=20pre-commi?= =?UTF-8?q?t.ci=20formatting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Automated formatting by clang-format, prettier, and other hooks. See https://pre-commit.ci for details. --- package/Shaders/Water.hlsl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/Shaders/Water.hlsl b/package/Shaders/Water.hlsl index a7fefe8031..d38b3755b2 100644 --- a/package/Shaders/Water.hlsl +++ b/package/Shaders/Water.hlsl @@ -843,7 +843,7 @@ WaterNormalData GetWaterNormal(PS_INPUT input, float distanceFactor, float norma float3 GetWaterSpecularColor(PS_INPUT input, float3 normal, float3 viewDirection, float distanceFactor, float skylightingSpecular) { - if (!(Permutation::PixelShaderDescriptor & Permutation::WaterFlags::Reflections)) + if (!(Permutation::PixelShaderDescriptor & Permutation::WaterFlags::Reflections)) return ReflectionColor.xyz * VarAmounts.y; float3 R = reflect(viewDirection, WaterParams.y * normal + float3(0, 0, 1 - WaterParams.y)); From 026f52527ed21b5ff791e5141844ffcdaa8dfb1c Mon Sep 17 00:00:00 2001 From: doodlum <15017472+doodlum@users.noreply.github.com> Date: Wed, 28 Jan 2026 23:29:54 +0000 Subject: [PATCH 32/63] fix: fix terrain shadows compile error --- .../Shaders/Features/TerrainShadows.ini | 2 +- .../TerrainShadows/TerrainShadows.hlsli | 21 +++++++++---------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/features/Terrain Shadows/Shaders/Features/TerrainShadows.ini b/features/Terrain Shadows/Shaders/Features/TerrainShadows.ini index 735cfd23a9..01aaedc093 100644 --- a/features/Terrain Shadows/Shaders/Features/TerrainShadows.ini +++ b/features/Terrain Shadows/Shaders/Features/TerrainShadows.ini @@ -1,2 +1,2 @@ [Info] -Version = 1-0-1 \ No newline at end of file +Version = 1-0-2 \ No newline at end of file diff --git a/features/Terrain Shadows/Shaders/TerrainShadows/TerrainShadows.hlsli b/features/Terrain Shadows/Shaders/TerrainShadows/TerrainShadows.hlsli index 3f01b0a0bc..83cf62d32e 100644 --- a/features/Terrain Shadows/Shaders/TerrainShadows/TerrainShadows.hlsli +++ b/features/Terrain Shadows/Shaders/TerrainShadows/TerrainShadows.hlsli @@ -19,19 +19,18 @@ namespace TerrainShadows float GetTerrainShadow(const float3 worldPos, SamplerState samp) { - if (SharedData::terraOccSettings.EnableTerrainShadow) { - float2 terraOccUV = GetTerrainShadowUV(worldPos.xy); - float2 shadowHeight = GetTerrainZ(ShadowHeightTexture.SampleLevel(samp, terraOccUV, 0)); + if (!SharedData::terraOccSettings.EnableTerrainShadow) + return 1.0; + + float2 terraOccUV = GetTerrainShadowUV(worldPos.xy); + float2 shadowHeight = GetTerrainZ(ShadowHeightTexture.SampleLevel(samp, terraOccUV, 0)); #if defined(DEFERRED) - // Sharp shadows - float shadowFraction = saturate(10.0 * (worldPos.z - shadowHeight.y) / (shadowHeight.x - shadowHeight.y)); + // Sharp shadows + float shadowFraction = saturate(10.0 * (worldPos.z - shadowHeight.y) / (shadowHeight.x - shadowHeight.y)); #else - // Blurry shadows to simulate scattering - float shadowFraction = saturate((worldPos.z - shadowHeight.y) / (shadowHeight.x - shadowHeight.y)); + // Blurry shadows to simulate scattering + float shadowFraction = saturate((worldPos.z - shadowHeight.y) / (shadowHeight.x - shadowHeight.y)); #endif - return shadowFraction; - } - - return 1.0; + return shadowFraction; } } From dfc5a446f758adce2d8b3b6bf8be8250a13ac4f7 Mon Sep 17 00:00:00 2001 From: doodlum <15017472+doodlum@users.noreply.github.com> Date: Thu, 29 Jan 2026 00:00:25 +0000 Subject: [PATCH 33/63] fix: fix compile error --- src/Hooks.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Hooks.cpp b/src/Hooks.cpp index 01094e1275..3df9f77e89 100644 --- a/src/Hooks.cpp +++ b/src/Hooks.cpp @@ -181,10 +181,11 @@ namespace EffectExtensions } } } - } static inline REL::Relocation func; -}; + static inline REL::Relocation func; + }; } + namespace LightingExtensions { struct BSLightingShader_SetupGeometry From 2709ffcf226d3abbc743ed6a57aa243a7120b7ae Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 29 Jan 2026 00:01:55 +0000 Subject: [PATCH 34/63] =?UTF-8?q?style:=20=F0=9F=8E=A8=20apply=20pre-commi?= =?UTF-8?q?t.ci=20formatting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Automated formatting by clang-format, prettier, and other hooks. See https://pre-commit.ci for details. --- src/Hooks.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Hooks.cpp b/src/Hooks.cpp index 3df9f77e89..585ab53caf 100644 --- a/src/Hooks.cpp +++ b/src/Hooks.cpp @@ -185,7 +185,6 @@ namespace EffectExtensions }; } - namespace LightingExtensions { struct BSLightingShader_SetupGeometry From c015b40ecbdd5a2ee58dd4bda344b4f8082b5d2c Mon Sep 17 00:00:00 2001 From: doodlum <15017472+doodlum@users.noreply.github.com> Date: Thu, 29 Jan 2026 00:56:15 +0000 Subject: [PATCH 35/63] fix: terrain shadow fixes --- .../Shaders/TerrainShadows/ShadowUpdate.cs.hlsl | 2 +- .../Shaders/TerrainShadows/TerrainShadows.hlsli | 15 +++------------ src/Features/TerrainShadows.cpp | 4 ++-- 3 files changed, 6 insertions(+), 15 deletions(-) diff --git a/features/Terrain Shadows/Shaders/TerrainShadows/ShadowUpdate.cs.hlsl b/features/Terrain Shadows/Shaders/TerrainShadows/ShadowUpdate.cs.hlsl index 42f7ef3632..2375fe2d76 100644 --- a/features/Terrain Shadows/Shaders/TerrainShadows/ShadowUpdate.cs.hlsl +++ b/features/Terrain Shadows/Shaders/TerrainShadows/ShadowUpdate.cs.hlsl @@ -81,7 +81,7 @@ groupshared float2 g_shadowHeight[NTHREADS]; float2 threadUV = rawThreadUV - floor(rawThreadUV); // wraparound float2 threadPxCoord = threadUV * dims; -float2 pastHeights = 0.0f.xx; + float2 pastHeights = 0.0; if (isValid) { pastHeights = RWTexShadowHeights[uint2(threadPxCoord)]; diff --git a/features/Terrain Shadows/Shaders/TerrainShadows/TerrainShadows.hlsli b/features/Terrain Shadows/Shaders/TerrainShadows/TerrainShadows.hlsli index 83cf62d32e..d87a6524b1 100644 --- a/features/Terrain Shadows/Shaders/TerrainShadows/TerrainShadows.hlsli +++ b/features/Terrain Shadows/Shaders/TerrainShadows/TerrainShadows.hlsli @@ -9,7 +9,7 @@ namespace TerrainShadows float GetTerrainZ(float norm_z) { - return lerp(SharedData::terraOccSettings.ZRange.x, SharedData::terraOccSettings.ZRange.y, norm_z) - 1024; + return lerp(SharedData::terraOccSettings.ZRange.x, SharedData::terraOccSettings.ZRange.y, norm_z) - 256; } float2 GetTerrainZ(float2 norm_z) @@ -21,16 +21,7 @@ namespace TerrainShadows { if (!SharedData::terraOccSettings.EnableTerrainShadow) return 1.0; - - float2 terraOccUV = GetTerrainShadowUV(worldPos.xy); - float2 shadowHeight = GetTerrainZ(ShadowHeightTexture.SampleLevel(samp, terraOccUV, 0)); -#if defined(DEFERRED) - // Sharp shadows - float shadowFraction = saturate(10.0 * (worldPos.z - shadowHeight.y) / (shadowHeight.x - shadowHeight.y)); -#else - // Blurry shadows to simulate scattering - float shadowFraction = saturate((worldPos.z - shadowHeight.y) / (shadowHeight.x - shadowHeight.y)); -#endif - return shadowFraction; + float2 shadowHeight = GetTerrainZ(ShadowHeightTexture.SampleLevel(samp, GetTerrainShadowUV(worldPos.xy), 0)); + return saturate((worldPos.z - shadowHeight.y) / (shadowHeight.x - shadowHeight.y));; } } diff --git a/src/Features/TerrainShadows.cpp b/src/Features/TerrainShadows.cpp index b138dcf0e1..5104ab46c5 100644 --- a/src/Features/TerrainShadows.cpp +++ b/src/Features/TerrainShadows.cpp @@ -281,7 +281,7 @@ void TerrainShadows::Precompute() .Height = texHeightMap->desc.Height, .MipLevels = 1, .ArraySize = 1, - .Format = DXGI_FORMAT_R16G16_FLOAT, + .Format = DXGI_FORMAT_R16G16_UNORM, .SampleDesc = { .Count = 1 }, .Usage = D3D11_USAGE_DEFAULT, .BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_UNORDERED_ACCESS @@ -372,7 +372,7 @@ void TerrainShadows::UpdateShadow() // soft shadow angles float lenUV = float2{ dirLightDir.x, dirLightDir.y }.Length(); float dirLightAngle = atan2(-dirLightDir.z, lenUV); - float shadowSofteningRadiusAngle = 4.f * RE::NI_PI / 180.f; + float shadowSofteningRadiusAngle = RE::NI_PI / 180.f; float upperAngle = std::max(0.f, dirLightAngle - shadowSofteningRadiusAngle); float lowerAngle = std::min(RE::NI_HALF_PI - 1e-2f, dirLightAngle + shadowSofteningRadiusAngle); From 61e6becfa1db49705a3adab32e6fb14969bbba9b Mon Sep 17 00:00:00 2001 From: doodlum <15017472+doodlum@users.noreply.github.com> Date: Thu, 29 Jan 2026 02:30:50 +0000 Subject: [PATCH 36/63] feat: sky static lighting --- package/Shaders/Common/ShadowSampling.hlsli | 133 ++++++++++---------- package/Shaders/Effect.hlsl | 47 ++++++- 2 files changed, 116 insertions(+), 64 deletions(-) diff --git a/package/Shaders/Common/ShadowSampling.hlsli b/package/Shaders/Common/ShadowSampling.hlsli index eba14ea8f9..713acf50e7 100644 --- a/package/Shaders/Common/ShadowSampling.hlsli +++ b/package/Shaders/Common/ShadowSampling.hlsli @@ -41,6 +41,24 @@ namespace ShadowSampling StructuredBuffer SharedShadowData : register(t19); + float GetWorldShadow(float3 positionWS, float3 offset, uint eyeIndex) + { + if (SharedData::InInterior || SharedData::HideSky) + return 1.0; + + float worldShadow = 1.0; +#if defined(TERRAIN_SHADOWS) + worldShadow = TerrainShadows::GetTerrainShadow(positionWS + offset, LinearSampler); +#endif + +#if defined(CLOUD_SHADOWS) + if (!SharedData::InMapMenu) + worldShadow *= CloudShadows::GetCloudShadowMult(positionWS, LinearSampler); +#endif + + return worldShadow; + } + float GetShadowDepth(float3 positionWS, uint eyeIndex) { float4 positionCSShifted = mul(FrameBuffer::CameraViewProj[eyeIndex], float4(positionWS, 1)); @@ -49,73 +67,83 @@ namespace ShadowSampling float Get3DFilteredShadow(float3 positionWS, float3 viewDirection, float2 screenPosition, uint eyeIndex) { + float noise = Random::InterleavedGradientNoise(screenPosition, SharedData::FrameCount); + ShadowData sD = SharedShadowData[0]; static const uint sampleCount = 8; static const float rcpSampleCount = 1.0 / float(sampleCount); - float noise = Random::InterleavedGradientNoise(screenPosition, SharedData::FrameCount); float noiseTransform = noise * 2.0 - 1.0; float2 rotation; sincos(Math::TAU * noise, rotation.y, rotation.x); float2x2 rotationMatrix = float2x2(rotation.x, rotation.y, -rotation.y, rotation.x); + float worldShadow = 0.0; + for(uint i = 0; i < 8; i++){ + float3 positionWSTemp = positionWS; + positionWSTemp.xy += mul(Random::SpiralSampleOffsets8[i], rotationMatrix) * 1024; + worldShadow += GetWorldShadow(positionWSTemp, FrameBuffer::CameraPosAdjust[eyeIndex].xyz, eyeIndex); + } + worldShadow /= 8.0; + if (worldShadow == 0.0) + return 0.0; + float shadowMapDepth = GetShadowDepth(positionWS, eyeIndex); float shadow = 0.0; - if (sD.EndSplitDistances.z >= shadowMapDepth) { - float cascade1Probability = saturate((shadowMapDepth - sD.StartSplitDistances.y) / (sD.EndSplitDistances.x - sD.StartSplitDistances.y)); - - // Precompute cascade data for both cascades - float compareValues[2]; - float sampleRadii[2]; - float3 positionsLS[2]; - float3 viewOffsetsLS[2]; + + if (sD.EndSplitDistances.z < shadowMapDepth) + return worldShadow; - [unroll] - for (uint cascadeIdx = 0; cascadeIdx < 2; cascadeIdx++) { - compareValues[cascadeIdx] = mul(transpose(sD.ShadowMapProj[eyeIndex][cascadeIdx]), float4(positionWS, 1)).z - sD.AlphaTestRef[1 + cascadeIdx]; - sampleRadii[cascadeIdx] = sD.ShadowSampleParam.z * rcp(1 + cascadeIdx) * 2.0; + float cascade1Probability = saturate((shadowMapDepth - sD.StartSplitDistances.y) / (sD.EndSplitDistances.x - sD.StartSplitDistances.y)); #if defined(EFFECT) - // Enough for non-billboards + enough for Sovngarde fog - float viewRayLength = 16.0 + Permutation::BillboardRadius * 0.1; - positionsLS[cascadeIdx] = mul(transpose(sD.ShadowMapProj[eyeIndex][cascadeIdx]), float4(positionWS - viewDirection * viewRayLength, 1)); - viewOffsetsLS[cascadeIdx] = mul(transpose(sD.ShadowMapProj[eyeIndex][cascadeIdx]), float4(positionWS + viewDirection * viewRayLength, 1)); + // Enough for non-billboards + enough for Sovngarde fog + float viewRayLength = 16.0 + Permutation::BillboardRadius * 0.1; + float3 startPosition = positionWS - viewDirection * viewRayLength; + float3 endPosition = positionWS + viewDirection * viewRayLength; #else - // Enough for Eastmarch water - float viewRayLength = 128.0; - positionsLS[cascadeIdx] = mul(transpose(sD.ShadowMapProj[eyeIndex][cascadeIdx]), float4(positionWS, 1)); - viewOffsetsLS[cascadeIdx] = mul(transpose(sD.ShadowMapProj[eyeIndex][cascadeIdx]), float4(positionWS + viewDirection * viewRayLength, 1)); + // Enough for Eastmarch water + float viewRayLength = 128.0; + float3 startPosition = positionWS; + float3 endPosition = positionWS + viewDirection * viewRayLength; #endif - } + // Precompute cascade data for both cascades + float compareValues[2]; + float sampleRadii[2]; + float3 positionsLS[2]; + float3 viewOffsetsLS[2]; + for (uint cascadeIdx = 0; cascadeIdx < 2; cascadeIdx++) { + compareValues[cascadeIdx] = mul(transpose(sD.ShadowMapProj[eyeIndex][cascadeIdx]), float4(positionWS, 1)).z - sD.AlphaTestRef[1 + cascadeIdx]; + sampleRadii[cascadeIdx] = sD.ShadowSampleParam.z * rcp(1 + cascadeIdx) * 2.0; + positionsLS[cascadeIdx] = mul(transpose(sD.ShadowMapProj[eyeIndex][cascadeIdx]), float4(startPosition, 1)); + viewOffsetsLS[cascadeIdx] = mul(transpose(sD.ShadowMapProj[eyeIndex][cascadeIdx]), float4(endPosition, 1)); + } - for (uint i = 0; i < sampleCount; i++) { - uint noisyIndex = uint((float(i) + sampleCount * noise) % sampleCount); - float t = (float(sampleCount) - float(noisyIndex + 1)) * rcpSampleCount; - uint cascadeIndex = frac(t + noise) < cascade1Probability; + for (uint i = 0; i < sampleCount; i++) { + uint noisyIndex = uint((float(i) + sampleCount * noise) % sampleCount); + float t = (float(sampleCount) - float(noisyIndex + 1)) * rcpSampleCount; + uint cascadeIndex = frac(t + noise) < cascade1Probability; - float compareValue = compareValues[cascadeIndex]; - float sampleRadius = sampleRadii[cascadeIndex]; - float3 positionLS = positionsLS[cascadeIndex]; - float3 viewOffsetLS = viewOffsetsLS[cascadeIndex]; + float compareValue = compareValues[cascadeIndex]; + float sampleRadius = sampleRadii[cascadeIndex]; + float3 positionLS = positionsLS[cascadeIndex]; + float3 viewOffsetLS = viewOffsetsLS[cascadeIndex]; - // Offset along view ray with optimised sample pattern - float3 sampledPositionLS = lerp(positionLS, viewOffsetLS, t + noiseTransform * rcpSampleCount); + // Offset along view ray with optimised sample pattern + float3 sampledPositionLS = lerp(positionLS, viewOffsetLS, t + noiseTransform * rcpSampleCount); - // Blur shadow with poisson disc - sampledPositionLS.xy += mul(Random::SpiralSampleOffsets8[i], rotationMatrix) * sampleRadius; + // Blur shadow with poisson disc + sampledPositionLS.xy += mul(Random::SpiralSampleOffsets8[i], rotationMatrix) * sampleRadius; - // Average 4 shadow samples for improved quality - float4 depths = SharedShadowMap.GatherRed(LinearSampler, float3(saturate(sampledPositionLS.xy), cascadeIndex), 0); - shadow += dot(depths > compareValue, 0.25); - } - } else { - shadow = 1.0; + // Average 4 shadow samples for improved quality + float4 depths = SharedShadowMap.GatherRed(LinearSampler, float3(saturate(sampledPositionLS.xy), cascadeIndex), 0); + shadow += dot(depths > compareValue, 0.25); } float fadeFactor = 1.0 - pow(saturate(dot(positionWS, positionWS) / sD.ShadowLightParam.z), 8); - return lerp(1.0, shadow * rcpSampleCount, fadeFactor); + return worldShadow * lerp(1.0, shadow * rcpSampleCount, fadeFactor); } float Get2DFilteredShadowCascade(float noise, float2x2 rotationMatrix, float sampleOffsetScale, float2 baseUV, float cascadeIndex, float compareValue, uint eyeIndex) @@ -176,30 +204,9 @@ namespace ShadowSampling return 1.0; } - float GetWorldShadow(float3 positionWS, float3 offset, uint eyeIndex) - { - if (SharedData::InInterior || SharedData::HideSky) - return 1.0; - - float worldShadow = 1.0; -#if defined(TERRAIN_SHADOWS) - worldShadow = TerrainShadows::GetTerrainShadow(positionWS + offset, LinearSampler); -#endif - -#if defined(CLOUD_SHADOWS) - if (!SharedData::InMapMenu) - worldShadow *= CloudShadows::GetCloudShadowMult(positionWS, LinearSampler); -#endif - - return worldShadow; - } - float GetEffectShadow(float3 worldPosition, float3 viewDirection, float2 screenPosition, uint eyeIndex) { - float worldShadow = GetWorldShadow(worldPosition, FrameBuffer::CameraPosAdjust[eyeIndex].xyz, eyeIndex); - if (worldShadow == 0.0) - return 0.0; - return worldShadow * Get3DFilteredShadow(worldPosition, viewDirection, screenPosition, eyeIndex); + return Get3DFilteredShadow(worldPosition, viewDirection, screenPosition, eyeIndex); } float GetLightingShadow(float noise, float3 worldPosition, uint eyeIndex) diff --git a/package/Shaders/Effect.hlsl b/package/Shaders/Effect.hlsl index 0f132efa66..3ed8b5cdc9 100644 --- a/package/Shaders/Effect.hlsl +++ b/package/Shaders/Effect.hlsl @@ -579,8 +579,48 @@ float3 GetLightingColor(float3 msPosition, float3 worldPosition, float2 screenPo return color; } +# else +float3 GetLightingShadow(float3 color, float3 worldPosition, float2 screenPosition, uint eyeIndex, inout float shadowVariance) +{ +# if defined(SKYLIGHTING) +# if defined(VR) + float3 positionMSSkylight = worldPosition + FrameBuffer::CameraPosAdjust[eyeIndex].xyz - FrameBuffer::CameraPosAdjust[0].xyz; +# else + float3 positionMSSkylight = worldPosition; +# endif + + sh2 skylightingSH = Skylighting::sampleNoBias(SharedData::skylightingSettings, Skylighting::SkylightingProbeArray, positionMSSkylight); + + float skylightingDiffuse = SphericalHarmonics::FuncProductIntegral(skylightingSH, SphericalHarmonics::EvaluateCosineLobe(float3(0, 0, 1))) / Math::PI; + skylightingDiffuse = saturate(skylightingDiffuse); + skylightingDiffuse = lerp(1.0, skylightingDiffuse, Skylighting::getFadeOutFactor(worldPosition)); + skylightingDiffuse = Skylighting::mixDiffuse(SharedData::skylightingSettings, skylightingDiffuse); # endif + float3 dirColor; + float3 ambientColor; +# if defined(SKYLIGHTING) + ShadowSampling::ExtractLighting(color, dirColor, ambientColor, skylightingDiffuse); +# else + ShadowSampling::ExtractLighting(color, dirColor, ambientColor); +# endif + + float shadow = ShadowSampling::GetEffectShadow(worldPosition.xyz, normalize(worldPosition.xyz), screenPosition, eyeIndex); + + shadowVariance = 1.0 - sqrt(saturate(fwidth(shadow))); + + dirColor *= shadow; + +# if defined(SKYLIGHTING) + ambientColor = Color::IrradianceToLinear(ambientColor); + ambientColor *= skylightingDiffuse; + ambientColor = Color::IrradianceToGamma(ambientColor); +# endif + + return dirColor + ambientColor;; +} +#endif + PS_OUTPUT main(PS_INPUT input) { PS_OUTPUT psout = (PS_OUTPUT)0; @@ -757,7 +797,7 @@ PS_OUTPUT main(PS_INPUT input) baseColor.xyz = Color::Effect(baseColorScale * TexGrayscaleSampler.Sample(SampGrayscaleSampler, grayscaleToColorUv).xyz); } - float3 lightColor = lerp(baseColor.xyz, propertyColor * baseColor.xyz, lightingInfluence.xxx); + float3 lightColor = lerp(baseColor.xyz, propertyColor * baseColor.xyz, lightingInfluence); # if !defined(MOTIONVECTORS_NORMALS) if (alpha * fogMul.w - AlphaTestRefRS < 0) { @@ -765,6 +805,11 @@ PS_OUTPUT main(PS_INPUT input) } # endif +#if !defined(LIGHTING) && defined(VC) && defined(TEXCOORD) && defined(NORMALS) && defined(TEXTURE) && defined(FALLOFF) && defined(SOFT) + if(Permutation::PixelShaderDescriptor & Permutation::EffectFlags::GrayscaleToAlpha && lightingInfluence == 1.0) + lightColor = GetLightingShadow(lightColor, input.WorldPosition.xyz, input.Position.xy, eyeIndex, shadowVariance); +#endif + # if !defined(MOTIONVECTORS_NORMALS) # if defined(ADDBLEND) float3 blendedColor = lightColor * (1 - Color::FogAlpha(input.FogParam.w).xxx); From 1e92d254db99f55e700f1e998d720f75a15c572f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 29 Jan 2026 02:32:20 +0000 Subject: [PATCH 37/63] =?UTF-8?q?style:=20=F0=9F=8E=A8=20apply=20pre-commi?= =?UTF-8?q?t.ci=20formatting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Automated formatting by clang-format, prettier, and other hooks. See https://pre-commit.ci for details. --- package/Shaders/Common/ShadowSampling.hlsli | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/Shaders/Common/ShadowSampling.hlsli b/package/Shaders/Common/ShadowSampling.hlsli index 713acf50e7..34c50c04df 100644 --- a/package/Shaders/Common/ShadowSampling.hlsli +++ b/package/Shaders/Common/ShadowSampling.hlsli @@ -92,7 +92,7 @@ namespace ShadowSampling float shadowMapDepth = GetShadowDepth(positionWS, eyeIndex); float shadow = 0.0; - + if (sD.EndSplitDistances.z < shadowMapDepth) return worldShadow; From 994c77225b70f1dbb4abae21f08d9db9bae45476 Mon Sep 17 00:00:00 2001 From: doodlum <15017472+doodlum@users.noreply.github.com> Date: Thu, 29 Jan 2026 02:34:45 +0000 Subject: [PATCH 38/63] refactor: cleanup --- package/Shaders/Common/ShadowSampling.hlsli | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/package/Shaders/Common/ShadowSampling.hlsli b/package/Shaders/Common/ShadowSampling.hlsli index 713acf50e7..5d678a1f22 100644 --- a/package/Shaders/Common/ShadowSampling.hlsli +++ b/package/Shaders/Common/ShadowSampling.hlsli @@ -67,32 +67,28 @@ namespace ShadowSampling float Get3DFilteredShadow(float3 positionWS, float3 viewDirection, float2 screenPosition, uint eyeIndex) { - float noise = Random::InterleavedGradientNoise(screenPosition, SharedData::FrameCount); - - ShadowData sD = SharedShadowData[0]; - static const uint sampleCount = 8; static const float rcpSampleCount = 1.0 / float(sampleCount); - + + float noise = Random::InterleavedGradientNoise(screenPosition, SharedData::FrameCount); float noiseTransform = noise * 2.0 - 1.0; float2 rotation; sincos(Math::TAU * noise, rotation.y, rotation.x); float2x2 rotationMatrix = float2x2(rotation.x, rotation.y, -rotation.y, rotation.x); float worldShadow = 0.0; - for(uint i = 0; i < 8; i++){ + for(uint i = 0; i < sampleCount; i++){ float3 positionWSTemp = positionWS; positionWSTemp.xy += mul(Random::SpiralSampleOffsets8[i], rotationMatrix) * 1024; worldShadow += GetWorldShadow(positionWSTemp, FrameBuffer::CameraPosAdjust[eyeIndex].xyz, eyeIndex); } - worldShadow /= 8.0; + worldShadow *= rcpSampleCount; if (worldShadow == 0.0) return 0.0; float shadowMapDepth = GetShadowDepth(positionWS, eyeIndex); - - float shadow = 0.0; + ShadowData sD = SharedShadowData[0]; if (sD.EndSplitDistances.z < shadowMapDepth) return worldShadow; @@ -121,6 +117,7 @@ namespace ShadowSampling viewOffsetsLS[cascadeIdx] = mul(transpose(sD.ShadowMapProj[eyeIndex][cascadeIdx]), float4(endPosition, 1)); } + float shadow = 0.0; for (uint i = 0; i < sampleCount; i++) { uint noisyIndex = uint((float(i) + sampleCount * noise) % sampleCount); float t = (float(sampleCount) - float(noisyIndex + 1)) * rcpSampleCount; From e002dc5e537388f39dd9fce0ff54970a7095fbc2 Mon Sep 17 00:00:00 2001 From: doodlum <15017472+doodlum@users.noreply.github.com> Date: Thu, 29 Jan 2026 02:36:40 +0000 Subject: [PATCH 39/63] chore: cleanup --- package/Shaders/Effect.hlsl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/Shaders/Effect.hlsl b/package/Shaders/Effect.hlsl index 3ed8b5cdc9..4f65bd4070 100644 --- a/package/Shaders/Effect.hlsl +++ b/package/Shaders/Effect.hlsl @@ -617,7 +617,7 @@ float3 GetLightingShadow(float3 color, float3 worldPosition, float2 screenPositi ambientColor = Color::IrradianceToGamma(ambientColor); # endif - return dirColor + ambientColor;; + return dirColor + ambientColor; } #endif From 3ddff543e2a551c188133b456b9d37664cee5db6 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 29 Jan 2026 02:36:50 +0000 Subject: [PATCH 40/63] =?UTF-8?q?style:=20=F0=9F=8E=A8=20apply=20pre-commi?= =?UTF-8?q?t.ci=20formatting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Automated formatting by clang-format, prettier, and other hooks. See https://pre-commit.ci for details. --- package/Shaders/Common/ShadowSampling.hlsli | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package/Shaders/Common/ShadowSampling.hlsli b/package/Shaders/Common/ShadowSampling.hlsli index 5d678a1f22..1933cad6b2 100644 --- a/package/Shaders/Common/ShadowSampling.hlsli +++ b/package/Shaders/Common/ShadowSampling.hlsli @@ -69,7 +69,7 @@ namespace ShadowSampling { static const uint sampleCount = 8; static const float rcpSampleCount = 1.0 / float(sampleCount); - + float noise = Random::InterleavedGradientNoise(screenPosition, SharedData::FrameCount); float noiseTransform = noise * 2.0 - 1.0; float2 rotation; @@ -87,7 +87,7 @@ namespace ShadowSampling return 0.0; float shadowMapDepth = GetShadowDepth(positionWS, eyeIndex); - + ShadowData sD = SharedShadowData[0]; if (sD.EndSplitDistances.z < shadowMapDepth) return worldShadow; From bd5e34a0d46bf819aa8eafac9008e4571fa28ba2 Mon Sep 17 00:00:00 2001 From: doodlum <15017472+doodlum@users.noreply.github.com> Date: Thu, 29 Jan 2026 16:05:12 +0000 Subject: [PATCH 41/63] chore: improvements --- package/Shaders/Common/Permutation.hlsli | 2 +- package/Shaders/Common/ShadowSampling.hlsli | 38 +++++++++------------ src/Hooks.cpp | 12 +------ src/State.h | 4 +-- 4 files changed, 21 insertions(+), 35 deletions(-) diff --git a/package/Shaders/Common/Permutation.hlsli b/package/Shaders/Common/Permutation.hlsli index ee3e4ea1c9..5a45489dfd 100644 --- a/package/Shaders/Common/Permutation.hlsli +++ b/package/Shaders/Common/Permutation.hlsli @@ -81,7 +81,7 @@ namespace Permutation uint ExtraShaderDescriptor; uint ExtraFeatureDescriptor; - float BillboardRadius; + float EffectRadius; }; } diff --git a/package/Shaders/Common/ShadowSampling.hlsli b/package/Shaders/Common/ShadowSampling.hlsli index 1933cad6b2..1d50d3556b 100644 --- a/package/Shaders/Common/ShadowSampling.hlsli +++ b/package/Shaders/Common/ShadowSampling.hlsli @@ -76,13 +76,20 @@ namespace ShadowSampling sincos(Math::TAU * noise, rotation.y, rotation.x); float2x2 rotationMatrix = float2x2(rotation.x, rotation.y, -rotation.y, rotation.x); - float worldShadow = 0.0; - for(uint i = 0; i < sampleCount; i++){ - float3 positionWSTemp = positionWS; - positionWSTemp.xy += mul(Random::SpiralSampleOffsets8[i], rotationMatrix) * 1024; - worldShadow += GetWorldShadow(positionWSTemp, FrameBuffer::CameraPosAdjust[eyeIndex].xyz, eyeIndex); - } - worldShadow *= rcpSampleCount; +#if defined(EFFECT) + // Enough for non-billboards + enough for Sovngarde fog + float viewRayLength = Permutation::EffectRadius * 0.1; + float3 startPosition = positionWS - viewDirection * viewRayLength; + float3 endPosition = positionWS + viewDirection * viewRayLength; +#else + // Enough for Eastmarch water + float viewRayLength = 128.0; + float3 startPosition = positionWS; + float3 endPosition = positionWS + viewDirection * viewRayLength; +#endif + + float worldShadow = GetWorldShadow(lerp(startPosition, endPosition, noise), FrameBuffer::CameraPosAdjust[eyeIndex].xyz, eyeIndex); + if (worldShadow == 0.0) return 0.0; @@ -94,17 +101,6 @@ namespace ShadowSampling float cascade1Probability = saturate((shadowMapDepth - sD.StartSplitDistances.y) / (sD.EndSplitDistances.x - sD.StartSplitDistances.y)); -#if defined(EFFECT) - // Enough for non-billboards + enough for Sovngarde fog - float viewRayLength = 16.0 + Permutation::BillboardRadius * 0.1; - float3 startPosition = positionWS - viewDirection * viewRayLength; - float3 endPosition = positionWS + viewDirection * viewRayLength; -#else - // Enough for Eastmarch water - float viewRayLength = 128.0; - float3 startPosition = positionWS; - float3 endPosition = positionWS + viewDirection * viewRayLength; -#endif // Precompute cascade data for both cascades float compareValues[2]; float sampleRadii[2]; @@ -219,7 +215,7 @@ namespace ShadowSampling #else void ExtractLighting(float3 inputColor, out float3 dirColor, out float3 ambientColor) #endif - { + { float3 ambientColorAmb = max(0, mul(SharedData::DirectionalAmbient, float4(0, 0, 1, 1))); # if defined(IBL) @@ -262,8 +258,8 @@ namespace ShadowSampling float3 dirLightColorAmb = max(0.0, inputColor - ambientColorAmb); float3 ambientColorDir = max(0.0, inputColor - dirLightColorDir); - dirColor = lerp(dirLightColorAmb, dirLightColorDir, 0.5); - ambientColor = lerp(ambientColorAmb, ambientColorDir, 0.5); + dirColor = lerp(dirLightColorAmb, dirLightColorDir, 0.0); + ambientColor = lerp(ambientColorAmb, ambientColorDir, 0.0); } } diff --git a/src/Hooks.cpp b/src/Hooks.cpp index 585ab53caf..6667c8dafb 100644 --- a/src/Hooks.cpp +++ b/src/Hooks.cpp @@ -169,17 +169,7 @@ namespace EffectExtensions static void thunk(RE::BSShader* shader, RE::BSRenderPass* pass, uint32_t renderFlags) { func(shader, pass, renderFlags); - - auto state = globals::state; - - state->permutationData.BillboardRadius = 0.0f; - - if (auto node = pass->geometry->parent) { - if (node->GetRTTI() == globals::rtti::NiBillboardNodeRTTI.get()) { - auto billboardNode = static_cast(node); - state->permutationData.BillboardRadius = billboardNode->worldBound.radius; - } - } + globals::state->permutationData.EffectRadius = pass->geometry->worldBound.radius; } static inline REL::Relocation func; }; diff --git a/src/State.h b/src/State.h index bb7e3238d3..360a4b4e06 100644 --- a/src/State.h +++ b/src/State.h @@ -181,14 +181,14 @@ class State uint ExtraShaderDescriptor; uint ExtraFeatureDescriptor; - float BillboardRadius; + float EffectRadius; float3 pad0; bool operator==(const PermutationCB& other) const { return PixelShaderDescriptor == other.PixelShaderDescriptor && ExtraShaderDescriptor == other.ExtraShaderDescriptor && - ExtraFeatureDescriptor == other.ExtraFeatureDescriptor && BillboardRadius == other.BillboardRadius; + ExtraFeatureDescriptor == other.ExtraFeatureDescriptor && EffectRadius == other.EffectRadius; } }; STATIC_ASSERT_ALIGNAS_16(PermutationCB); From 19e3f03d2d7f28e035ccba8a330b8f04979f191b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 29 Jan 2026 16:06:47 +0000 Subject: [PATCH 42/63] =?UTF-8?q?style:=20=F0=9F=8E=A8=20apply=20pre-commi?= =?UTF-8?q?t.ci=20formatting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Automated formatting by clang-format, prettier, and other hooks. See https://pre-commit.ci for details. --- package/Shaders/Common/ShadowSampling.hlsli | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/Shaders/Common/ShadowSampling.hlsli b/package/Shaders/Common/ShadowSampling.hlsli index 1d50d3556b..b2695a5c58 100644 --- a/package/Shaders/Common/ShadowSampling.hlsli +++ b/package/Shaders/Common/ShadowSampling.hlsli @@ -215,7 +215,7 @@ namespace ShadowSampling #else void ExtractLighting(float3 inputColor, out float3 dirColor, out float3 ambientColor) #endif - { + { float3 ambientColorAmb = max(0, mul(SharedData::DirectionalAmbient, float4(0, 0, 1, 1))); # if defined(IBL) From 1aefbb556f18e86adc1380347393b68e33080e19 Mon Sep 17 00:00:00 2001 From: doodlum <15017472+doodlum@users.noreply.github.com> Date: Thu, 29 Jan 2026 17:40:27 +0000 Subject: [PATCH 43/63] chore: more optimisations --- package/Shaders/Common/ShadowSampling.hlsli | 15 +++++-- package/Shaders/DownsampleShadowCS.hlsl | 48 ++++++--------------- package/Shaders/Effect.hlsl | 30 +++++++++++-- src/Deferred.cpp | 9 ++-- 4 files changed, 54 insertions(+), 48 deletions(-) diff --git a/package/Shaders/Common/ShadowSampling.hlsli b/package/Shaders/Common/ShadowSampling.hlsli index 1d50d3556b..950d43929a 100644 --- a/package/Shaders/Common/ShadowSampling.hlsli +++ b/package/Shaders/Common/ShadowSampling.hlsli @@ -78,7 +78,7 @@ namespace ShadowSampling #if defined(EFFECT) // Enough for non-billboards + enough for Sovngarde fog - float viewRayLength = Permutation::EffectRadius * 0.1; + float viewRayLength = min(Permutation::BillboardRadius * 0.1, 128); float3 startPosition = positionWS - viewDirection * viewRayLength; float3 endPosition = positionWS + viewDirection * viewRayLength; #else @@ -88,7 +88,15 @@ namespace ShadowSampling float3 endPosition = positionWS + viewDirection * viewRayLength; #endif - float worldShadow = GetWorldShadow(lerp(startPosition, endPosition, noise), FrameBuffer::CameraPosAdjust[eyeIndex].xyz, eyeIndex); + float worldShadow = 0; + for(uint i = 0; i < sampleCount; i++){ + uint noisyIndex = uint((float(i) + sampleCount * noise) % sampleCount); + float t = (float(sampleCount) - float(noisyIndex + 1)) * rcpSampleCount; + float tSample = t + noiseTransform * rcpSampleCount; + worldShadow += ShadowSampling::GetWorldShadow(lerp(startPosition, endPosition, tSample), FrameBuffer::CameraPosAdjust[eyeIndex].xyz, eyeIndex); + } + + worldShadow *= rcpSampleCount; if (worldShadow == 0.0) return 0.0; @@ -125,7 +133,8 @@ namespace ShadowSampling float3 viewOffsetLS = viewOffsetsLS[cascadeIndex]; // Offset along view ray with optimised sample pattern - float3 sampledPositionLS = lerp(positionLS, viewOffsetLS, t + noiseTransform * rcpSampleCount); + float tSample = t + noiseTransform * rcpSampleCount; + float3 sampledPositionLS = lerp(positionLS, viewOffsetLS, tSample); // Blur shadow with poisson disc sampledPositionLS.xy += mul(Random::SpiralSampleOffsets8[i], rotationMatrix) * sampleRadius; diff --git a/package/Shaders/DownsampleShadowCS.hlsl b/package/Shaders/DownsampleShadowCS.hlsl index 182d775f00..6cf71049f9 100644 --- a/package/Shaders/DownsampleShadowCS.hlsl +++ b/package/Shaders/DownsampleShadowCS.hlsl @@ -1,46 +1,22 @@ Texture2DArray InputTexture : register(t0); -RWTexture2DArray OutputTexture : register(u0); +RWTexture2DArray OutputTexture : register(u0); SamplerState PointSampler : register(s0); -groupshared float g_scratchDepths[8][8]; - [numthreads(8, 8, 1)] void main(uint3 dispatchThreadID - : SV_DispatchThreadID, uint2 groupThreadID - : SV_GroupThreadID) { - // MIP 0 -> 1: each thread gathers a 2x2 block and computes max - uint2 pixCoord = dispatchThreadID.xy * 2; - - uint inputW, inputH, inputSlices; - InputTexture.GetDimensions(inputW, inputH, inputSlices); - float2 uv = (pixCoord + 1.0) / float2(inputW, inputH); - - float4 depths4 = InputTexture.GatherRed(PointSampler, float3(uv, dispatchThreadID.z)); - float depth0 = depths4.w; - float depth1 = depths4.z; - float depth2 = depths4.x; - float depth3 = depths4.y; - - float dm1 = max(max(depth0, depth1), max(depth2, depth3)); - g_scratchDepths[groupThreadID.x][groupThreadID.y] = dm1; - - GroupMemoryBarrierWithGroupSync(); + : SV_DispatchThreadID) { + uint w, h, slices; + OutputTexture.GetDimensions(w, h, slices); - // MIP 1 -> 2: 2x2 reduction in shared memory (4x4 total) - [branch] - if (all((groupThreadID.xy % 2) == 0)) { - float inTL = g_scratchDepths[groupThreadID.x + 0][groupThreadID.y + 0]; - float inTR = g_scratchDepths[groupThreadID.x + 1][groupThreadID.y + 0]; - float inBL = g_scratchDepths[groupThreadID.x + 0][groupThreadID.y + 1]; - float inBR = g_scratchDepths[groupThreadID.x + 1][groupThreadID.y + 1]; + if (dispatchThreadID.x < w && dispatchThreadID.y < h){ + // MIP 0 -> 1: each thread gathers a 2x2 block and computes max + uint2 pixCoord = dispatchThreadID.xy * 2; - float dm2 = max(max(inTL, inTR), max(inBL, inBR)); - g_scratchDepths[groupThreadID.x][groupThreadID.y] = dm2; + uint inputW, inputH, inputSlices; + InputTexture.GetDimensions(inputW, inputH, inputSlices); + float2 uv = (pixCoord + 1.0) / float2(inputW, inputH); - uint3 outCoord = uint3(dispatchThreadID.xy / 2, dispatchThreadID.z); - uint w, h, slices; - OutputTexture.GetDimensions(w, h, slices); - if (outCoord.x < w && outCoord.y < h) - OutputTexture[outCoord] = dm2; + float4 depths4 = InputTexture.GatherRed(PointSampler, float3(uv, dispatchThreadID.z)); + OutputTexture[dispatchThreadID.xyz] = dot(depths4, 0.25); } } \ No newline at end of file diff --git a/package/Shaders/Effect.hlsl b/package/Shaders/Effect.hlsl index 4f65bd4070..9093a8784c 100644 --- a/package/Shaders/Effect.hlsl +++ b/package/Shaders/Effect.hlsl @@ -580,7 +580,7 @@ float3 GetLightingColor(float3 msPosition, float3 worldPosition, float2 screenPo return color; } # else -float3 GetLightingShadow(float3 color, float3 worldPosition, float2 screenPosition, uint eyeIndex, inout float shadowVariance) +float3 GetLightingShadow(float3 color, float3 worldPosition, float2 screenPosition, float depth, uint eyeIndex, inout float shadowVariance) { # if defined(SKYLIGHTING) # if defined(VR) @@ -605,7 +605,31 @@ float3 GetLightingShadow(float3 color, float3 worldPosition, float2 screenPositi ShadowSampling::ExtractLighting(color, dirColor, ambientColor); # endif - float shadow = ShadowSampling::GetEffectShadow(worldPosition.xyz, normalize(worldPosition.xyz), screenPosition, eyeIndex); + static const uint sampleCount = 8; + static const float rcpSampleCount = 1.0 / float(sampleCount); + + float noise = Random::InterleavedGradientNoise(screenPosition, SharedData::FrameCount); + float noiseTransform = noise * 2.0 - 1.0; + float2 rotation; + sincos(Math::TAU * noise, rotation.y, rotation.x); + float2x2 rotationMatrix = float2x2(rotation.x, rotation.y, -rotation.y, rotation.x); + + // Enough for sky statics + float maxDistance = max(0, SharedData::GetScreenDepth(depth)); + float viewRayLength = min(maxDistance, 4096); + float3 viewDirection = normalize(worldPosition); + float3 startPosition = worldPosition - viewDirection * viewRayLength; + float3 endPosition = worldPosition + viewDirection * min(maxDistance, 4096); + + float shadow = 0; + for(uint i = 0; i < sampleCount; i++){ + uint noisyIndex = uint((float(i) + sampleCount * noise) % sampleCount); + float t = (float(sampleCount) - float(noisyIndex + 1)) * rcpSampleCount; + float tSample = t + noiseTransform * rcpSampleCount; + shadow += ShadowSampling::GetWorldShadow(lerp(startPosition, endPosition, tSample), FrameBuffer::CameraPosAdjust[eyeIndex].xyz, eyeIndex); + } + + shadow *= rcpSampleCount; shadowVariance = 1.0 - sqrt(saturate(fwidth(shadow))); @@ -807,7 +831,7 @@ PS_OUTPUT main(PS_INPUT input) #if !defined(LIGHTING) && defined(VC) && defined(TEXCOORD) && defined(NORMALS) && defined(TEXTURE) && defined(FALLOFF) && defined(SOFT) if(Permutation::PixelShaderDescriptor & Permutation::EffectFlags::GrayscaleToAlpha && lightingInfluence == 1.0) - lightColor = GetLightingShadow(lightColor, input.WorldPosition.xyz, input.Position.xy, eyeIndex, shadowVariance); + lightColor = GetLightingShadow(lightColor, input.WorldPosition.xyz, input.Position.xy, depth, eyeIndex, shadowVariance); #endif # if !defined(MOTIONVECTORS_NORMALS) diff --git a/src/Deferred.cpp b/src/Deferred.cpp index 87035c424b..373cf239f5 100644 --- a/src/Deferred.cpp +++ b/src/Deferred.cpp @@ -226,8 +226,8 @@ void Deferred::CopyShadowData() D3D11_TEXTURE2D_DESC srcDesc; shadowTexture->GetDesc(&srcDesc); - uint32_t newWidth = srcDesc.Width / 4; - uint32_t newHeight = srcDesc.Height / 4; + uint32_t newWidth = srcDesc.Width / 2; + uint32_t newHeight = srcDesc.Height / 2; // Lazily create or recreate downscaled texture if dimensions changed if (!shadowCopyTexture || shadowCopyWidth != newWidth || shadowCopyHeight != newHeight || shadowCopyArraySize != srcDesc.ArraySize) { @@ -289,10 +289,7 @@ void Deferred::CopyShadowData() context->CSSetSamplers(0, 1, &pointSampler); context->CSSetShader(downsampleShadowCS, nullptr, 0); - - uint fullShadowSize = shadowCopyWidth * 4; - - context->Dispatch((fullShadowSize + 15) >> 4, (fullShadowSize + 15) >> 4, shadowCopyArraySize); + context->Dispatch((shadowCopyWidth + 7) >> 3, (shadowCopyWidth + 7) >> 3, shadowCopyArraySize); // Cleanup CS state csSrvs[0] = nullptr; From 5b6bbec2ab5ab95df9e1a2afb8063357d6614490 Mon Sep 17 00:00:00 2001 From: doodlum <15017472+doodlum@users.noreply.github.com> Date: Thu, 29 Jan 2026 18:22:52 +0000 Subject: [PATCH 44/63] Update package/Shaders/Common/ShadowSampling.hlsli Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- package/Shaders/Common/ShadowSampling.hlsli | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package/Shaders/Common/ShadowSampling.hlsli b/package/Shaders/Common/ShadowSampling.hlsli index 39d0eccdfc..986ba4ffa8 100644 --- a/package/Shaders/Common/ShadowSampling.hlsli +++ b/package/Shaders/Common/ShadowSampling.hlsli @@ -76,9 +76,9 @@ namespace ShadowSampling sincos(Math::TAU * noise, rotation.y, rotation.x); float2x2 rotationMatrix = float2x2(rotation.x, rotation.y, -rotation.y, rotation.x); -#if defined(EFFECT) +`#if` defined(EFFECT) // Enough for non-billboards + enough for Sovngarde fog - float viewRayLength = min(Permutation::BillboardRadius * 0.1, 128); + float viewRayLength = min(Permutation::EffectRadius * 0.1, 128); float3 startPosition = positionWS - viewDirection * viewRayLength; float3 endPosition = positionWS + viewDirection * viewRayLength; #else From 148f83184acc3af1a770dd2a016f34768793709d Mon Sep 17 00:00:00 2001 From: doodlum <15017472+doodlum@users.noreply.github.com> Date: Thu, 29 Jan 2026 18:47:36 +0000 Subject: [PATCH 45/63] fix: fix compiler error --- package/Shaders/Common/ShadowSampling.hlsli | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/Shaders/Common/ShadowSampling.hlsli b/package/Shaders/Common/ShadowSampling.hlsli index 986ba4ffa8..92d19a176c 100644 --- a/package/Shaders/Common/ShadowSampling.hlsli +++ b/package/Shaders/Common/ShadowSampling.hlsli @@ -76,7 +76,7 @@ namespace ShadowSampling sincos(Math::TAU * noise, rotation.y, rotation.x); float2x2 rotationMatrix = float2x2(rotation.x, rotation.y, -rotation.y, rotation.x); -`#if` defined(EFFECT) +#if defined(EFFECT) // Enough for non-billboards + enough for Sovngarde fog float viewRayLength = min(Permutation::EffectRadius * 0.1, 128); float3 startPosition = positionWS - viewDirection * viewRayLength; From 9c89bd6197056052b9fec143b964aaf4ed3c5d86 Mon Sep 17 00:00:00 2001 From: doodlum <15017472+doodlum@users.noreply.github.com> Date: Thu, 29 Jan 2026 19:07:21 +0000 Subject: [PATCH 46/63] fix: fix color space --- package/Shaders/Water.hlsl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/Shaders/Water.hlsl b/package/Shaders/Water.hlsl index ce0fc2eee3..0e98a83cf4 100644 --- a/package/Shaders/Water.hlsl +++ b/package/Shaders/Water.hlsl @@ -890,7 +890,7 @@ float3 GetWaterSpecularColor(PS_INPUT input, float3 normal, float3 viewDirection float4 ssrReflectionColor = lerp(ssrReflectionColorBlurred, ssrReflectionColorRaw, ssrAmount * 0.7); float3 finalSsrReflectionColor = max(0, ssrReflectionColor.xyz); float ssrFraction = saturate(ssrReflectionColor.w * distanceFactor * ssrAmount); - reflectionColor = lerp(Color::IrradianceToLinear(reflectionColor), Color::IrradianceToLinear(finalSsrReflectionColor), ssrFraction); + reflectionColor = lerp(reflectionColor, finalSsrReflectionColor, ssrFraction); } # endif From ff066fd1c5ecd7b30ed515abcca617165856ea2f Mon Sep 17 00:00:00 2001 From: doodlum <15017472+doodlum@users.noreply.github.com> Date: Fri, 30 Jan 2026 00:30:43 +0000 Subject: [PATCH 47/63] fix: fix compiler warning --- package/Shaders/Common/ShadowSampling.hlsli | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package/Shaders/Common/ShadowSampling.hlsli b/package/Shaders/Common/ShadowSampling.hlsli index 92d19a176c..0076302ba0 100644 --- a/package/Shaders/Common/ShadowSampling.hlsli +++ b/package/Shaders/Common/ShadowSampling.hlsli @@ -122,8 +122,8 @@ namespace ShadowSampling } float shadow = 0.0; - for (uint i = 0; i < sampleCount; i++) { - uint noisyIndex = uint((float(i) + sampleCount * noise) % sampleCount); + for (uint k = 0; k < sampleCount; k++) { + uint noisyIndex = uint((float(k) + sampleCount * noise) % sampleCount); float t = (float(sampleCount) - float(noisyIndex + 1)) * rcpSampleCount; uint cascadeIndex = frac(t + noise) < cascade1Probability; @@ -137,7 +137,7 @@ namespace ShadowSampling float3 sampledPositionLS = lerp(positionLS, viewOffsetLS, tSample); // Blur shadow with poisson disc - sampledPositionLS.xy += mul(Random::SpiralSampleOffsets8[i], rotationMatrix) * sampleRadius; + sampledPositionLS.xy += mul(Random::SpiralSampleOffsets8[k], rotationMatrix) * sampleRadius; // Average 4 shadow samples for improved quality float4 depths = SharedShadowMap.GatherRed(LinearSampler, float3(saturate(sampledPositionLS.xy), cascadeIndex), 0); From eb115a03e8096c91f26f5cdeb8ba0dfab73dcf00 Mon Sep 17 00:00:00 2001 From: doodlum <15017472+doodlum@users.noreply.github.com> Date: Fri, 30 Jan 2026 00:49:43 +0000 Subject: [PATCH 48/63] chore: reduce noise --- package/Shaders/Common/ShadowSampling.hlsli | 29 ++++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/package/Shaders/Common/ShadowSampling.hlsli b/package/Shaders/Common/ShadowSampling.hlsli index 0076302ba0..d08fb66ec9 100644 --- a/package/Shaders/Common/ShadowSampling.hlsli +++ b/package/Shaders/Common/ShadowSampling.hlsli @@ -67,8 +67,11 @@ namespace ShadowSampling float Get3DFilteredShadow(float3 positionWS, float3 viewDirection, float2 screenPosition, uint eyeIndex) { - static const uint sampleCount = 8; - static const float rcpSampleCount = 1.0 / float(sampleCount); + static const uint sampleCount8 = 8; + static const float rcpSampleCount8 = 1.0 / float(sampleCount8); + + static const uint sampleCount16 = 16; + static const float rcpSampleCount16 = 1.0 / float(sampleCount16); float noise = Random::InterleavedGradientNoise(screenPosition, SharedData::FrameCount); float noiseTransform = noise * 2.0 - 1.0; @@ -89,14 +92,14 @@ namespace ShadowSampling #endif float worldShadow = 0; - for(uint i = 0; i < sampleCount; i++){ - uint noisyIndex = uint((float(i) + sampleCount * noise) % sampleCount); - float t = (float(sampleCount) - float(noisyIndex + 1)) * rcpSampleCount; - float tSample = t + noiseTransform * rcpSampleCount; + for(uint i = 0; i < sampleCount8; i++){ + uint noisyIndex = uint((float(i) + sampleCount8 * noise) % sampleCount8); + float t = (float(sampleCount8) - float(noisyIndex + 1)) * rcpSampleCount8; + float tSample = t + noiseTransform * rcpSampleCount8; worldShadow += ShadowSampling::GetWorldShadow(lerp(startPosition, endPosition, tSample), FrameBuffer::CameraPosAdjust[eyeIndex].xyz, eyeIndex); } - worldShadow *= rcpSampleCount; + worldShadow *= rcpSampleCount8; if (worldShadow == 0.0) return 0.0; @@ -122,9 +125,9 @@ namespace ShadowSampling } float shadow = 0.0; - for (uint k = 0; k < sampleCount; k++) { - uint noisyIndex = uint((float(k) + sampleCount * noise) % sampleCount); - float t = (float(sampleCount) - float(noisyIndex + 1)) * rcpSampleCount; + for (uint k = 0; k < sampleCount16; k++) { + uint noisyIndex = uint((float(k) + sampleCount16 * noise) % sampleCount16); + float t = (float(sampleCount16) - float(noisyIndex + 1)) * rcpSampleCount16; uint cascadeIndex = frac(t + noise) < cascade1Probability; float compareValue = compareValues[cascadeIndex]; @@ -133,11 +136,11 @@ namespace ShadowSampling float3 viewOffsetLS = viewOffsetsLS[cascadeIndex]; // Offset along view ray with optimised sample pattern - float tSample = t + noiseTransform * rcpSampleCount; + float tSample = t + noiseTransform * rcpSampleCount16; float3 sampledPositionLS = lerp(positionLS, viewOffsetLS, tSample); // Blur shadow with poisson disc - sampledPositionLS.xy += mul(Random::SpiralSampleOffsets8[k], rotationMatrix) * sampleRadius; + sampledPositionLS.xy += mul(Random::PoissonSampleOffsets16[k], rotationMatrix) * sampleRadius; // Average 4 shadow samples for improved quality float4 depths = SharedShadowMap.GatherRed(LinearSampler, float3(saturate(sampledPositionLS.xy), cascadeIndex), 0); @@ -145,7 +148,7 @@ namespace ShadowSampling } float fadeFactor = 1.0 - pow(saturate(dot(positionWS, positionWS) / sD.ShadowLightParam.z), 8); - return worldShadow * lerp(1.0, shadow * rcpSampleCount, fadeFactor); + return worldShadow * lerp(1.0, shadow * rcpSampleCount16, fadeFactor); } float Get2DFilteredShadowCascade(float noise, float2x2 rotationMatrix, float sampleOffsetScale, float2 baseUV, float cascadeIndex, float compareValue, uint eyeIndex) From a8a461ab0d0a2ad4c884ed07f1305aa60e3858db Mon Sep 17 00:00:00 2001 From: doodlum <15017472+doodlum@users.noreply.github.com> Date: Fri, 30 Jan 2026 23:45:33 +0000 Subject: [PATCH 49/63] chore: optimise sky static shadow --- package/Shaders/Effect.hlsl | 26 +++----------------------- 1 file changed, 3 insertions(+), 23 deletions(-) diff --git a/package/Shaders/Effect.hlsl b/package/Shaders/Effect.hlsl index 9093a8784c..4be7815120 100644 --- a/package/Shaders/Effect.hlsl +++ b/package/Shaders/Effect.hlsl @@ -582,23 +582,9 @@ float3 GetLightingColor(float3 msPosition, float3 worldPosition, float2 screenPo # else float3 GetLightingShadow(float3 color, float3 worldPosition, float2 screenPosition, float depth, uint eyeIndex, inout float shadowVariance) { -# if defined(SKYLIGHTING) -# if defined(VR) - float3 positionMSSkylight = worldPosition + FrameBuffer::CameraPosAdjust[eyeIndex].xyz - FrameBuffer::CameraPosAdjust[0].xyz; -# else - float3 positionMSSkylight = worldPosition; -# endif - - sh2 skylightingSH = Skylighting::sampleNoBias(SharedData::skylightingSettings, Skylighting::SkylightingProbeArray, positionMSSkylight); - - float skylightingDiffuse = SphericalHarmonics::FuncProductIntegral(skylightingSH, SphericalHarmonics::EvaluateCosineLobe(float3(0, 0, 1))) / Math::PI; - skylightingDiffuse = saturate(skylightingDiffuse); - skylightingDiffuse = lerp(1.0, skylightingDiffuse, Skylighting::getFadeOutFactor(worldPosition)); - skylightingDiffuse = Skylighting::mixDiffuse(SharedData::skylightingSettings, skylightingDiffuse); -# endif - float3 dirColor; float3 ambientColor; + float skylightingDiffuse = 1.0; # if defined(SKYLIGHTING) ShadowSampling::ExtractLighting(color, dirColor, ambientColor, skylightingDiffuse); # else @@ -616,10 +602,10 @@ float3 GetLightingShadow(float3 color, float3 worldPosition, float2 screenPositi // Enough for sky statics float maxDistance = max(0, SharedData::GetScreenDepth(depth)); - float viewRayLength = min(maxDistance, 4096); + float viewRayLength = 4096; float3 viewDirection = normalize(worldPosition); float3 startPosition = worldPosition - viewDirection * viewRayLength; - float3 endPosition = worldPosition + viewDirection * min(maxDistance, 4096); + float3 endPosition = worldPosition + viewDirection * min(maxDistance, viewRayLength); float shadow = 0; for(uint i = 0; i < sampleCount; i++){ @@ -635,12 +621,6 @@ float3 GetLightingShadow(float3 color, float3 worldPosition, float2 screenPositi dirColor *= shadow; -# if defined(SKYLIGHTING) - ambientColor = Color::IrradianceToLinear(ambientColor); - ambientColor *= skylightingDiffuse; - ambientColor = Color::IrradianceToGamma(ambientColor); -# endif - return dirColor + ambientColor; } #endif From 3c0b2a124e4a9d2fb41b310b5752b184073ceeb1 Mon Sep 17 00:00:00 2001 From: davo0411 Date: Mon, 2 Feb 2026 20:44:06 +1000 Subject: [PATCH 50/63] fix(unified-water): fix normal data for correct lod texture --- package/Shaders/Water.hlsl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package/Shaders/Water.hlsl b/package/Shaders/Water.hlsl index 0e98a83cf4..80b1558910 100644 --- a/package/Shaders/Water.hlsl +++ b/package/Shaders/Water.hlsl @@ -1118,9 +1118,9 @@ PS_OUTPUT main(PS_INPUT input) # endif #if defined(SKYLIGHTING) - WaterNormalData waterData = GetWaterNormal(input, distanceFactor, depthControl.z, viewDirection, depth, eyeIndex, wetnessOcclusion); + WaterNormalData waterData = GetWaterNormal(input, distanceBlendFactor, depthControl.z, viewDirection, depth, eyeIndex, wetnessOcclusion); #else - WaterNormalData waterData = GetWaterNormal(input, distanceFactor, depthControl.z, viewDirection, depth, eyeIndex, inWorld); + WaterNormalData waterData = GetWaterNormal(input, distanceBlendFactor, depthControl.z, viewDirection, depth, eyeIndex, inWorld); #endif float3 normal = waterData.normal; @@ -1334,4 +1334,4 @@ PS_OUTPUT main(PS_INPUT input) # endif -#endif \ No newline at end of file +#endif From d98a4b486f137eb511d863abd36482764f13fece Mon Sep 17 00:00:00 2001 From: doodlum <15017472+doodlum@users.noreply.github.com> Date: Mon, 2 Feb 2026 16:33:50 +0000 Subject: [PATCH 51/63] chore: optimise mip level --- package/Shaders/Common/ShadowSampling.hlsli | 6 +- package/Shaders/DownsampleShadowCS.hlsl | 56 ++++++++++++++--- src/Deferred.cpp | 69 +++++++++++++-------- src/Deferred.h | 7 ++- 4 files changed, 96 insertions(+), 42 deletions(-) diff --git a/package/Shaders/Common/ShadowSampling.hlsli b/package/Shaders/Common/ShadowSampling.hlsli index d08fb66ec9..b400ec2dd8 100644 --- a/package/Shaders/Common/ShadowSampling.hlsli +++ b/package/Shaders/Common/ShadowSampling.hlsli @@ -20,7 +20,7 @@ namespace ShadowSampling { - Texture2DArray SharedShadowMap : register(t18); + Texture2D SharedShadowMap : register(t18); struct ShadowData { @@ -143,7 +143,7 @@ namespace ShadowSampling sampledPositionLS.xy += mul(Random::PoissonSampleOffsets16[k], rotationMatrix) * sampleRadius; // Average 4 shadow samples for improved quality - float4 depths = SharedShadowMap.GatherRed(LinearSampler, float3(saturate(sampledPositionLS.xy), cascadeIndex), 0); + float4 depths = SharedShadowMap.SampleLevel(LinearSampler, saturate(sampledPositionLS.xy), 1 - cascadeIndex); shadow += dot(depths > compareValue, 0.25); } @@ -166,7 +166,7 @@ namespace ShadowSampling float2 sampleUV = layerIndexRcp * sampleOffset * sampleOffsetScale + baseUV; - float4 depths = SharedShadowMap.GatherRed(LinearSampler, float3(saturate(sampleUV), cascadeIndex), 0); + float4 depths = SharedShadowMap.SampleLevel(LinearSampler, saturate(sampleUV), 1 - cascadeIndex); visibility += dot(depths > compareValue, 0.25); } diff --git a/package/Shaders/DownsampleShadowCS.hlsl b/package/Shaders/DownsampleShadowCS.hlsl index 6cf71049f9..5f6fb9847c 100644 --- a/package/Shaders/DownsampleShadowCS.hlsl +++ b/package/Shaders/DownsampleShadowCS.hlsl @@ -1,22 +1,58 @@ Texture2DArray InputTexture : register(t0); -RWTexture2DArray OutputTexture : register(u0); +RWTexture2D OutputTexture : register(u0); SamplerState PointSampler : register(s0); -[numthreads(8, 8, 1)] void main(uint3 dispatchThreadID - : SV_DispatchThreadID) { - uint w, h, slices; - OutputTexture.GetDimensions(w, h, slices); +#if defined(DOWNSAMPLE_SHADOW_MIP0) +[numthreads(8, 8, 1)] void main(uint3 dispatchThreadID : SV_DispatchThreadID) { + uint w, h; + OutputTexture.GetDimensions(w, h); - if (dispatchThreadID.x < w && dispatchThreadID.y < h){ - // MIP 0 -> 1: each thread gathers a 2x2 block and computes max + if (dispatchThreadID.x < w && dispatchThreadID.y < h) { + // Each thread gathers a 2x2 block and packs into RGBA uint2 pixCoord = dispatchThreadID.xy * 2; uint inputW, inputH, inputSlices; InputTexture.GetDimensions(inputW, inputH, inputSlices); float2 uv = (pixCoord + 1.0) / float2(inputW, inputH); - float4 depths4 = InputTexture.GatherRed(PointSampler, float3(uv, dispatchThreadID.z)); - OutputTexture[dispatchThreadID.xyz] = dot(depths4, 0.25); + float4 depths4 = InputTexture.GatherRed(PointSampler, float3(uv, 1)); + + OutputTexture[dispatchThreadID.xy] = depths4; } -} \ No newline at end of file +} +#elif defined(DOWNSAMPLE_SHADOW_MIP1) +groupshared float g_scratchDepths[8][8]; + +[numthreads(8, 8, 1)] void main(uint3 dispatchThreadID : SV_DispatchThreadID, uint3 groupThreadID : SV_GroupThreadID) { + // MIP 0 -> 1: each thread gathers a 2x2 block and averages + uint2 pixCoord = dispatchThreadID.xy * 2; + + uint inputW, inputH, inputSlices; + InputTexture.GetDimensions(inputW, inputH, inputSlices); + float2 uv = (pixCoord + 1.0) / float2(inputW, inputH); + + float4 depths4 = InputTexture.GatherRed(PointSampler, float3(uv, 0)); + + g_scratchDepths[groupThreadID.x][groupThreadID.y] = dot(depths4, 0.25); + + GroupMemoryBarrierWithGroupSync(); + + // MIP 1 -> 2: 2x2 reduction in shared memory (4x4 total) + [branch] + if (all((groupThreadID.xy % 2) == 0)) { + uint2 outCoord = uint2(dispatchThreadID.xy / 2); + uint w, h, slices; + OutputTexture.GetDimensions(w, h); + [branch] if (outCoord.x < w && outCoord.y < h){ + float inTL = g_scratchDepths[groupThreadID.x + 0][groupThreadID.y + 0]; + float inTR = g_scratchDepths[groupThreadID.x + 1][groupThreadID.y + 0]; + float inBL = g_scratchDepths[groupThreadID.x + 0][groupThreadID.y + 1]; + float inBR = g_scratchDepths[groupThreadID.x + 1][groupThreadID.y + 1]; + OutputTexture[outCoord] = float4(inTL, inTR, inBL, inBR); + } + } +} +#else +#error "Error: Missing downsample scale" +#endif diff --git a/src/Deferred.cpp b/src/Deferred.cpp index 373cf239f5..271b89bdd4 100644 --- a/src/Deferred.cpp +++ b/src/Deferred.cpp @@ -156,7 +156,13 @@ void Deferred::SetupResources() perShadow->CreateUAV(uavDesc); copyShadowCS = static_cast(Util::CompileShader(L"Data\\Shaders\\CopyShadowDataCS.hlsl", {}, "cs_5_0")); - downsampleShadowCS = static_cast(Util::CompileShader(L"Data\\Shaders\\DownsampleShadowCS.hlsl", {}, "cs_5_0")); + + std::vector> defines; + defines.push_back({"DOWNSAMPLE_SHADOW_MIP0", nullptr}); + downsampleShadowMip0CS = static_cast(Util::CompileShader(L"Data\\Shaders\\DownsampleShadowCS.hlsl", defines, "cs_5_0")); + defines.clear(); + defines.push_back({"DOWNSAMPLE_SHADOW_MIP1", nullptr}); + downsampleShadowMip1CS = static_cast(Util::CompileShader(L"Data\\Shaders\\DownsampleShadowCS.hlsl", defines, "cs_5_0")); } { @@ -230,14 +236,18 @@ void Deferred::CopyShadowData() uint32_t newHeight = srcDesc.Height / 2; // Lazily create or recreate downscaled texture if dimensions changed - if (!shadowCopyTexture || shadowCopyWidth != newWidth || shadowCopyHeight != newHeight || shadowCopyArraySize != srcDesc.ArraySize) { + if (!shadowCopyTexture || shadowCopyWidth != newWidth || shadowCopyHeight != newHeight) { if (shadowCopySRV) { shadowCopySRV->Release(); shadowCopySRV = nullptr; } - if (shadowCopyUAV) { - shadowCopyUAV->Release(); - shadowCopyUAV = nullptr; + if (shadowCopyMip0UAV) { + shadowCopyMip0UAV->Release(); + shadowCopyMip0UAV = nullptr; + } + if (shadowCopyMip1UAV) { + shadowCopyMip1UAV->Release(); + shadowCopyMip1UAV = nullptr; } if (shadowCopyTexture) { shadowCopyTexture->Release(); @@ -246,50 +256,57 @@ void Deferred::CopyShadowData() shadowCopyWidth = newWidth; shadowCopyHeight = newHeight; - shadowCopyArraySize = srcDesc.ArraySize; D3D11_TEXTURE2D_DESC copyDesc{}; copyDesc.Width = newWidth; copyDesc.Height = newHeight; - copyDesc.MipLevels = 1; - copyDesc.ArraySize = srcDesc.ArraySize; - copyDesc.Format = DXGI_FORMAT_R16_UNORM; + copyDesc.MipLevels = 2; + copyDesc.ArraySize = 1; + copyDesc.Format = DXGI_FORMAT_R16G16B16A16_UNORM; copyDesc.SampleDesc.Count = 1; copyDesc.SampleDesc.Quality = 0; copyDesc.Usage = D3D11_USAGE_DEFAULT; - copyDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_UNORDERED_ACCESS; + copyDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_UNORDERED_ACCESS | D3D11_BIND_RENDER_TARGET; + copyDesc.MiscFlags = 0; auto device = globals::d3d::device; DX::ThrowIfFailed(device->CreateTexture2D(©Desc, nullptr, &shadowCopyTexture)); D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc{}; - srvDesc.Format = DXGI_FORMAT_R16_UNORM; - srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2DARRAY; - srvDesc.Texture2DArray.MostDetailedMip = 0; - srvDesc.Texture2DArray.MipLevels = 1; - srvDesc.Texture2DArray.FirstArraySlice = 0; - srvDesc.Texture2DArray.ArraySize = srcDesc.ArraySize; + srvDesc.Format = DXGI_FORMAT_R16G16B16A16_UNORM; + srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; + srvDesc.Texture2D.MostDetailedMip = 0; + srvDesc.Texture2D.MipLevels = 2; DX::ThrowIfFailed(device->CreateShaderResourceView(shadowCopyTexture, &srvDesc, &shadowCopySRV)); D3D11_UNORDERED_ACCESS_VIEW_DESC uavDesc{}; - uavDesc.Format = DXGI_FORMAT_R16_UNORM; - uavDesc.ViewDimension = D3D11_UAV_DIMENSION_TEXTURE2DARRAY; - uavDesc.Texture2DArray.MipSlice = 0; - uavDesc.Texture2DArray.FirstArraySlice = 0; - uavDesc.Texture2DArray.ArraySize = srcDesc.ArraySize; - DX::ThrowIfFailed(device->CreateUnorderedAccessView(shadowCopyTexture, &uavDesc, &shadowCopyUAV)); + uavDesc.Format = DXGI_FORMAT_R16G16B16A16_UNORM; + uavDesc.ViewDimension = D3D11_UAV_DIMENSION_TEXTURE2D; + uavDesc.Texture2D.MipSlice = 0; + DX::ThrowIfFailed(device->CreateUnorderedAccessView(shadowCopyTexture, &uavDesc, &shadowCopyMip0UAV)); + + uavDesc.Texture2D.MipSlice = 1; + DX::ThrowIfFailed(device->CreateUnorderedAccessView(shadowCopyTexture, &uavDesc, &shadowCopyMip1UAV)); } // Dispatch downsample compute shader ID3D11ShaderResourceView* csSrvs[1]{ shadowView }; context->CSSetShaderResources(0, 1, csSrvs); - ID3D11UnorderedAccessView* csUavs[1]{ shadowCopyUAV }; - context->CSSetUnorderedAccessViews(0, 1, csUavs, nullptr); context->CSSetSamplers(0, 1, &pointSampler); - context->CSSetShader(downsampleShadowCS, nullptr, 0); - context->Dispatch((shadowCopyWidth + 7) >> 3, (shadowCopyWidth + 7) >> 3, shadowCopyArraySize); + + // Mip 0 with second cascade + ID3D11UnorderedAccessView* csUavs[1]{ shadowCopyMip0UAV }; + context->CSSetUnorderedAccessViews(0, 1, csUavs, nullptr); + context->CSSetShader(downsampleShadowMip0CS, nullptr, 0); + context->Dispatch((shadowCopyWidth + 7) >> 3, (shadowCopyHeight + 7) >> 3, 1); + + // Mip 1 with first cascade + csUavs[0] = shadowCopyMip1UAV; + context->CSSetUnorderedAccessViews(0, 1, csUavs, nullptr); + context->CSSetShader(downsampleShadowMip1CS, nullptr, 0); + context->Dispatch((shadowCopyWidth + 7) >> 3, (shadowCopyHeight + 7) >> 3, 1); // Cleanup CS state csSrvs[0] = nullptr; diff --git a/src/Deferred.h b/src/Deferred.h index 27d627b58d..93664fe042 100644 --- a/src/Deferred.h +++ b/src/Deferred.h @@ -67,16 +67,17 @@ class Deferred STATIC_ASSERT_ALIGNAS_16(PerGeometry); ID3D11ComputeShader* copyShadowCS = nullptr; - ID3D11ComputeShader* downsampleShadowCS = nullptr; + ID3D11ComputeShader* downsampleShadowMip0CS = nullptr; + ID3D11ComputeShader* downsampleShadowMip1CS = nullptr; Buffer* perShadow = nullptr; ID3D11ShaderResourceView* shadowView = nullptr; ID3D11Texture2D* shadowCopyTexture = nullptr; ID3D11ShaderResourceView* shadowCopySRV = nullptr; - ID3D11UnorderedAccessView* shadowCopyUAV = nullptr; + ID3D11UnorderedAccessView* shadowCopyMip0UAV = nullptr; + ID3D11UnorderedAccessView* shadowCopyMip1UAV = nullptr; uint32_t shadowCopyWidth = 0; uint32_t shadowCopyHeight = 0; - uint32_t shadowCopyArraySize = 0; struct Hooks { From 3e63452451fad7239b4263b80acbfab0f34965d0 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 2 Feb 2026 16:35:49 +0000 Subject: [PATCH 52/63] =?UTF-8?q?style:=20=F0=9F=8E=A8=20apply=20pre-commi?= =?UTF-8?q?t.ci=20formatting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Automated formatting by clang-format, prettier, and other hooks. See https://pre-commit.ci for details. --- package/Shaders/DownsampleShadowCS.hlsl | 2 +- src/Deferred.cpp | 11 +++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/package/Shaders/DownsampleShadowCS.hlsl b/package/Shaders/DownsampleShadowCS.hlsl index 5f6fb9847c..43bc977e58 100644 --- a/package/Shaders/DownsampleShadowCS.hlsl +++ b/package/Shaders/DownsampleShadowCS.hlsl @@ -17,7 +17,7 @@ SamplerState PointSampler : register(s0); float2 uv = (pixCoord + 1.0) / float2(inputW, inputH); float4 depths4 = InputTexture.GatherRed(PointSampler, float3(uv, 1)); - + OutputTexture[dispatchThreadID.xy] = depths4; } } diff --git a/src/Deferred.cpp b/src/Deferred.cpp index 271b89bdd4..06191cf9b3 100644 --- a/src/Deferred.cpp +++ b/src/Deferred.cpp @@ -156,12 +156,12 @@ void Deferred::SetupResources() perShadow->CreateUAV(uavDesc); copyShadowCS = static_cast(Util::CompileShader(L"Data\\Shaders\\CopyShadowDataCS.hlsl", {}, "cs_5_0")); - + std::vector> defines; - defines.push_back({"DOWNSAMPLE_SHADOW_MIP0", nullptr}); + defines.push_back({ "DOWNSAMPLE_SHADOW_MIP0", nullptr }); downsampleShadowMip0CS = static_cast(Util::CompileShader(L"Data\\Shaders\\DownsampleShadowCS.hlsl", defines, "cs_5_0")); defines.clear(); - defines.push_back({"DOWNSAMPLE_SHADOW_MIP1", nullptr}); + defines.push_back({ "DOWNSAMPLE_SHADOW_MIP1", nullptr }); downsampleShadowMip1CS = static_cast(Util::CompileShader(L"Data\\Shaders\\DownsampleShadowCS.hlsl", defines, "cs_5_0")); } @@ -284,7 +284,7 @@ void Deferred::CopyShadowData() uavDesc.ViewDimension = D3D11_UAV_DIMENSION_TEXTURE2D; uavDesc.Texture2D.MipSlice = 0; DX::ThrowIfFailed(device->CreateUnorderedAccessView(shadowCopyTexture, &uavDesc, &shadowCopyMip0UAV)); - + uavDesc.Texture2D.MipSlice = 1; DX::ThrowIfFailed(device->CreateUnorderedAccessView(shadowCopyTexture, &uavDesc, &shadowCopyMip1UAV)); } @@ -293,7 +293,6 @@ void Deferred::CopyShadowData() ID3D11ShaderResourceView* csSrvs[1]{ shadowView }; context->CSSetShaderResources(0, 1, csSrvs); - context->CSSetSamplers(0, 1, &pointSampler); // Mip 0 with second cascade @@ -301,7 +300,7 @@ void Deferred::CopyShadowData() context->CSSetUnorderedAccessViews(0, 1, csUavs, nullptr); context->CSSetShader(downsampleShadowMip0CS, nullptr, 0); context->Dispatch((shadowCopyWidth + 7) >> 3, (shadowCopyHeight + 7) >> 3, 1); - + // Mip 1 with first cascade csUavs[0] = shadowCopyMip1UAV; context->CSSetUnorderedAccessViews(0, 1, csUavs, nullptr); From ea817313c0319434c8ade2181061bc2a587252fc Mon Sep 17 00:00:00 2001 From: doodlum <15017472+doodlum@users.noreply.github.com> Date: Mon, 2 Feb 2026 17:40:15 +0000 Subject: [PATCH 53/63] feat: third cascade --- package/Shaders/Common/ShadowSampling.hlsli | 32 +++++---- package/Shaders/DownsampleShadowCS.hlsl | 74 ++++++++++++++++----- src/Deferred.cpp | 25 +++++-- src/Deferred.h | 2 + 4 files changed, 98 insertions(+), 35 deletions(-) diff --git a/package/Shaders/Common/ShadowSampling.hlsli b/package/Shaders/Common/ShadowSampling.hlsli index b400ec2dd8..4572630c5e 100644 --- a/package/Shaders/Common/ShadowSampling.hlsli +++ b/package/Shaders/Common/ShadowSampling.hlsli @@ -107,18 +107,24 @@ namespace ShadowSampling float shadowMapDepth = GetShadowDepth(positionWS, eyeIndex); ShadowData sD = SharedShadowData[0]; - if (sD.EndSplitDistances.z < shadowMapDepth) + if (sD.EndSplitDistances.w < shadowMapDepth) // Early out beyond cascade 2 return worldShadow; + // Calculate blend probabilities for cascade transitions float cascade1Probability = saturate((shadowMapDepth - sD.StartSplitDistances.y) / (sD.EndSplitDistances.x - sD.StartSplitDistances.y)); - - // Precompute cascade data for both cascades - float compareValues[2]; - float sampleRadii[2]; - float3 positionsLS[2]; - float3 viewOffsetsLS[2]; - for (uint cascadeIdx = 0; cascadeIdx < 2; cascadeIdx++) { - compareValues[cascadeIdx] = mul(transpose(sD.ShadowMapProj[eyeIndex][cascadeIdx]), float4(positionWS, 1)).z - sD.AlphaTestRef[1 + cascadeIdx]; + float cascade2Probability = saturate((shadowMapDepth - sD.StartSplitDistances.z) / (sD.EndSplitDistances.y - sD.StartSplitDistances.z)); + + // Determine which cascade pair to blend between + bool inFarRegion = shadowMapDepth > sD.EndSplitDistances.x; + if (inFarRegion) cascade1Probability = cascade2Probability; + + // Precompute cascade data for all 3 cascades + float compareValues[3]; + float sampleRadii[3]; + float3 positionsLS[3]; + float3 viewOffsetsLS[3]; + for (uint cascadeIdx = 0; cascadeIdx < 3; cascadeIdx++) { + compareValues[cascadeIdx] = mul(transpose(sD.ShadowMapProj[eyeIndex][cascadeIdx]), float4(positionWS, 1)).z - sD.AlphaTestRef[cascadeIdx ? 1 : 2]; sampleRadii[cascadeIdx] = sD.ShadowSampleParam.z * rcp(1 + cascadeIdx) * 2.0; positionsLS[cascadeIdx] = mul(transpose(sD.ShadowMapProj[eyeIndex][cascadeIdx]), float4(startPosition, 1)); viewOffsetsLS[cascadeIdx] = mul(transpose(sD.ShadowMapProj[eyeIndex][cascadeIdx]), float4(endPosition, 1)); @@ -128,8 +134,10 @@ namespace ShadowSampling for (uint k = 0; k < sampleCount16; k++) { uint noisyIndex = uint((float(k) + sampleCount16 * noise) % sampleCount16); float t = (float(sampleCount16) - float(noisyIndex + 1)) * rcpSampleCount16; - uint cascadeIndex = frac(t + noise) < cascade1Probability; - + + // Probabilistically select cascade based on region + uint cascadeIndex = uint(inFarRegion) + (frac(t + noise) < cascade1Probability); + float compareValue = compareValues[cascadeIndex]; float sampleRadius = sampleRadii[cascadeIndex]; float3 positionLS = positionsLS[cascadeIndex]; @@ -143,7 +151,7 @@ namespace ShadowSampling sampledPositionLS.xy += mul(Random::PoissonSampleOffsets16[k], rotationMatrix) * sampleRadius; // Average 4 shadow samples for improved quality - float4 depths = SharedShadowMap.SampleLevel(LinearSampler, saturate(sampledPositionLS.xy), 1 - cascadeIndex); + float4 depths = SharedShadowMap.SampleLevel(LinearSampler, saturate(sampledPositionLS.xy), 2 - cascadeIndex); shadow += dot(depths > compareValue, 0.25); } diff --git a/package/Shaders/DownsampleShadowCS.hlsl b/package/Shaders/DownsampleShadowCS.hlsl index 5f6fb9847c..baa2051722 100644 --- a/package/Shaders/DownsampleShadowCS.hlsl +++ b/package/Shaders/DownsampleShadowCS.hlsl @@ -8,17 +8,16 @@ SamplerState PointSampler : register(s0); uint w, h; OutputTexture.GetDimensions(w, h); - if (dispatchThreadID.x < w && dispatchThreadID.y < h) { + if (dispatchThreadID.x < w && dispatchThreadID.y < h) + { // Each thread gathers a 2x2 block and packs into RGBA uint2 pixCoord = dispatchThreadID.xy * 2; uint inputW, inputH, inputSlices; InputTexture.GetDimensions(inputW, inputH, inputSlices); - float2 uv = (pixCoord + 1.0) / float2(inputW, inputH); + float2 uv = (pixCoord + 0.5) / float2(inputW, inputH); - float4 depths4 = InputTexture.GatherRed(PointSampler, float3(uv, 1)); - - OutputTexture[dispatchThreadID.xy] = depths4; + OutputTexture[dispatchThreadID.xy] = InputTexture.GatherRed(PointSampler, float3(uv, 2)); } } #elif defined(DOWNSAMPLE_SHADOW_MIP1) @@ -30,7 +29,38 @@ groupshared float g_scratchDepths[8][8]; uint inputW, inputH, inputSlices; InputTexture.GetDimensions(inputW, inputH, inputSlices); - float2 uv = (pixCoord + 1.0) / float2(inputW, inputH); + float2 uv = (pixCoord + 0.5) / float2(inputW, inputH); + + float4 depths4 = InputTexture.GatherRed(PointSampler, float3(uv, 1)); + + g_scratchDepths[groupThreadID.x][groupThreadID.y] = dot(depths4, 0.25); + + GroupMemoryBarrierWithGroupSync(); + + // MIP 1 -> 2: 2x2 reduction in shared memory (4x4 total) + [branch] + if (all((groupThreadID.xy % 2) == 0)) + { + float inTL = g_scratchDepths[groupThreadID.x + 0][groupThreadID.y + 0]; + float inTR = g_scratchDepths[groupThreadID.x + 1][groupThreadID.y + 0]; + float inBL = g_scratchDepths[groupThreadID.x + 0][groupThreadID.y + 1]; + float inBR = g_scratchDepths[groupThreadID.x + 1][groupThreadID.y + 1]; + OutputTexture[dispatchThreadID.xy / 2] = float4(inTL, inTR, inBL, inBR); + } +} +#elif defined(DOWNSAMPLE_SHADOW_MIP2) +groupshared float g_scratchDepths[8][8]; + +[numthreads(8, 8, 1)] void main(uint3 dispatchThreadID : SV_DispatchThreadID, uint3 groupThreadID : SV_GroupThreadID) { + uint w, h; + OutputTexture.GetDimensions(w, h); + + // MIP 0 -> 1: each thread gathers a 2x2 block and averages + uint2 pixCoord = dispatchThreadID.xy * 2; + + uint inputW, inputH, inputSlices; + InputTexture.GetDimensions(inputW, inputH, inputSlices); + float2 uv = (pixCoord + 0.5) / float2(inputW, inputH); float4 depths4 = InputTexture.GatherRed(PointSampler, float3(uv, 0)); @@ -40,19 +70,27 @@ groupshared float g_scratchDepths[8][8]; // MIP 1 -> 2: 2x2 reduction in shared memory (4x4 total) [branch] - if (all((groupThreadID.xy % 2) == 0)) { - uint2 outCoord = uint2(dispatchThreadID.xy / 2); - uint w, h, slices; - OutputTexture.GetDimensions(w, h); - [branch] if (outCoord.x < w && outCoord.y < h){ - float inTL = g_scratchDepths[groupThreadID.x + 0][groupThreadID.y + 0]; - float inTR = g_scratchDepths[groupThreadID.x + 1][groupThreadID.y + 0]; - float inBL = g_scratchDepths[groupThreadID.x + 0][groupThreadID.y + 1]; - float inBR = g_scratchDepths[groupThreadID.x + 1][groupThreadID.y + 1]; - OutputTexture[outCoord] = float4(inTL, inTR, inBL, inBR); - } + if (all((groupThreadID.xy % 2) == 0)) + { + float inTL = g_scratchDepths[groupThreadID.x + 0][groupThreadID.y + 0]; + float inTR = g_scratchDepths[groupThreadID.x + 1][groupThreadID.y + 0]; + float inBL = g_scratchDepths[groupThreadID.x + 0][groupThreadID.y + 1]; + float inBR = g_scratchDepths[groupThreadID.x + 1][groupThreadID.y + 1]; + g_scratchDepths[groupThreadID.x][groupThreadID.y] = dot(float4(inTL, inTR, inBL, inBR), 0.25); + } + + GroupMemoryBarrierWithGroupSync(); + + // MIP 2 -> 3: 2x2 reduction in shared memory (8x8 total) + [branch] if (all((groupThreadID.xy % 4) == 0)) + { + float inTL = g_scratchDepths[groupThreadID.x + 0][groupThreadID.y + 0]; + float inTR = g_scratchDepths[groupThreadID.x + 2][groupThreadID.y + 0]; + float inBL = g_scratchDepths[groupThreadID.x + 0][groupThreadID.y + 2]; + float inBR = g_scratchDepths[groupThreadID.x + 2][groupThreadID.y + 2]; + OutputTexture[dispatchThreadID.xy / 4] = float4(inTL, inTR, inBL, inBR); } } #else -#error "Error: Missing downsample scale" +# error "Error: Missing downsample scale" #endif diff --git a/src/Deferred.cpp b/src/Deferred.cpp index 271b89bdd4..c6964b76af 100644 --- a/src/Deferred.cpp +++ b/src/Deferred.cpp @@ -163,6 +163,9 @@ void Deferred::SetupResources() defines.clear(); defines.push_back({"DOWNSAMPLE_SHADOW_MIP1", nullptr}); downsampleShadowMip1CS = static_cast(Util::CompileShader(L"Data\\Shaders\\DownsampleShadowCS.hlsl", defines, "cs_5_0")); + defines.clear(); + defines.push_back({"DOWNSAMPLE_SHADOW_MIP2", nullptr}); + downsampleShadowMip2CS = static_cast(Util::CompileShader(L"Data\\Shaders\\DownsampleShadowCS.hlsl", defines, "cs_5_0")); } { @@ -249,6 +252,10 @@ void Deferred::CopyShadowData() shadowCopyMip1UAV->Release(); shadowCopyMip1UAV = nullptr; } + if (shadowCopyMip2UAV) { + shadowCopyMip2UAV->Release(); + shadowCopyMip2UAV = nullptr; + } if (shadowCopyTexture) { shadowCopyTexture->Release(); shadowCopyTexture = nullptr; @@ -260,7 +267,7 @@ void Deferred::CopyShadowData() D3D11_TEXTURE2D_DESC copyDesc{}; copyDesc.Width = newWidth; copyDesc.Height = newHeight; - copyDesc.MipLevels = 2; + copyDesc.MipLevels = 3; copyDesc.ArraySize = 1; copyDesc.Format = DXGI_FORMAT_R16G16B16A16_UNORM; copyDesc.SampleDesc.Count = 1; @@ -276,7 +283,7 @@ void Deferred::CopyShadowData() srvDesc.Format = DXGI_FORMAT_R16G16B16A16_UNORM; srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; srvDesc.Texture2D.MostDetailedMip = 0; - srvDesc.Texture2D.MipLevels = 2; + srvDesc.Texture2D.MipLevels = 3; DX::ThrowIfFailed(device->CreateShaderResourceView(shadowCopyTexture, &srvDesc, &shadowCopySRV)); D3D11_UNORDERED_ACCESS_VIEW_DESC uavDesc{}; @@ -287,27 +294,35 @@ void Deferred::CopyShadowData() uavDesc.Texture2D.MipSlice = 1; DX::ThrowIfFailed(device->CreateUnorderedAccessView(shadowCopyTexture, &uavDesc, &shadowCopyMip1UAV)); + + uavDesc.Texture2D.MipSlice = 2; + DX::ThrowIfFailed(device->CreateUnorderedAccessView(shadowCopyTexture, &uavDesc, &shadowCopyMip2UAV)); } // Dispatch downsample compute shader ID3D11ShaderResourceView* csSrvs[1]{ shadowView }; context->CSSetShaderResources(0, 1, csSrvs); - context->CSSetSamplers(0, 1, &pointSampler); - // Mip 0 with second cascade + // Mip 0 with third cascade ID3D11UnorderedAccessView* csUavs[1]{ shadowCopyMip0UAV }; context->CSSetUnorderedAccessViews(0, 1, csUavs, nullptr); context->CSSetShader(downsampleShadowMip0CS, nullptr, 0); context->Dispatch((shadowCopyWidth + 7) >> 3, (shadowCopyHeight + 7) >> 3, 1); - // Mip 1 with first cascade + // Mip 1 with second cascade csUavs[0] = shadowCopyMip1UAV; context->CSSetUnorderedAccessViews(0, 1, csUavs, nullptr); context->CSSetShader(downsampleShadowMip1CS, nullptr, 0); context->Dispatch((shadowCopyWidth + 7) >> 3, (shadowCopyHeight + 7) >> 3, 1); + // Mip 2 with third cascade + csUavs[0] = shadowCopyMip2UAV; + context->CSSetUnorderedAccessViews(0, 1, csUavs, nullptr); + context->CSSetShader(downsampleShadowMip2CS, nullptr, 0); + context->Dispatch((shadowCopyWidth + 7) >> 3, (shadowCopyHeight + 7) >> 3, 1); + // Cleanup CS state csSrvs[0] = nullptr; context->CSSetShaderResources(0, 1, csSrvs); diff --git a/src/Deferred.h b/src/Deferred.h index 93664fe042..34905cfe09 100644 --- a/src/Deferred.h +++ b/src/Deferred.h @@ -69,6 +69,7 @@ class Deferred ID3D11ComputeShader* copyShadowCS = nullptr; ID3D11ComputeShader* downsampleShadowMip0CS = nullptr; ID3D11ComputeShader* downsampleShadowMip1CS = nullptr; + ID3D11ComputeShader* downsampleShadowMip2CS = nullptr; Buffer* perShadow = nullptr; ID3D11ShaderResourceView* shadowView = nullptr; @@ -76,6 +77,7 @@ class Deferred ID3D11ShaderResourceView* shadowCopySRV = nullptr; ID3D11UnorderedAccessView* shadowCopyMip0UAV = nullptr; ID3D11UnorderedAccessView* shadowCopyMip1UAV = nullptr; + ID3D11UnorderedAccessView* shadowCopyMip2UAV = nullptr; uint32_t shadowCopyWidth = 0; uint32_t shadowCopyHeight = 0; From edf67146f3da841b70fb1a6b1961d57cccbbfe9b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 2 Feb 2026 17:42:49 +0000 Subject: [PATCH 54/63] =?UTF-8?q?style:=20=F0=9F=8E=A8=20apply=20pre-commi?= =?UTF-8?q?t.ci=20formatting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Automated formatting by clang-format, prettier, and other hooks. See https://pre-commit.ci for details. --- package/Shaders/Common/ShadowSampling.hlsli | 4 ++-- package/Shaders/DownsampleShadowCS.hlsl | 14 +++++++------- src/Deferred.cpp | 4 ++-- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/package/Shaders/Common/ShadowSampling.hlsli b/package/Shaders/Common/ShadowSampling.hlsli index 4572630c5e..3d9371dd90 100644 --- a/package/Shaders/Common/ShadowSampling.hlsli +++ b/package/Shaders/Common/ShadowSampling.hlsli @@ -134,10 +134,10 @@ namespace ShadowSampling for (uint k = 0; k < sampleCount16; k++) { uint noisyIndex = uint((float(k) + sampleCount16 * noise) % sampleCount16); float t = (float(sampleCount16) - float(noisyIndex + 1)) * rcpSampleCount16; - + // Probabilistically select cascade based on region uint cascadeIndex = uint(inFarRegion) + (frac(t + noise) < cascade1Probability); - + float compareValue = compareValues[cascadeIndex]; float sampleRadius = sampleRadii[cascadeIndex]; float3 positionLS = positionsLS[cascadeIndex]; diff --git a/package/Shaders/DownsampleShadowCS.hlsl b/package/Shaders/DownsampleShadowCS.hlsl index 5523fef88d..a61ae62289 100644 --- a/package/Shaders/DownsampleShadowCS.hlsl +++ b/package/Shaders/DownsampleShadowCS.hlsl @@ -8,7 +8,7 @@ SamplerState PointSampler : register(s0); uint w, h; OutputTexture.GetDimensions(w, h); - if (dispatchThreadID.x < w && dispatchThreadID.y < h) + if (dispatchThreadID.x < w && dispatchThreadID.y < h) { // Each thread gathers a 2x2 block and packs into RGBA uint2 pixCoord = dispatchThreadID.xy * 2; @@ -40,13 +40,13 @@ groupshared float g_scratchDepths[8][8]; // MIP 1 -> 2: 2x2 reduction in shared memory (4x4 total) [branch] - if (all((groupThreadID.xy % 2) == 0)) + if (all((groupThreadID.xy % 2) == 0)) { float inTL = g_scratchDepths[groupThreadID.x + 0][groupThreadID.y + 0]; float inTR = g_scratchDepths[groupThreadID.x + 1][groupThreadID.y + 0]; float inBL = g_scratchDepths[groupThreadID.x + 0][groupThreadID.y + 1]; - float inBR = g_scratchDepths[groupThreadID.x + 1][groupThreadID.y + 1]; - OutputTexture[dispatchThreadID.xy / 2] = float4(inTL, inTR, inBL, inBR); + float inBR = g_scratchDepths[groupThreadID.x + 1][groupThreadID.y + 1]; + OutputTexture[dispatchThreadID.xy / 2] = float4(inTL, inTR, inBL, inBR); } } #elif defined(DOWNSAMPLE_SHADOW_MIP2) @@ -71,12 +71,12 @@ groupshared float g_scratchDepths[8][8]; // MIP 1 -> 2: 2x2 reduction in shared memory (4x4 total) [branch] - if (all((groupThreadID.xy % 2) == 0)) + if (all((groupThreadID.xy % 2) == 0)) { float inTL = g_scratchDepths[groupThreadID.x + 0][groupThreadID.y + 0]; float inTR = g_scratchDepths[groupThreadID.x + 1][groupThreadID.y + 0]; float inBL = g_scratchDepths[groupThreadID.x + 0][groupThreadID.y + 1]; - float inBR = g_scratchDepths[groupThreadID.x + 1][groupThreadID.y + 1]; + float inBR = g_scratchDepths[groupThreadID.x + 1][groupThreadID.y + 1]; g_scratchDepths[groupThreadID.x][groupThreadID.y] = dot(float4(inTL, inTR, inBL, inBR), 0.25); } @@ -89,7 +89,7 @@ groupshared float g_scratchDepths[8][8]; float inTR = g_scratchDepths[groupThreadID.x + 2][groupThreadID.y + 0]; float inBL = g_scratchDepths[groupThreadID.x + 0][groupThreadID.y + 2]; float inBR = g_scratchDepths[groupThreadID.x + 2][groupThreadID.y + 2]; - OutputTexture[dispatchThreadID.xy / 4] = float4(inTL, inTR, inBL, inBR); + OutputTexture[dispatchThreadID.xy / 4] = float4(inTL, inTR, inBL, inBR); } } #else diff --git a/src/Deferred.cpp b/src/Deferred.cpp index 0b8311eabc..1db967d0f7 100644 --- a/src/Deferred.cpp +++ b/src/Deferred.cpp @@ -164,7 +164,7 @@ void Deferred::SetupResources() defines.push_back({ "DOWNSAMPLE_SHADOW_MIP1", nullptr }); downsampleShadowMip1CS = static_cast(Util::CompileShader(L"Data\\Shaders\\DownsampleShadowCS.hlsl", defines, "cs_5_0")); defines.clear(); - defines.push_back({"DOWNSAMPLE_SHADOW_MIP2", nullptr}); + defines.push_back({ "DOWNSAMPLE_SHADOW_MIP2", nullptr }); downsampleShadowMip2CS = static_cast(Util::CompileShader(L"Data\\Shaders\\DownsampleShadowCS.hlsl", defines, "cs_5_0")); } @@ -310,7 +310,7 @@ void Deferred::CopyShadowData() context->CSSetUnorderedAccessViews(0, 1, csUavs, nullptr); context->CSSetShader(downsampleShadowMip0CS, nullptr, 0); context->Dispatch((shadowCopyWidth + 7) >> 3, (shadowCopyHeight + 7) >> 3, 1); - + // Mip 1 with second cascade csUavs[0] = shadowCopyMip1UAV; context->CSSetUnorderedAccessViews(0, 1, csUavs, nullptr); From 92a1a595732ebacd20130e9cbbb43630b4e672cd Mon Sep 17 00:00:00 2001 From: doodlum <15017472+doodlum@users.noreply.github.com> Date: Mon, 2 Feb 2026 18:30:22 +0000 Subject: [PATCH 55/63] chore: auto set shadow settings --- src/Hooks.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Hooks.cpp b/src/Hooks.cpp index 6667c8dafb..ac566d77ae 100644 --- a/src/Hooks.cpp +++ b/src/Hooks.cpp @@ -355,6 +355,11 @@ struct BSShaderRenderTargets_Create */ static void thunk() { + auto iniPrefSettingCollection = RE::INIPrefSettingCollection::GetSingleton(); + iniPrefSettingCollection->GetSetting("iNumSplits:Display")->data.i = 3; + iniPrefSettingCollection->GetSetting("iShadowMapResolution:Display")->data.i = 2048; + iniPrefSettingCollection->GetSetting("iNumFocusShadow:Display")->data.i = 0; + func(); globals::ReInit(); globals::state->Setup(); From 2029401aa56850c275964914221909c4110de373 Mon Sep 17 00:00:00 2001 From: doodlum <15017472+doodlum@users.noreply.github.com> Date: Mon, 2 Feb 2026 18:30:46 +0000 Subject: [PATCH 56/63] chore: optimise cascade selection --- package/Shaders/Common/ShadowSampling.hlsli | 76 +++++++++++---------- 1 file changed, 39 insertions(+), 37 deletions(-) diff --git a/package/Shaders/Common/ShadowSampling.hlsli b/package/Shaders/Common/ShadowSampling.hlsli index 3d9371dd90..1244518f19 100644 --- a/package/Shaders/Common/ShadowSampling.hlsli +++ b/package/Shaders/Common/ShadowSampling.hlsli @@ -69,9 +69,11 @@ namespace ShadowSampling { static const uint sampleCount8 = 8; static const float rcpSampleCount8 = 1.0 / float(sampleCount8); + static const uint sampleCount8Minus1 = 7; static const uint sampleCount16 = 16; static const float rcpSampleCount16 = 1.0 / float(sampleCount16); + static const uint sampleCount16Minus1 = sampleCount16 - 1; float noise = Random::InterleavedGradientNoise(screenPosition, SharedData::FrameCount); float noiseTransform = noise * 2.0 - 1.0; @@ -81,12 +83,12 @@ namespace ShadowSampling #if defined(EFFECT) // Enough for non-billboards + enough for Sovngarde fog - float viewRayLength = min(Permutation::EffectRadius * 0.1, 128); + float viewRayLength = min(Permutation::EffectRadius * 0.2, 256); float3 startPosition = positionWS - viewDirection * viewRayLength; float3 endPosition = positionWS + viewDirection * viewRayLength; #else // Enough for Eastmarch water - float viewRayLength = 128.0; + float viewRayLength = 256.0; float3 startPosition = positionWS; float3 endPosition = positionWS + viewDirection * viewRayLength; #endif @@ -94,69 +96,69 @@ namespace ShadowSampling float worldShadow = 0; for(uint i = 0; i < sampleCount8; i++){ uint noisyIndex = uint((float(i) + sampleCount8 * noise) % sampleCount8); - float t = (float(sampleCount8) - float(noisyIndex + 1)) * rcpSampleCount8; + float t = (float(sampleCount8Minus1) - float(noisyIndex)) * rcpSampleCount8; float tSample = t + noiseTransform * rcpSampleCount8; worldShadow += ShadowSampling::GetWorldShadow(lerp(startPosition, endPosition, tSample), FrameBuffer::CameraPosAdjust[eyeIndex].xyz, eyeIndex); } - worldShadow *= rcpSampleCount8; - if (worldShadow == 0.0) return 0.0; + worldShadow *= rcpSampleCount8; + float shadowMapDepth = GetShadowDepth(positionWS, eyeIndex); ShadowData sD = SharedShadowData[0]; if (sD.EndSplitDistances.w < shadowMapDepth) // Early out beyond cascade 2 return worldShadow; - // Calculate blend probabilities for cascade transitions - float cascade1Probability = saturate((shadowMapDepth - sD.StartSplitDistances.y) / (sD.EndSplitDistances.x - sD.StartSplitDistances.y)); - float cascade2Probability = saturate((shadowMapDepth - sD.StartSplitDistances.z) / (sD.EndSplitDistances.y - sD.StartSplitDistances.z)); - // Determine which cascade pair to blend between - bool inFarRegion = shadowMapDepth > sD.EndSplitDistances.x; - if (inFarRegion) cascade1Probability = cascade2Probability; - - // Precompute cascade data for all 3 cascades - float compareValues[3]; - float sampleRadii[3]; - float3 positionsLS[3]; - float3 viewOffsetsLS[3]; - for (uint cascadeIdx = 0; cascadeIdx < 3; cascadeIdx++) { - compareValues[cascadeIdx] = mul(transpose(sD.ShadowMapProj[eyeIndex][cascadeIdx]), float4(positionWS, 1)).z - sD.AlphaTestRef[cascadeIdx ? 1 : 2]; - sampleRadii[cascadeIdx] = sD.ShadowSampleParam.z * rcp(1 + cascadeIdx) * 2.0; - positionsLS[cascadeIdx] = mul(transpose(sD.ShadowMapProj[eyeIndex][cascadeIdx]), float4(startPosition, 1)); - viewOffsetsLS[cascadeIdx] = mul(transpose(sD.ShadowMapProj[eyeIndex][cascadeIdx]), float4(endPosition, 1)); + bool inFarRegion = sD.EndSplitDistances.x < shadowMapDepth; + float2 blendRange = inFarRegion + ? float2(sD.StartSplitDistances.z, sD.EndSplitDistances.y) + : float2(sD.StartSplitDistances.y, sD.EndSplitDistances.x); + float cascadeProbability = saturate((shadowMapDepth - blendRange.x) / (blendRange.y - blendRange.x)); + + // Precompute cascade data + uint baseCascade = uint(inFarRegion); + + float compareValues[2]; + float sampleRadii[2]; + float3 positionsLS[2]; + float3 viewOffsetsLS[2]; + + for (uint i = 0; i < 2; i++) { + uint cascadeIdx = baseCascade + i; + float4x3 proj = sD.ShadowMapProj[eyeIndex][cascadeIdx]; + compareValues[i] = mul(transpose(proj), float4(positionWS, 1)).z - sD.AlphaTestRef[1 + min(1, cascadeIdx)]; + sampleRadii[i] = sD.ShadowSampleParam.z * exp2(-float(cascadeIdx)) * 2.0; + positionsLS[i] = mul(transpose(proj), float4(startPosition, 1)); + viewOffsetsLS[i] = mul(transpose(proj), float4(endPosition, 1)); } float shadow = 0.0; for (uint k = 0; k < sampleCount16; k++) { - uint noisyIndex = uint((float(k) + sampleCount16 * noise) % sampleCount16); - float t = (float(sampleCount16) - float(noisyIndex + 1)) * rcpSampleCount16; - - // Probabilistically select cascade based on region - uint cascadeIndex = uint(inFarRegion) + (frac(t + noise) < cascade1Probability); - - float compareValue = compareValues[cascadeIndex]; - float sampleRadius = sampleRadii[cascadeIndex]; - float3 positionLS = positionsLS[cascadeIndex]; - float3 viewOffsetLS = viewOffsetsLS[cascadeIndex]; + uint noisyIndex = (k + uint(sampleCount16 * noise)) % sampleCount16; + float t = (sampleCount16 - 1 - noisyIndex) * rcpSampleCount16; + + // Probabilistically select cascade (0 or 1 within the pair) + uint cascadeIndex = uint(frac(t + noise) < cascadeProbability); // Offset along view ray with optimised sample pattern float tSample = t + noiseTransform * rcpSampleCount16; - float3 sampledPositionLS = lerp(positionLS, viewOffsetLS, tSample); + float3 sampledPositionLS = lerp(positionsLS[cascadeIndex], viewOffsetsLS[cascadeIndex], tSample); // Blur shadow with poisson disc - sampledPositionLS.xy += mul(Random::PoissonSampleOffsets16[k], rotationMatrix) * sampleRadius; + sampledPositionLS.xy += mul(Random::PoissonSampleOffsets16[k], rotationMatrix) * sampleRadii[cascadeIndex]; // Average 4 shadow samples for improved quality - float4 depths = SharedShadowMap.SampleLevel(LinearSampler, saturate(sampledPositionLS.xy), 2 - cascadeIndex); - shadow += dot(depths > compareValue, 0.25); + float4 depths = SharedShadowMap.SampleLevel(LinearSampler, saturate(sampledPositionLS.xy), 2u - baseCascade - cascadeIndex); + float4 shadowVisibilities = float4(depths > compareValues[cascadeIndex]); + shadow += shadowVisibilities.x + shadowVisibilities.y + shadowVisibilities.z + shadowVisibilities.w; } float fadeFactor = 1.0 - pow(saturate(dot(positionWS, positionWS) / sD.ShadowLightParam.z), 8); - return worldShadow * lerp(1.0, shadow * rcpSampleCount16, fadeFactor); + return worldShadow * lerp(1.0, shadow * rcpSampleCount16 * 0.25, fadeFactor); } float Get2DFilteredShadowCascade(float noise, float2x2 rotationMatrix, float sampleOffsetScale, float2 baseUV, float cascadeIndex, float compareValue, uint eyeIndex) From 64160e9ffb48d6208cd751539f2a009f247d65ba Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 2 Feb 2026 18:32:12 +0000 Subject: [PATCH 57/63] =?UTF-8?q?style:=20=F0=9F=8E=A8=20apply=20pre-commi?= =?UTF-8?q?t.ci=20formatting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Automated formatting by clang-format, prettier, and other hooks. See https://pre-commit.ci for details. --- package/Shaders/Common/ShadowSampling.hlsli | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package/Shaders/Common/ShadowSampling.hlsli b/package/Shaders/Common/ShadowSampling.hlsli index 1244518f19..cfe47998e0 100644 --- a/package/Shaders/Common/ShadowSampling.hlsli +++ b/package/Shaders/Common/ShadowSampling.hlsli @@ -114,7 +114,7 @@ namespace ShadowSampling // Determine which cascade pair to blend between bool inFarRegion = sD.EndSplitDistances.x < shadowMapDepth; - float2 blendRange = inFarRegion + float2 blendRange = inFarRegion ? float2(sD.StartSplitDistances.z, sD.EndSplitDistances.y) : float2(sD.StartSplitDistances.y, sD.EndSplitDistances.x); float cascadeProbability = saturate((shadowMapDepth - blendRange.x) / (blendRange.y - blendRange.x)); @@ -140,7 +140,7 @@ namespace ShadowSampling for (uint k = 0; k < sampleCount16; k++) { uint noisyIndex = (k + uint(sampleCount16 * noise)) % sampleCount16; float t = (sampleCount16 - 1 - noisyIndex) * rcpSampleCount16; - + // Probabilistically select cascade (0 or 1 within the pair) uint cascadeIndex = uint(frac(t + noise) < cascadeProbability); From 84c6a858a4b0dc51ee9e9274c6239635d32f6876 Mon Sep 17 00:00:00 2001 From: doodlum <15017472+doodlum@users.noreply.github.com> Date: Mon, 2 Feb 2026 18:45:11 +0000 Subject: [PATCH 58/63] chore: further optimisation --- package/Shaders/Common/ShadowSampling.hlsli | 23 +++++++++++---------- package/Shaders/Effect.hlsl | 9 ++++---- package/Shaders/Water.hlsl | 19 +++++++++-------- 3 files changed, 27 insertions(+), 24 deletions(-) diff --git a/package/Shaders/Common/ShadowSampling.hlsli b/package/Shaders/Common/ShadowSampling.hlsli index 1244518f19..a880e3c144 100644 --- a/package/Shaders/Common/ShadowSampling.hlsli +++ b/package/Shaders/Common/ShadowSampling.hlsli @@ -65,7 +65,7 @@ namespace ShadowSampling return positionCSShifted.z / positionCSShifted.w; } - float Get3DFilteredShadow(float3 positionWS, float3 viewDirection, float2 screenPosition, uint eyeIndex) + float Get3DFilteredShadow(float3 positionWS, float3 viewDirection, float2 screenPosition, uint eyeIndex, float depth) { static const uint sampleCount8 = 8; static const float rcpSampleCount8 = 1.0 / float(sampleCount8); @@ -81,16 +81,20 @@ namespace ShadowSampling sincos(Math::TAU * noise, rotation.y, rotation.x); float2x2 rotationMatrix = float2x2(rotation.x, rotation.y, -rotation.y, rotation.x); + float screenDepth = SharedData::GetScreenDepth(depth); + float objectDepth = length(positionWS); + float maxDistance = max(0, screenDepth - objectDepth); + #if defined(EFFECT) // Enough for non-billboards + enough for Sovngarde fog float viewRayLength = min(Permutation::EffectRadius * 0.2, 256); float3 startPosition = positionWS - viewDirection * viewRayLength; - float3 endPosition = positionWS + viewDirection * viewRayLength; + float3 endPosition = positionWS + viewDirection * min(viewRayLength, maxDistance); #else // Enough for Eastmarch water - float viewRayLength = 256.0; + float viewRayLength = 128.0; float3 startPosition = positionWS; - float3 endPosition = positionWS + viewDirection * viewRayLength; + float3 endPosition = positionWS + viewDirection * min(viewRayLength, maxDistance); #endif float worldShadow = 0; @@ -109,7 +113,9 @@ namespace ShadowSampling float shadowMapDepth = GetShadowDepth(positionWS, eyeIndex); ShadowData sD = SharedShadowData[0]; - if (sD.EndSplitDistances.w < shadowMapDepth) // Early out beyond cascade 2 + + // Early out beyond cascade 2 + if (sD.EndSplitDistances.w < shadowMapDepth) return worldShadow; // Determine which cascade pair to blend between @@ -131,7 +137,7 @@ namespace ShadowSampling uint cascadeIdx = baseCascade + i; float4x3 proj = sD.ShadowMapProj[eyeIndex][cascadeIdx]; compareValues[i] = mul(transpose(proj), float4(positionWS, 1)).z - sD.AlphaTestRef[1 + min(1, cascadeIdx)]; - sampleRadii[i] = sD.ShadowSampleParam.z * exp2(-float(cascadeIdx)) * 2.0; + sampleRadii[i] = sD.ShadowSampleParam.z * exp2(-float(cascadeIdx)) * 4.0; positionsLS[i] = mul(transpose(proj), float4(startPosition, 1)); viewOffsetsLS[i] = mul(transpose(proj), float4(endPosition, 1)); } @@ -219,11 +225,6 @@ namespace ShadowSampling return 1.0; } - float GetEffectShadow(float3 worldPosition, float3 viewDirection, float2 screenPosition, uint eyeIndex) - { - return Get3DFilteredShadow(worldPosition, viewDirection, screenPosition, eyeIndex); - } - float GetLightingShadow(float noise, float3 worldPosition, uint eyeIndex) { float2 rotation; diff --git a/package/Shaders/Effect.hlsl b/package/Shaders/Effect.hlsl index 4be7815120..cc493b5e30 100644 --- a/package/Shaders/Effect.hlsl +++ b/package/Shaders/Effect.hlsl @@ -525,7 +525,7 @@ cbuffer PerGeometry : register(b2) # include "Common/ShadowSampling.hlsli" # if defined(LIGHTING) -float3 GetLightingColor(float3 msPosition, float3 worldPosition, float2 screenPosition, uint eyeIndex, inout float shadowVariance) +float3 GetLightingColor(float3 msPosition, float3 worldPosition, float2 screenPosition, uint eyeIndex, float depth, inout float shadowVariance) { float3 color = DLightColor.xyz * Color::EffectLightingMult(); @@ -552,7 +552,7 @@ float3 GetLightingColor(float3 msPosition, float3 worldPosition, float2 screenPo ShadowSampling::ExtractLighting(color, dirColor, ambientColor); # endif - float shadow = ShadowSampling::GetEffectShadow(worldPosition.xyz, normalize(worldPosition.xyz), screenPosition, eyeIndex); + float shadow = ShadowSampling::Get3DFilteredShadow(worldPosition.xyz, normalize(worldPosition.xyz), screenPosition, eyeIndex, depth); shadowVariance = 1.0 - sqrt(saturate(fwidth(shadow))); @@ -673,8 +673,9 @@ PS_OUTPUT main(PS_INPUT input) # endif float softMul = 1; + float depth = 1; # if defined(SOFT) - float depth = TexDepthSamplerEffect.Load(int3(input.Position.xy, 0)).x; + depth = TexDepthSamplerEffect.Load(int3(input.Position.xy, 0)).x; softMul = saturate(-input.TexCoord0.w + LightingInfluence.y / ((1 - depth) * CameraDataEffect.z + CameraDataEffect.y)); # endif @@ -683,7 +684,7 @@ PS_OUTPUT main(PS_INPUT input) float shadowVariance = 1.0; # if defined(LIGHTING) - propertyColor = GetLightingColor(input.MSPosition.xyz, input.WorldPosition.xyz, input.Position.xy, eyeIndex, shadowVariance); + propertyColor = GetLightingColor(input.MSPosition.xyz, input.WorldPosition.xyz, input.Position.xy, eyeIndex, depth, shadowVariance); # if defined(LIGHT_LIMIT_FIX) uint lightCount = 0; diff --git a/package/Shaders/Water.hlsl b/package/Shaders/Water.hlsl index 80b1558910..1541912c88 100644 --- a/package/Shaders/Water.hlsl +++ b/package/Shaders/Water.hlsl @@ -897,13 +897,13 @@ float3 GetWaterSpecularColor(PS_INPUT input, float3 normal, float3 viewDirection return reflectionColor; } -float GetScreenDepthWater(float2 screenPosition, uint a_useVR = 0) +float GetScreenDepthWater(float2 screenPosition, out float realDepth, uint a_useVR = 0) { - float depth = DepthTex.Load(float3(screenPosition, 0)).x; + realDepth = DepthTex.Load(float3(screenPosition, 0)).x; # if defined(VR) // VR appears to use hard coded values - return depth * 1.01 + -0.01; + return realDepth * 1.01 + -0.01; # else - return (CameraDataWater.w / (-depth * CameraDataWater.z + CameraDataWater.x)); + return (CameraDataWater.w / (-realDepth * CameraDataWater.z + CameraDataWater.x)); # endif } @@ -937,7 +937,7 @@ struct DiffuseOutput float refractionMul; }; -DiffuseOutput GetWaterDiffuseColor(PS_INPUT input, float3 normal, float3 viewDirection, inout float4 distanceMul, float refractionsDepthFactor, float fresnel, uint eyeIndex, float3 viewPosition, float depth) +DiffuseOutput GetWaterDiffuseColor(PS_INPUT input, float3 normal, float3 viewDirection, inout float4 distanceMul, float refractionsDepthFactor, float fresnel, uint eyeIndex, float3 viewPosition, float depth, inout float realDepth) { # if defined(REFRACTIONS) float4 refractionNormal = mul(transpose(TextureProj[eyeIndex]), float4((VarAmounts.w * refractionsDepthFactor * normal.xy) + input.MPosition.xy, input.MPosition.z, 1)); @@ -955,7 +955,7 @@ DiffuseOutput GetWaterDiffuseColor(PS_INPUT input, float3 normal, float3 viewDir float4 refractionWorldPosition = float4(input.WPosition.xyz * depth / viewPosition.z, 0); # if defined(DEPTH) && !defined(VERTEX_ALPHA_DEPTH) - float refractionDepth = GetScreenDepthWater(refractionScreenPosition); + float refractionDepth = GetScreenDepthWater(refractionScreenPosition, realDepth); # if !defined(VR) float refractionDepthMul = length(float3((((VPOSOffset.zw + refractionUvRaw) * 2 - 1)) * refractionDepth / ProjData.xy, refractionDepth)); @@ -1057,6 +1057,7 @@ PS_OUTPUT main(PS_INPUT input) bool isSpecular = false; float depth = 0; + float realDepth = 1.0; # if defined(DEPTH) # if defined(VERTEX_ALPHA_DEPTH) @@ -1066,7 +1067,7 @@ PS_OUTPUT main(PS_INPUT input) # else distanceMul = 0; - depth = GetScreenDepthWater(screenPosition); + depth = GetScreenDepthWater(screenPosition, realDepth); float2 depthOffset = FrameBuffer::DynamicResolutionParams2.xy * input.HPosition.xy * VPOSOffset.xy + VPOSOffset.zw; # if !defined(VR) @@ -1165,9 +1166,9 @@ PS_OUTPUT main(PS_INPUT input) float3 specularColor = GetWaterSpecularColor(input, normal, viewDirection, distanceFactor, 1.0); #endif - DiffuseOutput diffuseOutput = GetWaterDiffuseColor(input, normal, viewDirection, distanceMul, depthControl.y, fresnel, eyeIndex, viewPosition, depth); + DiffuseOutput diffuseOutput = GetWaterDiffuseColor(input, normal, viewDirection, distanceMul, depthControl.y, fresnel, eyeIndex, viewPosition, depth, realDepth); - float dirShadow = ShadowSampling::GetEffectShadow(input.WPosition.xyz, viewDirection, input.HPosition.xy, eyeIndex); + float dirShadow = ShadowSampling::Get3DFilteredShadow(input.WPosition.xyz, viewDirection, input.HPosition.xy, eyeIndex, realDepth); float3 dirColor; float3 ambientColor; From 2c1fdd2b368a8bb383ef23bb13fa827b956cd9ac Mon Sep 17 00:00:00 2001 From: doodlum <15017472+doodlum@users.noreply.github.com> Date: Mon, 2 Feb 2026 18:48:04 +0000 Subject: [PATCH 59/63] chore: less blur --- package/Shaders/Common/ShadowSampling.hlsli | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/Shaders/Common/ShadowSampling.hlsli b/package/Shaders/Common/ShadowSampling.hlsli index b8283dd84d..00a9f3ac66 100644 --- a/package/Shaders/Common/ShadowSampling.hlsli +++ b/package/Shaders/Common/ShadowSampling.hlsli @@ -137,7 +137,7 @@ namespace ShadowSampling uint cascadeIdx = baseCascade + i; float4x3 proj = sD.ShadowMapProj[eyeIndex][cascadeIdx]; compareValues[i] = mul(transpose(proj), float4(positionWS, 1)).z - sD.AlphaTestRef[1 + min(1, cascadeIdx)]; - sampleRadii[i] = sD.ShadowSampleParam.z * exp2(-float(cascadeIdx)) * 4.0; + sampleRadii[i] = sD.ShadowSampleParam.z * exp2(-float(cascadeIdx)) * 2.0; positionsLS[i] = mul(transpose(proj), float4(startPosition, 1)); viewOffsetsLS[i] = mul(transpose(proj), float4(endPosition, 1)); } From a77cac337324756c24511c8b54f23f32ffae0c8c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 2 Feb 2026 18:49:30 +0000 Subject: [PATCH 60/63] =?UTF-8?q?style:=20=F0=9F=8E=A8=20apply=20pre-commi?= =?UTF-8?q?t.ci=20formatting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Automated formatting by clang-format, prettier, and other hooks. See https://pre-commit.ci for details. --- package/Shaders/Common/ShadowSampling.hlsli | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/Shaders/Common/ShadowSampling.hlsli b/package/Shaders/Common/ShadowSampling.hlsli index 00a9f3ac66..649102d957 100644 --- a/package/Shaders/Common/ShadowSampling.hlsli +++ b/package/Shaders/Common/ShadowSampling.hlsli @@ -113,7 +113,7 @@ namespace ShadowSampling float shadowMapDepth = GetShadowDepth(positionWS, eyeIndex); ShadowData sD = SharedShadowData[0]; - + // Early out beyond cascade 2 if (sD.EndSplitDistances.w < shadowMapDepth) return worldShadow; From 57f4d5ba5631a01899ed0969aa34bcb578aad32b Mon Sep 17 00:00:00 2001 From: doodlum <15017472+doodlum@users.noreply.github.com> Date: Mon, 2 Feb 2026 22:14:05 +0000 Subject: [PATCH 61/63] fix: fix compiler error --- package/Shaders/Common/ShadowSampling.hlsli | 4 ++-- package/Shaders/Particle.hlsl | 8 -------- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/package/Shaders/Common/ShadowSampling.hlsli b/package/Shaders/Common/ShadowSampling.hlsli index 649102d957..05af3387df 100644 --- a/package/Shaders/Common/ShadowSampling.hlsli +++ b/package/Shaders/Common/ShadowSampling.hlsli @@ -69,7 +69,7 @@ namespace ShadowSampling { static const uint sampleCount8 = 8; static const float rcpSampleCount8 = 1.0 / float(sampleCount8); - static const uint sampleCount8Minus1 = 7; + static const uint sampleCount8Minus1 = sampleCount8 - 1; static const uint sampleCount16 = 16; static const float rcpSampleCount16 = 1.0 / float(sampleCount16); @@ -145,7 +145,7 @@ namespace ShadowSampling float shadow = 0.0; for (uint k = 0; k < sampleCount16; k++) { uint noisyIndex = (k + uint(sampleCount16 * noise)) % sampleCount16; - float t = (sampleCount16 - 1 - noisyIndex) * rcpSampleCount16; + float t = float(sampleCount16Minus1 - noisyIndex) * rcpSampleCount16; // Probabilistically select cascade (0 or 1 within the pair) uint cascadeIndex = uint(frac(t + noise) < cascadeProbability); diff --git a/package/Shaders/Particle.hlsl b/package/Shaders/Particle.hlsl index 9a725c336a..87b77e3bb3 100644 --- a/package/Shaders/Particle.hlsl +++ b/package/Shaders/Particle.hlsl @@ -247,14 +247,6 @@ cbuffer PerGeometry : register(b2) float3 TextureSize : packoffset(c1); }; -# if defined(TERRAIN_SHADOWS) -# include "TerrainShadows/TerrainShadows.hlsli" -# endif - -# if defined(CLOUD_SHADOWS) -# include "CloudShadows/CloudShadows.hlsli" -# endif - # define LinearSampler SampSourceTexture # include "Common/ShadowSampling.hlsli" From 58d874a0bd6c23cde970df2107a869a3719969c7 Mon Sep 17 00:00:00 2001 From: doodlum <15017472+doodlum@users.noreply.github.com> Date: Mon, 2 Feb 2026 23:53:01 +0000 Subject: [PATCH 62/63] chore: revert some changes --- package/Shaders/Common/ShadowSampling.hlsli | 42 ++++++++--------- package/Shaders/DownsampleShadowCS.hlsl | 52 ++------------------- package/Shaders/Effect.hlsl | 9 +++- package/Shaders/Water.hlsl | 10 +++- src/Deferred.cpp | 20 ++------ src/Deferred.h | 2 - src/Hooks.cpp | 3 -- 7 files changed, 43 insertions(+), 95 deletions(-) diff --git a/package/Shaders/Common/ShadowSampling.hlsli b/package/Shaders/Common/ShadowSampling.hlsli index 05af3387df..821d6bf0a1 100644 --- a/package/Shaders/Common/ShadowSampling.hlsli +++ b/package/Shaders/Common/ShadowSampling.hlsli @@ -87,9 +87,14 @@ namespace ShadowSampling #if defined(EFFECT) // Enough for non-billboards + enough for Sovngarde fog - float viewRayLength = min(Permutation::EffectRadius * 0.2, 256); + float viewRayLength = min(Permutation::EffectRadius * 0.1, 128); float3 startPosition = positionWS - viewDirection * viewRayLength; float3 endPosition = positionWS + viewDirection * min(viewRayLength, maxDistance); +#elif defined(UNDERWATER) + // Enough for Eastmarch water + float viewRayLength = 128.0; + float3 startPosition = positionWS - viewDirection * viewRayLength; + float3 endPosition = positionWS; #else // Enough for Eastmarch water float viewRayLength = 128.0; @@ -102,7 +107,12 @@ namespace ShadowSampling uint noisyIndex = uint((float(i) + sampleCount8 * noise) % sampleCount8); float t = (float(sampleCount8Minus1) - float(noisyIndex)) * rcpSampleCount8; float tSample = t + noiseTransform * rcpSampleCount8; - worldShadow += ShadowSampling::GetWorldShadow(lerp(startPosition, endPosition, tSample), FrameBuffer::CameraPosAdjust[eyeIndex].xyz, eyeIndex); + + float3 samplePositionWS = lerp(startPosition, endPosition, tSample); + samplePositionWS.xy += mul(Random::SpiralSampleOffsets8[i], rotationMatrix) * viewRayLength; + samplePositionWS.z += length(Random::SpiralSampleOffsets8[i]); + + worldShadow += ShadowSampling::GetWorldShadow(samplePositionWS, FrameBuffer::CameraPosAdjust[eyeIndex].xyz, eyeIndex); } if (worldShadow == 0.0) @@ -118,28 +128,18 @@ namespace ShadowSampling if (sD.EndSplitDistances.w < shadowMapDepth) return worldShadow; - // Determine which cascade pair to blend between - bool inFarRegion = sD.EndSplitDistances.x < shadowMapDepth; - float2 blendRange = inFarRegion - ? float2(sD.StartSplitDistances.z, sD.EndSplitDistances.y) - : float2(sD.StartSplitDistances.y, sD.EndSplitDistances.x); - float cascadeProbability = saturate((shadowMapDepth - blendRange.x) / (blendRange.y - blendRange.x)); - // Precompute cascade data - uint baseCascade = uint(inFarRegion); - + float cascade1Probability = saturate((shadowMapDepth - sD.StartSplitDistances.y) / (sD.EndSplitDistances.x - sD.StartSplitDistances.y)); + float compareValues[2]; float sampleRadii[2]; float3 positionsLS[2]; float3 viewOffsetsLS[2]; - - for (uint i = 0; i < 2; i++) { - uint cascadeIdx = baseCascade + i; - float4x3 proj = sD.ShadowMapProj[eyeIndex][cascadeIdx]; - compareValues[i] = mul(transpose(proj), float4(positionWS, 1)).z - sD.AlphaTestRef[1 + min(1, cascadeIdx)]; - sampleRadii[i] = sD.ShadowSampleParam.z * exp2(-float(cascadeIdx)) * 2.0; - positionsLS[i] = mul(transpose(proj), float4(startPosition, 1)); - viewOffsetsLS[i] = mul(transpose(proj), float4(endPosition, 1)); + for (uint cascadeIdx = 0; cascadeIdx < 2; cascadeIdx++) { + compareValues[cascadeIdx] = mul(transpose(sD.ShadowMapProj[eyeIndex][cascadeIdx]), float4(positionWS, 1)).z - sD.AlphaTestRef[1 + cascadeIdx]; + sampleRadii[cascadeIdx] = sD.ShadowSampleParam.z * rcp(1 + cascadeIdx) * 2.0; + positionsLS[cascadeIdx] = mul(transpose(sD.ShadowMapProj[eyeIndex][cascadeIdx]), float4(startPosition, 1)); + viewOffsetsLS[cascadeIdx] = mul(transpose(sD.ShadowMapProj[eyeIndex][cascadeIdx]), float4(endPosition, 1)); } float shadow = 0.0; @@ -148,7 +148,7 @@ namespace ShadowSampling float t = float(sampleCount16Minus1 - noisyIndex) * rcpSampleCount16; // Probabilistically select cascade (0 or 1 within the pair) - uint cascadeIndex = uint(frac(t + noise) < cascadeProbability); + uint cascadeIndex = uint(frac(t + noise) < cascade1Probability); // Offset along view ray with optimised sample pattern float tSample = t + noiseTransform * rcpSampleCount16; @@ -158,7 +158,7 @@ namespace ShadowSampling sampledPositionLS.xy += mul(Random::PoissonSampleOffsets16[k], rotationMatrix) * sampleRadii[cascadeIndex]; // Average 4 shadow samples for improved quality - float4 depths = SharedShadowMap.SampleLevel(LinearSampler, saturate(sampledPositionLS.xy), 2u - baseCascade - cascadeIndex); + float4 depths = SharedShadowMap.SampleLevel(LinearSampler, saturate(sampledPositionLS.xy), 1u - cascadeIndex); float4 shadowVisibilities = float4(depths > compareValues[cascadeIndex]); shadow += shadowVisibilities.x + shadowVisibilities.y + shadowVisibilities.z + shadowVisibilities.w; } diff --git a/package/Shaders/DownsampleShadowCS.hlsl b/package/Shaders/DownsampleShadowCS.hlsl index a61ae62289..f8c7145072 100644 --- a/package/Shaders/DownsampleShadowCS.hlsl +++ b/package/Shaders/DownsampleShadowCS.hlsl @@ -17,8 +17,7 @@ SamplerState PointSampler : register(s0); InputTexture.GetDimensions(inputW, inputH, inputSlices); float2 uv = (pixCoord + 0.5) / float2(inputW, inputH); - - float4 depths4 = InputTexture.GatherRed(PointSampler, float3(uv, 1)); + OutputTexture[dispatchThreadID.xy] = InputTexture.GatherRed(PointSampler, float3(uv, 1)); } } #elif defined(DOWNSAMPLE_SHADOW_MIP1) @@ -32,37 +31,6 @@ groupshared float g_scratchDepths[8][8]; InputTexture.GetDimensions(inputW, inputH, inputSlices); float2 uv = (pixCoord + 0.5) / float2(inputW, inputH); - float4 depths4 = InputTexture.GatherRed(PointSampler, float3(uv, 1)); - - g_scratchDepths[groupThreadID.x][groupThreadID.y] = dot(depths4, 0.25); - - GroupMemoryBarrierWithGroupSync(); - - // MIP 1 -> 2: 2x2 reduction in shared memory (4x4 total) - [branch] - if (all((groupThreadID.xy % 2) == 0)) - { - float inTL = g_scratchDepths[groupThreadID.x + 0][groupThreadID.y + 0]; - float inTR = g_scratchDepths[groupThreadID.x + 1][groupThreadID.y + 0]; - float inBL = g_scratchDepths[groupThreadID.x + 0][groupThreadID.y + 1]; - float inBR = g_scratchDepths[groupThreadID.x + 1][groupThreadID.y + 1]; - OutputTexture[dispatchThreadID.xy / 2] = float4(inTL, inTR, inBL, inBR); - } -} -#elif defined(DOWNSAMPLE_SHADOW_MIP2) -groupshared float g_scratchDepths[8][8]; - -[numthreads(8, 8, 1)] void main(uint3 dispatchThreadID : SV_DispatchThreadID, uint3 groupThreadID : SV_GroupThreadID) { - uint w, h; - OutputTexture.GetDimensions(w, h); - - // MIP 0 -> 1: each thread gathers a 2x2 block and averages - uint2 pixCoord = dispatchThreadID.xy * 2; - - uint inputW, inputH, inputSlices; - InputTexture.GetDimensions(inputW, inputH, inputSlices); - float2 uv = (pixCoord + 0.5) / float2(inputW, inputH); - float4 depths4 = InputTexture.GatherRed(PointSampler, float3(uv, 0)); g_scratchDepths[groupThreadID.x][groupThreadID.y] = dot(depths4, 0.25); @@ -77,21 +45,7 @@ groupshared float g_scratchDepths[8][8]; float inTR = g_scratchDepths[groupThreadID.x + 1][groupThreadID.y + 0]; float inBL = g_scratchDepths[groupThreadID.x + 0][groupThreadID.y + 1]; float inBR = g_scratchDepths[groupThreadID.x + 1][groupThreadID.y + 1]; - g_scratchDepths[groupThreadID.x][groupThreadID.y] = dot(float4(inTL, inTR, inBL, inBR), 0.25); - } - - GroupMemoryBarrierWithGroupSync(); - - // MIP 2 -> 3: 2x2 reduction in shared memory (8x8 total) - [branch] if (all((groupThreadID.xy % 4) == 0)) - { - float inTL = g_scratchDepths[groupThreadID.x + 0][groupThreadID.y + 0]; - float inTR = g_scratchDepths[groupThreadID.x + 2][groupThreadID.y + 0]; - float inBL = g_scratchDepths[groupThreadID.x + 0][groupThreadID.y + 2]; - float inBR = g_scratchDepths[groupThreadID.x + 2][groupThreadID.y + 2]; - OutputTexture[dispatchThreadID.xy / 4] = float4(inTL, inTR, inBL, inBR); + OutputTexture[dispatchThreadID.xy / 2] = float4(inTL, inTR, inBL, inBR); } } -#else -# error "Error: Missing downsample scale" -#endif +#endif \ No newline at end of file diff --git a/package/Shaders/Effect.hlsl b/package/Shaders/Effect.hlsl index cc493b5e30..f6d8c68f1b 100644 --- a/package/Shaders/Effect.hlsl +++ b/package/Shaders/Effect.hlsl @@ -602,7 +602,7 @@ float3 GetLightingShadow(float3 color, float3 worldPosition, float2 screenPositi // Enough for sky statics float maxDistance = max(0, SharedData::GetScreenDepth(depth)); - float viewRayLength = 4096; + float viewRayLength = 4096.0; float3 viewDirection = normalize(worldPosition); float3 startPosition = worldPosition - viewDirection * viewRayLength; float3 endPosition = worldPosition + viewDirection * min(maxDistance, viewRayLength); @@ -612,7 +612,12 @@ float3 GetLightingShadow(float3 color, float3 worldPosition, float2 screenPositi uint noisyIndex = uint((float(i) + sampleCount * noise) % sampleCount); float t = (float(sampleCount) - float(noisyIndex + 1)) * rcpSampleCount; float tSample = t + noiseTransform * rcpSampleCount; - shadow += ShadowSampling::GetWorldShadow(lerp(startPosition, endPosition, tSample), FrameBuffer::CameraPosAdjust[eyeIndex].xyz, eyeIndex); + + float3 samplePositionWS = lerp(startPosition, endPosition, tSample); + samplePositionWS.xy += mul(Random::SpiralSampleOffsets8[i], rotationMatrix) * 4096.0; + samplePositionWS.z += length(Random::SpiralSampleOffsets8[i]); + + shadow += ShadowSampling::GetWorldShadow(samplePositionWS, FrameBuffer::CameraPosAdjust[eyeIndex].xyz, eyeIndex); } shadow *= rcpSampleCount; diff --git a/package/Shaders/Water.hlsl b/package/Shaders/Water.hlsl index 1541912c88..f4be3f0e4b 100644 --- a/package/Shaders/Water.hlsl +++ b/package/Shaders/Water.hlsl @@ -935,10 +935,13 @@ struct DiffuseOutput float3 refractionDiffuseColor; float depth; float refractionMul; + float3 refractionDepthAdjustedViewDirection; }; DiffuseOutput GetWaterDiffuseColor(PS_INPUT input, float3 normal, float3 viewDirection, inout float4 distanceMul, float refractionsDepthFactor, float fresnel, uint eyeIndex, float3 viewPosition, float depth, inout float realDepth) { + float3 refractionDepthAdjustedViewDirection = viewDirection; + # if defined(REFRACTIONS) float4 refractionNormal = mul(transpose(TextureProj[eyeIndex]), float4((VarAmounts.w * refractionsDepthFactor * normal.xy) + input.MPosition.xy, input.MPosition.z, 1)); @@ -953,6 +956,7 @@ DiffuseOutput GetWaterDiffuseColor(PS_INPUT input, float3 normal, float3 viewDir float2 refractionScreenPosition = FrameBuffer::DynamicResolutionParams1.xy * (refractionUvRaw / VPOSOffset.xy); float4 refractionWorldPosition = float4(input.WPosition.xyz * depth / viewPosition.z, 0); + # if defined(DEPTH) && !defined(VERTEX_ALPHA_DEPTH) float refractionDepth = GetScreenDepthWater(refractionScreenPosition, realDepth); @@ -963,7 +967,7 @@ DiffuseOutput GetWaterDiffuseColor(PS_INPUT input, float3 normal, float3 viewDir float refractionDepthMul = CalculateDepthMultFromUV(refractionUvRawNoStereo, refractionDepth, eyeIndex); # endif //VR - float3 refractionDepthAdjustedViewDirection = -viewDirection * refractionDepthMul; + refractionDepthAdjustedViewDirection = -viewDirection * refractionDepthMul; float refractionViewSurfaceAngle = dot(refractionDepthAdjustedViewDirection, ReflectPlane[eyeIndex].xyz); float refractionPlaneMul = (1 - ReflectPlane[eyeIndex].w / refractionViewSurfaceAngle); @@ -997,6 +1001,7 @@ DiffuseOutput GetWaterDiffuseColor(PS_INPUT input, float3 normal, float3 viewDir output.refractionDiffuseColor = refractionDiffuseColor; output.depth = depth; output.refractionMul = refractionMul; + output.refractionDepthAdjustedViewDirection = refractionDepthAdjustedViewDirection; return output; # else DiffuseOutput output; @@ -1004,6 +1009,7 @@ DiffuseOutput GetWaterDiffuseColor(PS_INPUT input, float3 normal, float3 viewDir output.refractionDiffuseColor = output.refractionColor; output.depth = 1; output.refractionMul = 1; + output.refractionDepthAdjustedViewDirection = refractionDepthAdjustedViewDirection; return output; # endif } @@ -1168,7 +1174,7 @@ PS_OUTPUT main(PS_INPUT input) DiffuseOutput diffuseOutput = GetWaterDiffuseColor(input, normal, viewDirection, distanceMul, depthControl.y, fresnel, eyeIndex, viewPosition, depth, realDepth); - float dirShadow = ShadowSampling::Get3DFilteredShadow(input.WPosition.xyz, viewDirection, input.HPosition.xy, eyeIndex, realDepth); + float dirShadow = ShadowSampling::Get3DFilteredShadow(input.WPosition.xyz, diffuseOutput.refractionDepthAdjustedViewDirection, input.HPosition.xy, eyeIndex, realDepth); float3 dirColor; float3 ambientColor; diff --git a/src/Deferred.cpp b/src/Deferred.cpp index 1db967d0f7..3e7b79772c 100644 --- a/src/Deferred.cpp +++ b/src/Deferred.cpp @@ -163,9 +163,6 @@ void Deferred::SetupResources() defines.clear(); defines.push_back({ "DOWNSAMPLE_SHADOW_MIP1", nullptr }); downsampleShadowMip1CS = static_cast(Util::CompileShader(L"Data\\Shaders\\DownsampleShadowCS.hlsl", defines, "cs_5_0")); - defines.clear(); - defines.push_back({ "DOWNSAMPLE_SHADOW_MIP2", nullptr }); - downsampleShadowMip2CS = static_cast(Util::CompileShader(L"Data\\Shaders\\DownsampleShadowCS.hlsl", defines, "cs_5_0")); } { @@ -267,7 +264,7 @@ void Deferred::CopyShadowData() D3D11_TEXTURE2D_DESC copyDesc{}; copyDesc.Width = newWidth; copyDesc.Height = newHeight; - copyDesc.MipLevels = 3; + copyDesc.MipLevels = 2; copyDesc.ArraySize = 1; copyDesc.Format = DXGI_FORMAT_R16G16B16A16_UNORM; copyDesc.SampleDesc.Count = 1; @@ -283,7 +280,7 @@ void Deferred::CopyShadowData() srvDesc.Format = DXGI_FORMAT_R16G16B16A16_UNORM; srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; srvDesc.Texture2D.MostDetailedMip = 0; - srvDesc.Texture2D.MipLevels = 3; + srvDesc.Texture2D.MipLevels = 2; DX::ThrowIfFailed(device->CreateShaderResourceView(shadowCopyTexture, &srvDesc, &shadowCopySRV)); D3D11_UNORDERED_ACCESS_VIEW_DESC uavDesc{}; @@ -294,9 +291,6 @@ void Deferred::CopyShadowData() uavDesc.Texture2D.MipSlice = 1; DX::ThrowIfFailed(device->CreateUnorderedAccessView(shadowCopyTexture, &uavDesc, &shadowCopyMip1UAV)); - - uavDesc.Texture2D.MipSlice = 2; - DX::ThrowIfFailed(device->CreateUnorderedAccessView(shadowCopyTexture, &uavDesc, &shadowCopyMip2UAV)); } // Dispatch downsample compute shader @@ -305,24 +299,18 @@ void Deferred::CopyShadowData() context->CSSetSamplers(0, 1, &pointSampler); - // Mip 0 with third cascade + // Mip 0 with second cascade ID3D11UnorderedAccessView* csUavs[1]{ shadowCopyMip0UAV }; context->CSSetUnorderedAccessViews(0, 1, csUavs, nullptr); context->CSSetShader(downsampleShadowMip0CS, nullptr, 0); context->Dispatch((shadowCopyWidth + 7) >> 3, (shadowCopyHeight + 7) >> 3, 1); - // Mip 1 with second cascade + // Mip 1 with first cascade csUavs[0] = shadowCopyMip1UAV; context->CSSetUnorderedAccessViews(0, 1, csUavs, nullptr); context->CSSetShader(downsampleShadowMip1CS, nullptr, 0); context->Dispatch((shadowCopyWidth + 7) >> 3, (shadowCopyHeight + 7) >> 3, 1); - // Mip 2 with third cascade - csUavs[0] = shadowCopyMip2UAV; - context->CSSetUnorderedAccessViews(0, 1, csUavs, nullptr); - context->CSSetShader(downsampleShadowMip2CS, nullptr, 0); - context->Dispatch((shadowCopyWidth + 7) >> 3, (shadowCopyHeight + 7) >> 3, 1); - // Cleanup CS state csSrvs[0] = nullptr; context->CSSetShaderResources(0, 1, csSrvs); diff --git a/src/Deferred.h b/src/Deferred.h index 34905cfe09..93664fe042 100644 --- a/src/Deferred.h +++ b/src/Deferred.h @@ -69,7 +69,6 @@ class Deferred ID3D11ComputeShader* copyShadowCS = nullptr; ID3D11ComputeShader* downsampleShadowMip0CS = nullptr; ID3D11ComputeShader* downsampleShadowMip1CS = nullptr; - ID3D11ComputeShader* downsampleShadowMip2CS = nullptr; Buffer* perShadow = nullptr; ID3D11ShaderResourceView* shadowView = nullptr; @@ -77,7 +76,6 @@ class Deferred ID3D11ShaderResourceView* shadowCopySRV = nullptr; ID3D11UnorderedAccessView* shadowCopyMip0UAV = nullptr; ID3D11UnorderedAccessView* shadowCopyMip1UAV = nullptr; - ID3D11UnorderedAccessView* shadowCopyMip2UAV = nullptr; uint32_t shadowCopyWidth = 0; uint32_t shadowCopyHeight = 0; diff --git a/src/Hooks.cpp b/src/Hooks.cpp index ac566d77ae..c72bb30585 100644 --- a/src/Hooks.cpp +++ b/src/Hooks.cpp @@ -356,10 +356,7 @@ struct BSShaderRenderTargets_Create static void thunk() { auto iniPrefSettingCollection = RE::INIPrefSettingCollection::GetSingleton(); - iniPrefSettingCollection->GetSetting("iNumSplits:Display")->data.i = 3; - iniPrefSettingCollection->GetSetting("iShadowMapResolution:Display")->data.i = 2048; iniPrefSettingCollection->GetSetting("iNumFocusShadow:Display")->data.i = 0; - func(); globals::ReInit(); globals::state->Setup(); From 672734b80e169d3ca15f29dad11e86e94ca4beff Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 2 Feb 2026 23:54:25 +0000 Subject: [PATCH 63/63] =?UTF-8?q?style:=20=F0=9F=8E=A8=20apply=20pre-commi?= =?UTF-8?q?t.ci=20formatting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Automated formatting by clang-format, prettier, and other hooks. See https://pre-commit.ci for details. --- package/Shaders/Common/ShadowSampling.hlsli | 4 ++-- package/Shaders/Effect.hlsl | 2 +- package/Shaders/Water.hlsl | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package/Shaders/Common/ShadowSampling.hlsli b/package/Shaders/Common/ShadowSampling.hlsli index 821d6bf0a1..b6ac3cbe86 100644 --- a/package/Shaders/Common/ShadowSampling.hlsli +++ b/package/Shaders/Common/ShadowSampling.hlsli @@ -107,7 +107,7 @@ namespace ShadowSampling uint noisyIndex = uint((float(i) + sampleCount8 * noise) % sampleCount8); float t = (float(sampleCount8Minus1) - float(noisyIndex)) * rcpSampleCount8; float tSample = t + noiseTransform * rcpSampleCount8; - + float3 samplePositionWS = lerp(startPosition, endPosition, tSample); samplePositionWS.xy += mul(Random::SpiralSampleOffsets8[i], rotationMatrix) * viewRayLength; samplePositionWS.z += length(Random::SpiralSampleOffsets8[i]); @@ -130,7 +130,7 @@ namespace ShadowSampling // Precompute cascade data float cascade1Probability = saturate((shadowMapDepth - sD.StartSplitDistances.y) / (sD.EndSplitDistances.x - sD.StartSplitDistances.y)); - + float compareValues[2]; float sampleRadii[2]; float3 positionsLS[2]; diff --git a/package/Shaders/Effect.hlsl b/package/Shaders/Effect.hlsl index f6d8c68f1b..486f3a9a84 100644 --- a/package/Shaders/Effect.hlsl +++ b/package/Shaders/Effect.hlsl @@ -612,7 +612,7 @@ float3 GetLightingShadow(float3 color, float3 worldPosition, float2 screenPositi uint noisyIndex = uint((float(i) + sampleCount * noise) % sampleCount); float t = (float(sampleCount) - float(noisyIndex + 1)) * rcpSampleCount; float tSample = t + noiseTransform * rcpSampleCount; - + float3 samplePositionWS = lerp(startPosition, endPosition, tSample); samplePositionWS.xy += mul(Random::SpiralSampleOffsets8[i], rotationMatrix) * 4096.0; samplePositionWS.z += length(Random::SpiralSampleOffsets8[i]); diff --git a/package/Shaders/Water.hlsl b/package/Shaders/Water.hlsl index f4be3f0e4b..8773dab99d 100644 --- a/package/Shaders/Water.hlsl +++ b/package/Shaders/Water.hlsl @@ -956,7 +956,7 @@ DiffuseOutput GetWaterDiffuseColor(PS_INPUT input, float3 normal, float3 viewDir float2 refractionScreenPosition = FrameBuffer::DynamicResolutionParams1.xy * (refractionUvRaw / VPOSOffset.xy); float4 refractionWorldPosition = float4(input.WPosition.xyz * depth / viewPosition.z, 0); - + # if defined(DEPTH) && !defined(VERTEX_ALPHA_DEPTH) float refractionDepth = GetScreenDepthWater(refractionScreenPosition, realDepth);