vec3 GetShadowPos(vec3 playerPos) {
    vec3 shadowPos = PlayerToShadow(playerPos);
    float distb = sqrt(shadowPos.x * shadowPos.x + shadowPos.y * shadowPos.y);
    float distortFactor = distb * shadowMapBias + (1.0 - shadowMapBias);
    shadowPos.xy /= distortFactor;
    shadowPos.z *= 0.2;
    return shadowPos * 0.5 + 0.5;
}

#ifndef COMPOSITE1
    vec3 SampleShadow(vec3 shadowPos, float colorMult, float colorPow) {
        float shadow0 = shadow2D(shadowtex0, vec3(shadowPos.st, shadowPos.z)).x;

        vec3 shadowcol = vec3(0.0);
        if (shadow0 < 1.0) {
            float shadow1 = shadow2D(shadowtex1, vec3(shadowPos.st, shadowPos.z)).x;
            if (shadow1 > 0.9999) {
                shadowcol = texture2D(shadowcolor0, shadowPos.st).rgb * shadow1;

                shadowcol *= colorMult;
                shadowcol = pow(shadowcol, vec3(colorPow));
            }
        }

        return shadowcol * (1.0 - shadow0) + shadow0;
    }

    float InterleavedGradientNoiseForShadows() {
        float n = 52.9829189 * fract(0.06711056 * gl_FragCoord.x + 0.00583715 * gl_FragCoord.y);
        #if !defined GBUFFERS_ENTITIES && !defined GBUFFERS_HAND && !defined GBUFFERS_TEXTURED && defined TAA
            return fract(n + goldenRatio * mod(float(frameCounter), 3600.0));
        #else
            return fract(n);
        #endif
    }

    vec2 offsetDist(float x, int s) {
        float n = fract(x * 2.427) * 3.1415;
        return vec2(cos(n), sin(n)) * 1.4 * x / s;
    }

    vec3 SampleTAAFilteredShadow(vec3 shadowPos, float offset, int shadowSamples, bool leaves, float colorMult, float colorPow) {
        vec3 shadow = vec3(0.0);
        float gradientNoise = InterleavedGradientNoiseForShadows();

        #if !defined GBUFFERS_ENTITIES && !defined GBUFFERS_HAND && !defined GBUFFERS_TEXTURED
            offset *= 1.3875;
        #else
            shadowSamples *= 2;
            offset *= 0.69375;
        #endif

        float shadowPosZM = shadowPos.z;
        for (int i = 0; i < shadowSamples; i++) {
            vec2 offset2 = offsetDist(gradientNoise + i, shadowSamples) * offset;
            if (leaves) shadowPosZM = shadowPos.z - 0.12 * offset * (gradientNoise + i) / shadowSamples;
            shadow += SampleShadow(vec3(shadowPos.st + offset2, shadowPosZM), colorMult, colorPow);
            shadow += SampleShadow(vec3(shadowPos.st - offset2, shadowPosZM), colorMult, colorPow);
        }

        shadow /= shadowSamples * 2.0;

        return shadow;
    }

    vec2 shadowOffsets[4] = vec2[4](
        vec2( 1.0, 0.0),
        vec2( 0.0, 1.0),
        vec2(-1.0, 0.0),
        vec2( 0.0,-1.0));

    vec3 SampleFilteredShadow(vec3 shadowPos, float offset, float colorMult, float colorPow) {
        vec3 shadow = vec3(0.0);

        for (int i = 0; i < 4; i++) {
            shadow += SampleShadow(vec3(offset * shadowOffsets[i] + shadowPos.st, shadowPos.z), colorMult, colorPow);
        }
        shadow += SampleShadow(shadowPos, colorMult, colorPow);

        return shadow * 0.2;
    }

    vec3 SampleBasicFilteredShadow(vec3 shadowPos, float offset) {
        float shadow = 0.0;

        for (int i = 0; i < 4; i++) {
            shadow += shadow2D(shadowtex0, vec3(offset * shadowOffsets[i] + shadowPos.st, shadowPos.z)).x;
        }

        return vec3(shadow * 0.25);
    }

    vec3 GetShadow(vec3 shadowPos, float lightmapY, float offset, int shadowSamples, bool leaves, vec3 playerPos) {
        #if SHADOW_QUALITY > 0
            #if ENTITY_SHADOW <= 1 && defined GBUFFERS_BLOCK
                offset *= 4.0;
            #else
                #ifdef OVERWORLD
                    offset *= 1.0 + rainFactor2
                        #ifdef SUN_MOON_DURING_RAIN
                            * 2.0
                        #else
                            * 4.0
                        #endif
                    ;
                #else
                    offset *= 3.0;
                #endif
            #endif
        #endif

        float lightmapY2 = pow2(lightmapY);
        float lightmapY4 = pow2(lightmapY2);

        float colorMult = 2.5 + 5.5 * pow1_5(lightmapY) + 2.0 * lightmapY2; // 423HDSS: Shadow color strength is stored 10 times lower to allow for water shadows going above 1.0
        float colorPow = mix(1.5 + 0.5 * float(isEyeInWater == 0), 0.5, lightmapY4);

        #if defined DISTANT_HORIZONS || defined VOXY
            float horizontalDistance = length(playerPos.xz);
            float verticalDistance = abs(playerPos.y);
            float fadeEndistance = max(horizontalDistance, verticalDistance);
            #ifdef DISTANT_HORIZONS
                float farM = far * 0.8;
            #else
                float farM = far * 0.95;
            #endif
            float fade = smoothstep(far * 0.4, far * 0.9, fadeEndistance);

            colorMult *= mix(1.0, 0.1 + 0.99 * pow2(pow2(lightmapY4)), fade);
            colorPow *= mix(1.0, 1.0 - 0.99 * lightmapY4, fade);
        #endif

        #if SHADOW_QUALITY >= 2 && defined TAA // Medium and Above
            vec3 shadow = SampleTAAFilteredShadow(shadowPos, offset, shadowSamples, leaves, colorMult, colorPow);
        #elif SHADOW_QUALITY >= 1 // Low
            vec3 shadow = SampleFilteredShadow(shadowPos, offset, colorMult, colorPow);
        #elif SHADOW_QUALITY == 0 // Very Low
            vec3 shadow = SampleBasicFilteredShadow(shadowPos, offset);
        #endif

        return shadow;
    }
#endif
