#ifndef INCLUDE_SHADOW_MAPPING
    #define INCLUDE_SHADOW_MAPPING

    vec3 playerToShadowViewPos (vec4 playerPos)
    {
        mat3 shadowViewMatrix = tbnNormal(shadowDir);

        return (playerPos.xyz * shadowViewMatrix) + playerPos.w * (fract(cameraMod256 * shadowViewMatrix) - 0.5);
    }

    vec3 shadowViewToPlayerPos (vec4 shadowViewPos)
    {
        mat3 shadowViewMatrix = tbnNormal(shadowDir);

        return shadowViewMatrix * (shadowViewPos.xyz - shadowViewPos.w * (fract(cameraMod256 * shadowViewMatrix) - 0.5));
    }

    float textureShadowRaw (vec2 shadowViewPos, uint cascade)
    {
        return SHADOW_MAX_RT_DISTANCE - 2.0 * SHADOW_MAX_RT_DISTANCE * imageLoad(imgShadowTex, ivec2(shadowViewPos * exp2(3.0 - float(cascade)) + 256.0 * vec2(cascade & 1, cascade >> 1) + 128.0)).r;
    }

    // === Shadow with bias + PCF v1.6.8 ===
    float textureShadow (vec2 shadowViewPos, uint cascade)
    {
        // Shadow bias: reduce self-shadowing (acne) — scales with cascade level
        float bias = SHADOW_SOFTNESS * exp2(float(cascade)) * 0.5;

        // PCF 2x2: smoother shadow edges
        float texelSize = exp2(float(cascade) - 3.0);
        float s0 = textureShadowRaw(shadowViewPos + vec2(-0.5, -0.5) * texelSize, cascade);
        float s1 = textureShadowRaw(shadowViewPos + vec2( 0.5, -0.5) * texelSize, cascade);
        float s2 = textureShadowRaw(shadowViewPos + vec2(-0.5,  0.5) * texelSize, cascade);
        float s3 = textureShadowRaw(shadowViewPos + vec2( 0.5,  0.5) * texelSize, cascade);

        return 0.25 * (s0 + s1 + s2 + s3) + bias;
    }

    float textureShadow (vec2 shadowViewPos) 
    {
        uint cascade = uint(log2(max(abs(shadowViewPos.x), abs(shadowViewPos.y))) - 3.0);

        if (cascade > 3) return 0.0;
        else return textureShadow(shadowViewPos, cascade);
    }

#endif