vec3 do_water_vl(vec3 StartPos, vec3 EndPos, vec3 PlayerPosN, vec3 Color, float Depth, const int STEP_COUNT) {
    const vec3 WATER_ABSORBTION = WaterAbsorbtion;
    const vec3 WATER_SCATTERING = WaterColor * 1e-1;
    const float SIGMA_WATER = 1;
    const vec3 WATER_EXTINCTION = SIGMA_WATER * (WATER_ABSORBTION + WATER_SCATTERING);

    vec3 WorldPos = cameraPosition + StartPos;

    if (Depth == 1) {
        EndPos = PlayerPosN * far;
    }

    vec3 Step = (EndPos - StartPos) / STEP_COUNT;
    float StepSize = length(Step);

    float Dither = dither(gl_FragCoord.xy);
    vec3 PlayerPosC = StartPos + Dither * Step;

    vec3 AmbientColor = dataBuf.AmbientColor;

    AmbientColor = mix(MinLight, AmbientColor, isIndoorsSmooth);
    float VdotL = dot(view_player(sLightPosN), PlayerPosN);

    float Density = StepSize * 0.9;
    vec3 TotalScattering = vec3(0);
    vec3 TotalTransmittance = vec3(1);

    vec3 Transmittance = exp(-Density * WATER_EXTINCTION);
    float SunPhase = ISOTROPIC_PHASE;

    for (int i = 1; i <= STEP_COUNT; i++) {
        vec3 WorldPosC = PlayerPosC + cameraPosition;

        float DistToSky = 5; // seven
        vec3 SkyAttenuation = exp(-DistToSky * WATER_EXTINCTION);
        TotalScattering += TotalTransmittance * AmbientColor * SkyAttenuation * ISOTROPIC_PHASE;

        vec3 ShadowViewPosC = (shadowModelView * vec4(PlayerPosC, 1)).xyz;
        vec3 ShadowNDCPosC = project_and_divide(shadowProjection, ShadowViewPosC);
        vec3 ShadowPosC = distort(ShadowNDCPosC);
        ShadowPosC = ShadowPosC * 0.5 + 0.5;
        float ExpectedDepth = ShadowPosC.z;
        float ActualDepth = texture(shadowtex1, ShadowPosC.xy).x;

        if (ActualDepth > ExpectedDepth) { // Is in sun
            float DistToSun = DistToSky / view_player(sLightPosN).y;
            vec3 SunAttenuation = exp(-DistToSun * WATER_EXTINCTION);
            TotalScattering += TotalTransmittance * LightColorDirect * SunAttenuation * SunPhase;
        }
        PlayerPosC += Step;
        TotalTransmittance *= Transmittance;
    }

    TotalScattering *= (WATER_SCATTERING / WATER_EXTINCTION) * (1 - Transmittance);

    return Color * TotalTransmittance + TotalScattering;
}

vec3 do_vl(vec3 StartPos, vec3 EndPos, vec3 PlayerPosN, vec3 Color, float Depth, const int STEP_COUNT) {
    if (isEyeInWater == 1) return do_water_vl(StartPos, EndPos, PlayerPosN, Color, Depth, STEP_COUNT * 2);

    const float MAX_HEIGHT = 150;
    const float SIGMA_AIR = 0.5;
    const float DENSITY = 1e-3;

    vec3 WorldPos = cameraPosition + StartPos;
    // Fog gets strong at morning
    float DaytimeFactor = worldTime > 12000 ? smoothstep(20000, 24000, worldTime) : 1 - smoothstep(0, 2000, worldTime);
    DaytimeFactor = max(DaytimeFactor, 0.2);

    if (WorldPos.y <= MAX_HEIGHT) {
        if (EndPos.y + cameraPosition.y > MAX_HEIGHT)
            EndPos = intersectRayWithPlane(WorldPos, PlayerPosN, MAX_HEIGHT);
        if (EndPos == vec3(0)) return Color;
    }
    else {
        if (EndPos.y + cameraPosition.y > MAX_HEIGHT) return Color;
        StartPos = intersectRayWithPlane(WorldPos, PlayerPosN, MAX_HEIGHT);
        if (StartPos == vec3(0)) return Color;
    }

    vec3 Step = (EndPos - StartPos) / STEP_COUNT;
    float StepSize = length(Step);
    vec3 TotalTransmittance = vec3(1);
    vec3 FinalColor = vec3(0);
    float Dither = dither(gl_FragCoord.xy);
    vec3 PlayerPosC = StartPos + Dither * Step;

    vec3 AmbientColor = dataBuf.AmbientColor * vec3(1, 2, 4); // This looks good
    AmbientColor = mix(MinLight, AmbientColor, isIndoorsSmooth);

    float VdotL = dot(view_player(sLightPosN), PlayerPosN);
    float MiePhase = cs_phase(VdotL, 0.6) * 0.75 + cs_phase(VdotL, -0.3) * 0.25;
    float DensityConstant = DaytimeFactor * StepSize * DENSITY;
    for (int i = 1; i <= STEP_COUNT && length(PlayerPosC.xz) < far; i++) {
        vec3 WorldPosC = PlayerPosC + cameraPosition;

        float Detail = texture(worleyNoiseTexture, WorldPosC / vec3(256, 32, 256) * 3).r;
        float Density = Detail;
        Density *= DensityConstant;
        float HeightFalloff = 1 - smoothstep(MAX_HEIGHT - 50, MAX_HEIGHT, WorldPosC.y);
        Density *= HeightFalloff;
        if (Density <= 0) {
            PlayerPosC += Step;
            continue;
        }

        vec3 Transmittance = vec3(exp(-Density * SIGMA_AIR));
        FinalColor += AmbientColor * Density * TotalTransmittance;

        vec3 ShadowViewPosC = (shadowModelView * vec4(PlayerPosC, 1)).xyz;
        vec3 ShadowNDCPosC = project_and_divide(shadowProjection, ShadowViewPosC);
        vec3 ShadowPosC = distort(ShadowNDCPosC);
        ShadowPosC = ShadowPosC * 0.5 + 0.5;
        float ExpectedDepth = ShadowPosC.z;
        float ActualDepth = texture(shadowtex1, ShadowPosC.xy).x;

        if (ActualDepth > ExpectedDepth) { // Is in sun
            //float CloudCoverage = cloud_shadows(WorldPosC);
            FinalColor += LightColorDirect * MiePhase * Density * TotalTransmittance;
        }
        PlayerPosC += Step;
        TotalTransmittance *= Transmittance;
    }

    return Color * TotalTransmittance + FinalColor;
}
