

const vec2 shadowOffsets[6] = vec2[6](vec2(0.5303, 0.5303), vec2(-0.6250, -0.0000), vec2(0.3536, -0.3536),
                                      vec2(-0.0000, 0.3750), vec2(-0.1768, -0.1768), vec2(0.1250, 0.0000));
float R2_dither3()
{
    vec2 alpha = vec2(0.75487765, 0.56984026);
    return fract(alpha.x * gl_FragCoord.x + alpha.y * gl_FragCoord.y + 1.0 / 1.6180339887 * frameCounter);
}
float shadowFunc(
    float diffuseSun,
    vec3 p3,
    float alpha)
{
    float noise = R2_dither3();
    // Initialize shading
    float shading = 1.0;

    // Offset to reduce shadow acne

    // Transform p3 to shadow map space
    vec3 ProjShadowPos = mat3(shadowModelView) * p3 + shadowModelView[3].xyz;
    ProjShadowPos = diagonal3(shadowProjection) * ProjShadowPos + shadowProjection[3].xyz;

    // Calculate distortion factor and apply it
    float distortFac = calcDistort(ProjShadowPos.xy);
    ProjShadowPos.xy *= vec2(distortFac);

    // Define shadow bounds
    vec2 bounds = vec2(1.0) - 1.5 / shadowMapResolution;

    // Determine if the fragment is within shadow bounds and conditions
    bool inBounds = all(lessThan(abs(ProjShadowPos.xy), bounds)) &&
                    (abs(ProjShadowPos.z) < 6.0) &&
                    (diffuseSun > 0.001);

    if (inBounds)
    {
        // Reset shading and adjust ProjShadowPos for shadow sampling
        shading = 0.0;
        ProjShadowPos = ProjShadowPos * vec3(0.5, 0.5, 1.0 / 12.0) + vec3(0.5);

        // Compute threshold multiplier
        const float threshMul = max(16.0 * shaddist / shadowMapResolution, 0.95);

        // Calculate distortion threshold
        float temp = 1.0 - diffuseSun * diffuseSun;
        float distortThresh = clamp(sqrt(temp) / diffuseSun, 0.0, 1.0) / distortFac;

        // Compute differential threshold
        float diffthresh = distortThresh * (threshMul / 6000.0);

#if SHADOW_FILTERING >= 1
        // Precompute rotation matrix with noise
        float prec = noise * 6.28318530718; // 2 * PI
        mat2 noiseM = mat2(cos(prec), -sin(prec),
                           sin(prec), cos(prec)) *
                      0.001;

        // Calculate weight factors
        float weight = 1.0 + noise * shadowMapResolution * 0.00016;
        float dweight = -diffthresh * weight;
        float amount = mix(50.0, 1.0, alpha);
        // Manually unroll the loop for better performance
        shading += texture(shadow, ProjShadowPos + vec3(noiseM * shadowOffsets[0] * amount, dweight));
        shading += texture(shadow, ProjShadowPos + vec3(noiseM * shadowOffsets[1] * amount, dweight));
        shading += texture(shadow, ProjShadowPos + vec3(noiseM * shadowOffsets[2] * amount, dweight));
        shading += texture(shadow, ProjShadowPos + vec3(noiseM * shadowOffsets[3] * amount, dweight));
        shading += texture(shadow, ProjShadowPos + vec3(noiseM * shadowOffsets[4] * amount, dweight));
        shading += texture(shadow, ProjShadowPos + vec3(noiseM * shadowOffsets[5] * amount, dweight));

        // Average the shading from multiple samples
        shading *= 0.16666666667; // Equivalent to dividing by 6
#else
        // Adjust depth for shadow sampling without filtering
        ProjShadowPos.z -= diffthresh;
        shading = texture(shadow, ProjShadowPos);
#endif

        // Blend shading with night shadows
        shading = mix(1.0, shading, nightblendShadows);
    }
    diffuseSun = min(diffuseSun, shading);

    // Apply rain strength effect
    diffuseSun *= (1.0 - rainStrength * 0.9);

    return diffuseSun;
}
