ADD: Clipmap shadow better quality

This commit is contained in:
2025-10-26 06:57:13 +09:00
parent 3127658b01
commit 85d93fbd67
4 changed files with 100 additions and 27 deletions

View File

@@ -10,6 +10,16 @@ layout(set=1, binding=1) uniform sampler2D normalTex;
layout(set=1, binding=2) uniform sampler2D albedoTex;
layout(set=2, binding=0) uniform sampler2D shadowTex[4];
// Tunables for shadow quality and blending
// Border smoothing width in light-space NDC (0..1). Larger = wider cross-fade.
const float SHADOW_BORDER_SMOOTH_NDC = 0.08;
// Base PCF radius in texels for cascade 0; higher cascades scale this up slightly.
const float SHADOW_PCF_BASE_RADIUS = 1.35;
// Additional per-cascade radius scale for coarser cascades (0..1 factor added across levels)
const float SHADOW_PCF_CASCADE_GAIN = 2.0; // extra radius at far end
// Receiver normal-based offset to reduce acne (in world units)
const float SHADOW_NORMAL_OFFSET = 0.0025;
const float PI = 3.14159265359;
float hash12(vec2 p)
@@ -29,24 +39,54 @@ vec2(0.0281, -0.2468), vec2(-0.2104, 0.0573),
vec2(0.1197, 0.0779), vec2(-0.0905, -0.1203)
);
// Clipmap selection: choose the smallest level whose light-space XY NDC contains the point.
uint selectCascadeIndex(vec3 worldPos)
// Compute primary cascade and an optional neighbor for cross-fade near borders
struct CascadeMix { uint i0; uint i1; float w1; };
CascadeMix computeCascadeMix(vec3 worldPos)
{
uint primary = 3u;
vec3 ndcP = vec3(0);
for (uint i = 0u; i < 4u; ++i)
{
vec4 lclip = sceneData.lightViewProjCascades[i] * vec4(worldPos, 1.0);
vec3 ndc = lclip.xyz / max(lclip.w, 1e-6);
if (abs(ndc.x) <= 1.0 && abs(ndc.y) <= 1.0 && ndc.z >= 0.0 && ndc.z <= 1.0)
{
return i;
primary = i;
ndcP = ndc;
break;
}
}
return 3u;
CascadeMix cm; cm.i0 = primary; cm.i1 = primary; cm.w1 = 0.0;
if (primary < 3u)
{
float edge = max(abs(ndcP.x), abs(ndcP.y)); // 0..1, 1 at border
// start blending when we are within S of the border
float t = clamp((edge - (1.0 - SHADOW_BORDER_SMOOTH_NDC)) / max(SHADOW_BORDER_SMOOTH_NDC, 1e-4), 0.0, 1.0);
float w = smoothstep(0.0, 1.0, t);
if (w > 0.0)
{
// Only blend if neighbor actually covers the point
uint neighbor = primary + 1u;
vec4 lclipN = sceneData.lightViewProjCascades[neighbor] * vec4(worldPos, 1.0);
vec3 ndcN = lclipN.xyz / max(lclipN.w, 1e-6);
bool insideN = (abs(ndcN.x) <= 1.0 && abs(ndcN.y) <= 1.0 && ndcN.z >= 0.0 && ndcN.z <= 1.0);
if (insideN)
{
cm.i1 = neighbor;
cm.w1 = w;
}
}
}
return cm;
}
float calcShadowVisibility(vec3 worldPos, vec3 N, vec3 L)
float sampleCascadeShadow(uint ci, vec3 worldPos, vec3 N, vec3 L)
{
uint ci = selectCascadeIndex(worldPos);
mat4 lightMat = sceneData.lightViewProjCascades[ci];
vec4 lclip = lightMat * vec4(worldPos, 1.0);
@@ -69,9 +109,8 @@ float calcShadowVisibility(vec3 worldPos, vec3 N, vec3 L)
ivec2 dim = textureSize(shadowTex[ci], 0);
vec2 texelSize = 1.0 / vec2(dim);
float baseRadius = 1.25;
float radius = mix(baseRadius, baseRadius * 3.0, float(ci) / 3.0);
float baseRadius = SHADOW_PCF_BASE_RADIUS;
float radius = mix(baseRadius, baseRadius + SHADOW_PCF_CASCADE_GAIN, float(ci) / 3.0);
float ang = hash12(suv * 4096.0) * 6.2831853;
vec2 r = vec2(cos(ang), sin(ang));
@@ -100,6 +139,19 @@ float calcShadowVisibility(vec3 worldPos, vec3 N, vec3 L)
return visibility;
}
float calcShadowVisibility(vec3 worldPos, vec3 N, vec3 L)
{
vec3 wp = worldPos + N * SHADOW_NORMAL_OFFSET * (0.5 + 0.5 * (1.0 - max(dot(N, L), 0.0)));
CascadeMix cm = computeCascadeMix(wp);
float v0 = sampleCascadeShadow(cm.i0, wp, N, L);
if (cm.w1 <= 0.0)
return v0;
float v1 = sampleCascadeShadow(cm.i1, wp, N, L);
return mix(v0, v1, clamp(cm.w1, 0.0, 1.0));
}
vec3 fresnelSchlick(float cosTheta, vec3 F0)
{
return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0);