uniform sampler2DShadow shadow;

#if defined RSM
uniform sampler2D shadowcolor0;
#endif
uniform ivec2 eyeBrightnessSmooth;
uniform mat4 shadowModelView;
uniform mat4 shadowProjection;
#define ffstep(x, y) clamp((y - x) * 1e35, 0.0, 1.0)
#define diagonal3(m) vec3((m)[0].x, (m)[1].y, m[2].z)

vec2 R2(const float n) {
    return fract(float(n) * vec2(0.245122333753, 0.430159709002)) * 2 - 1;
}
float phaseg(float x, float g) {
    float gg = g * g;
    return (gg * -0.25 + 0.25) * pow(-2.0 * (g * x) + (gg + 1.0), -1.5) / pi;
}

float shadowFunc(float diffuseSun, inout vec3 SSS, float sssAmount, vec3 p3, vec3 WsunVec, float noise, vec2 lightmap, vec3 fragpos, inout vec3 sAlbedo, vec2 shadowdata) {
    float blend = smoothstep(64, 0, length(p3));
    float shading = 1.0;
    vec3 ProjShadowPos = mat3(shadowModelView) * p3 + shadowModelView[3].xyz;
    ProjShadowPos = diagonal3(shadowProjection) * ProjShadowPos + shadowProjection[3].xyz;

    float distortFac = calcDistort(ProjShadowPos.xy);

    ProjShadowPos.xy *= distortFac;

    vec2 bounds = vec2(1.0, 1.0) - 1.5 / shadowMapResolution;

    if (all(lessThan(abs(ProjShadowPos.xy), bounds)) && abs(ProjShadowPos.z) < 6) {
        const float threshMul = max(2048.0 / shadowMapResolution * shadowDistance / 128.0, 0.95);
        shading = 0.0;
        float pdepth = 3;
        float avgDepth = 0;
        #ifdef PENUMBRA_SHADOWS
        avgDepth = shadowdata.r;
        pdepth = shadowdata.g;
        #endif

        ProjShadowPos = ProjShadowPos * vec3(0.5, 0.5, 0.5 / 6.0) + 0.5;
        float d01 = d0 * 1 + sssAmount * 0.1;

        float distortThresh = (sqrt(1.0 - diffuseSun * diffuseSun) / diffuseSun) / distortFac;
        float diffthresh = sssAmount > 0 ? 1e-5 : distortThresh / 6000.0 * threshMul;

        float d01k = d01 * k * distortFac;
        float rdMul0 = d01k / shadowMapResolution;

        const int iterations = SHADOW_SAMPLES;
        float rdMul = pdepth * rdMul0;
        float precomp2 = rdMul / iterations * shadowMapResolution;
        for (int i = 0; i < iterations; i++) {

            vec2 offsetS = R2((i + frameCounter * noise) * 64.0) * rdMul;

            #ifndef PENUMBRA_SHADOWS
            offsetS *= 1 + sssAmount * 10;
            #endif

            float weight = 1.0 + (i + noise) * precomp2;
            shading += texture(shadow, vec3(ProjShadowPos + vec3(offsetS, -diffthresh * weight)));
        }
        shading /= iterations;

        if (sssAmount > 0) {
            avgDepth *= 11;
            #ifndef PENUMBRA_SHADOWS
            avgDepth -= shading * 1.5;
            #endif

            vec3 extinction = vec3(0.5);
            float scattering = clamp((pi * 0.5 * phaseg(dot(p3, WsunVec) / length(p3), 0.85) + 0.7), 0, 2) * sssAmount;
            SSS = 0.5 * sssAmount * scattering * sunColor.rgb;
            SSS *= pi * SSS_MULTI * exp(-avgDepth * extinction) + exp(-avgDepth / pi * extinction);
            shading *= 1.0 - sssAmount;
            #ifndef PENUMBRA_SHADOWS
            SSS *= 0.5 * (shading + 0.1);
            #endif
            SSS = mix(vec3(0.0), SSS, min(eyeBrightnessSmooth.y * recip + lightmap.y, 1.0));

        }

        #if defined RSM

        const float offsetSize = 100 * shadowMapResolution * 0.00048828125;
        if (blend > 0) {

            vec2 offset = circlemap(noise, 4096.0) * offsetSize * rdMul0;
            vec2 GIprojectedShadowPosition = ProjShadowPos.xy + offset;
            GIprojectedShadowPosition *= shadowMapResolution;
            sAlbedo = texelFetch2D(shadowcolor0, ivec2(GIprojectedShadowPosition), 0).rgb;
        }
        sAlbedo = mix(vec3(0.5), sAlbedo, blend);

        #endif
    }

    if (diffuseSun * shading > 0.00001) {
        #ifdef CONTACT_SHADOWS
        shading = min(rayTraceShadow((when_gt(sunElevation, 1e-5) * 2 - 1.) * sunVec, fragpos, noise), shading);
        #endif
        shading = mix(0.0, shading, min(eyeBrightnessSmooth.y * recip + lightmap.y, 1.0));
    }

    return diffuseSun * shading * lightmap.y * (1 - rainStrength * 0.5);
}