/* Refrax — program/deferred1.fsh.glsl */

#include "/lib/settings.glsl"
#ifdef CONTACT_SHADOW_HIZ_ACTIVE
  #define HIZ_READ
  #include "/lib/hiz.glsl"
#endif
#include "/lib/common.glsl"
#include "/lib/blockid.glsl"
#include "/lib/atmosphere.glsl"
#include "/lib/clouds.glsl"
#include "/lib/shadows.glsl"
#include "/lib/dh.glsl"
#include "/lib/voxel.glsl"
#include "/lib/labpbr.glsl"
#include "/lib/ssr.glsl"
#include "/lib/wetness.glsl"
#include "/lib/thunder.glsl"

uniform sampler2D noisetex;
uniform sampler2D depthtex0;
uniform sampler2D colortex0;
uniform sampler2D colortex1;
uniform sampler2D colortex2;
uniform sampler2D colortex3;
uniform sampler2D colortex13;
uniform sampler2D colortex5;
uniform sampler2D colortex6;
uniform sampler2D colortex10;
#ifdef SSR_ACCUM_ACTIVE
uniform sampler2D colortex15;
#endif
#ifdef VOXY
uniform sampler2D colortex8;
#endif
uniform sampler2D shadowtex0, shadowtex1, shadowcolor0;
#ifdef COLORED_LIGHTING
uniform sampler3D lpvSampler1;
#endif
uniform mat4 gbufferModelView, gbufferModelViewInverse;
uniform mat4 gbufferProjection, gbufferProjectionInverse;
uniform mat4 gbufferPreviousModelView, gbufferPreviousProjection;
uniform mat4 shadowModelView, shadowProjection;
uniform vec3 cameraPosition, previousCameraPosition;
uniform vec3 sunPosition;
uniform vec3 shadowLightPosition;
uniform vec3 fogColor;
uniform float frameTimeCounter, rainStrength, viewWidth, viewHeight;
uniform float wetness;
uniform float nightVision;
uniform int frameCounter, isEyeInWater;
uniform int heldBlockLightValue, heldBlockLightValue2;
uniform int heldItemId, heldItemId2;
#ifdef IS_IRIS
uniform vec3 relativeEyePosition;
#endif
uniform float refraxWetBiome;
uniform float refraxSnowBiome;
uniform float refraxBiomeTemp, refraxBiomeHumid, refraxBiomeSwamp, refraxBiomeAlpine;
#ifdef THUNDER_ACTIVE
uniform vec4 lightningBoltPosition;
#endif

in vec2 uv;

#ifdef SSR_ACCUM_ACTIVE
/* RENDERTARGETS: 0,15 */
layout(location = 0) out vec4 outColor;
layout(location = 1) out vec4 outSSR;
#else
/* RENDERTARGETS: 0 */
layout(location = 0) out vec4 outColor;
#endif

#if MC_VERSION < 260100
uniform sampler2D colortex7;
vec4 lineOverlay(vec4 color) {
    vec4 line = texture(colortex7, uv);
    return vec4(color.rgb * (1.0 - line.a) + line.rgb, color.a);
}
#else
#define lineOverlay(c) (c)
  #ifdef ARMOR_GLINT
  #define REFRAX_GLINT_BUFFER
uniform sampler2D colortex7;
  #endif
#endif

#if REFLECTION_MODE > 0 || RAIN_PUDDLE_REFLECTIONS == 2
vec3 ssrReflection(vec3 viewPos, vec3 reflDirW, vec3 fallback, float dither, vec2 ssrJitter, vec2 viewTexel, float quality) {
    vec3 reflDirV = mat3(gbufferModelView) * reflDirW;
    vec3 hit;
    float hitS = raymarchSSR(depthtex0, ivec2(viewWidth, viewHeight), viewPos, reflDirV, gbufferProjection, gbufferProjectionInverse, dither, ssrJitter, quality, hit);
    if (hitS <= 0.0) return fallback;
    vec3 hitView = screenToView(hit, gbufferProjectionInverse);
    vec3 hitScene = (gbufferModelViewInverse * vec4(hitView, 1.0)).xyz;
    vec3 prevUV = reprojectScene(hitScene, gbufferPreviousModelView, gbufferPreviousProjection, cameraPosition, previousCameraPosition);
    if (clamp(prevUV.xy, 0.0, 1.0) != prevUV.xy) return fallback;
    vec4 hist = texture(colortex5, historyUV(prevUV.xy, viewTexel));
    return mix(fallback, hist.rgb, hitS * saturate(hist.a));
}
#endif

#ifdef SSR_ACCUM_ACTIVE
bool ssrHistory(vec3 scenePos, vec2 viewTexel, out vec3 histColor, out float edge) {
    histColor = vec3(0.0);
    edge = 0.0;
    vec3 prevUV = reprojectScene(scenePos, gbufferPreviousModelView, gbufferPreviousProjection, cameraPosition, previousCameraPosition);
    if (clamp(prevUV.xy, 0.0, 1.0) != prevUV.xy) return false;
    vec4 hist = texture(colortex15, historyUV(prevUV.xy, viewTexel));
    if (hist.a <= 0.0 || hist.a > 1e4 || any(isnan(hist))) return false;
    float prevKey = length(scenePos + cameraPosition - previousCameraPosition);
    if (abs(hist.a - prevKey) > prevKey * 0.06 + 0.05) return false;
    vec2 border = min(prevUV.xy, 1.0 - prevUV.xy);
    histColor = clamp(hist.rgb, vec3(0.0), vec3(64.0));
    edge = saturate(min(border.x, border.y) * 24.0);
    return true;
}

vec3 ssrAccumulate(vec3 current, vec3 scenePos, vec2 viewTexel) {
    vec3 histColor;
    float edge;
    if (!ssrHistory(scenePos, viewTexel, histColor, edge)) return current;
    return mix(current, histColor, SSR_ACCUM_BLEND * edge);
}
#endif

vec3 blockLightAt(vec3 pos, vec3 N, float lmBlock) {
    vec3 fallback = FALLBACK_BLOCKLIGHT * pow3(lmBlock) * 1.85;
#ifdef WORLD_NETHER
    fallback *= NETHER_FALLBACK_SCALE;
#endif
#ifdef COLORED_LIGHTING
    float fade;
    vec3 lpv = sampleLPV(lpvSampler1, pos, cameraPosition, N, fade);
  #ifdef WORLD_NETHER
    lpv *= NETHER_LPV_SCALE;
  #endif
    float vanillaContribution = exp2(-4.0 * luminance(lpv));
    vec3 colored = lpv + fallback * vanillaContribution * LPV_VANILLA_MIX;
    vec3 light = mix(fallback, max(colored, fallback * LPV_VANILLA_MIX * 0.7), fade);
#else
    vec3 light = fallback;
#endif
#ifdef HAND_LIGHT
  #ifdef IS_IRIS
    light += heldLightAt(pos + relativeEyePosition, heldItemId, heldBlockLightValue, heldItemId2, heldBlockLightValue2);
  #else
    light += heldLightAt(pos, heldItemId, heldBlockLightValue, heldItemId2, heldBlockLightValue2);
  #endif
#endif
    return light;
}

void main() {
    vec2 viewTexel = 1.0 / vec2(viewWidth, viewHeight);
#ifdef SSR_ACCUM_ACTIVE
    outSSR = vec4(0.0, 0.0, 0.0, -1.0);
#endif
    vec4 prev = texture(colortex0, uv);
    if (prev.a > 0.15 && prev.a < 0.85) { outColor = lineOverlay(prev); return; }

    float depth = texture(depthtex0, uv).r;
    vec3 viewPos = screenToView(vec3(uv, depth), gbufferProjectionInverse);
    bool lodPixel = false;
#ifdef LOD_ACTIVE
    if (depth >= 1.0) {
        float lodDepth = texture(lodDepthTex1, uv).r;
        if (lodDepth < 1.0) {
            lodPixel = true;
            viewPos = screenToView(vec3(uv, lodDepth), lodProjectionInverse);
        }
    }
#endif
    vec3 scenePos = (gbufferModelViewInverse * vec4(viewPos, 1.0)).xyz;
    vec3 dirW = normalize(scenePos);
    vec3 sunDir = normalize(mat3(gbufferModelViewInverse) * sunPosition);
    vec3 lightDir = normalize(mat3(gbufferModelViewInverse) * shadowLightPosition);

    float auroraW = 0.0;
#ifdef AURORA_ACTIVE
    auroraW = smoothstep(0.04, -0.10, sunDir.y) * (1.0 - saturate(rainStrength * 1.6));
  #ifdef AURORA_COLD_ONLY
    auroraW *= smoothstep(0.45, -0.10, refraxBiomeTemp + BIOME_TEMP_NEUTRAL);
  #endif
#endif

    float rainbowWet = 0.0;
#ifdef RAINBOW_ACTIVE
    rainbowWet = saturate(wetness) * smoothstep(0.32, 0.02, rainStrength);
#endif

    if (depth >= 1.0 && !lodPixel) {
        vec3 sky = dimensionSky(dirW, sunDir, fogColor, frameTimeCounter, rainStrength, cameraPosition, rainbowWet, auroraW);
#ifdef BIOME_ATMOS_ACTIVE
        {
            BiomeAtmos biome = biomeAtmos(refraxBiomeTemp, refraxBiomeHumid, refraxBiomeSwamp, refraxBiomeAlpine);
            float band = (1.0 - smoothstep(0.0, 0.30, dirW.y)) * (1.0 - rainStrength) * 0.65;
            sky *= mix(vec3(1.0), biome.tint, band);
        }
#endif
        vec4 clouds = texture(colortex13, cloudRegionUV(uv, viewTexel));
        sky = sky * clouds.a + clouds.rgb;
#ifdef THUNDER_ACTIVE
        sky += (clouds.rgb * 1.6 + sky * 0.25) * thunderSkyGlow(lightningBoltPosition);
#endif
#ifdef VOXY
        vec4 lodLayer = texture(colortex8, uv);
        sky = sky * (1.0 - lodLayer.a) + lodLayer.rgb;
#endif
        outColor = lineOverlay(vec4(sky, 1.0));
        return;
    }

    vec4 c1 = texture(colortex1, uv);
    vec3 albedo = srgbToLinear(c1.rgb);
    float pomShadow = c1.a;
    vec4 c2 = texture(colortex2, uv);
    vec3 N = unpackSurfaceNormal(c2);
    float emission = unpackSurfaceEmission(c2);
    float dynamicSurface = unpackSurfaceDynamic(c2);
    vec4 c3 = texture(colortex3, uv);
    vec2 lm = c3.xy;
    float roughness = c3.z;
    float f0raw = c3.w;
#if AO_RES > 1
    float ao;
    {
        vec2 aoRes = vec2(viewWidth, viewHeight) * AO_REGION_SCALE;
        vec2 nearestUV = (floor(uv * aoRes) + 0.5) / aoRes;
        float zAO = screenToView(vec3(nearestUV, texture(depthtex0, nearestUV).r), gbufferProjectionInverse).z;
        float w = exp2(-abs(viewPos.z - zAO) * 2.0);
        ao = mix(1.0, texture(colortex6, aoRegionUV(uv, viewTexel)).r, w);
    }
#else
    float ao = texture(colortex6, aoRegionUV(uv, viewTexel)).r;
#endif
    vec4 extra = texture(colortex10, uv);
    float sss = extra.r;
    float porosity = extra.g;
    float anisotropy = extra.b;
    vec2 coating = unpackCoating(extra.a);
    float clearcoat = coating.x;
    float thinFilm = coating.y;
    float dither = ignAnim(gl_FragCoord.xy, frameCounter);
    vec2 ssrJitter = taaJitterUV(vec2(viewWidth, viewHeight), frameCounter);

    vec3 geomNV = cross(dFdx(viewPos), dFdy(viewPos));
    geomNV = normalize(dot(geomNV, viewPos) > 0.0 ? -geomNV : geomNV);
    vec3 geomNW = normalize(mat3(gbufferModelViewInverse) * geomNV);

    vec3 worldPos = scenePos + cameraPosition;
    vec2 fpXZ = fwidth(scenePos.xz);
    float footprint = max(fpXZ.x, fpXZ.y);

#ifdef SNOW_ACTIVE
    float snowCover = 0.0;
    {
        float snowAmt = saturate(wetness) * refraxSnowBiome;
        if (snowAmt > 0.001 && emission < 0.02) {
            snowCover = snowAccumulation(worldPos, geomNW, lm.y, snowAmt, footprint, dynamicSurface);
            if (snowCover > 0.001) {
                albedo = mix(albedo, SNOW_ALBEDO, snowCover);
                roughness = mix(roughness, 0.58, snowCover);
                if (snowCover > 0.5) f0raw = 0.045;
                N = normalize(mix(N, normalize(mix(geomNW, vec3(0.0, 1.0, 0.0), 0.8)), snowCover * 0.85));
                sss = max(sss, snowCover * 0.30);
                anisotropy *= 1.0 - snowCover;
                clearcoat *= 1.0 - snowCover;
                thinFilm *= 1.0 - snowCover;
            }
        }
    }
#endif

#if defined RAIN_PUDDLES && !defined WORLD_NETHER && !defined WORLD_END
    vec3 puddleN = N;
    float puddleCover = 0.0;
    {
        if (wetness * refraxWetBiome > 0.001 && !isMetal(f0raw) && !isMatteFoliageMaterial(roughness, f0raw)) {
            WetResult wr = computeWetness(worldPos, geomNW, lm.y, wetness, refraxWetBiome, footprint);
            float notEmit = 1.0 - saturate(emission);
            WetModulation pr = porosityResponse(porosity);
            albedo *= mix(1.0, WETNESS_DARKEN, saturate(max(wr.wet * 0.85, wr.puddle) * notEmit * pr.darken));
            roughness = mix(roughness, roughness * 0.55, wr.wet * 0.6 * notEmit * pr.smoothen);
            puddleCover = wr.puddle * notEmit * pr.puddle;
            if (puddleCover > 0.001) {
                vec3 flatPuddleN = normalize(mix(geomNW, vec3(0.0, 1.0, 0.0), 0.9));
                vec3 rippleN = puddleNormal(worldPos.xz, geomNW, frameTimeCounter, rainStrength);
                float rippleBlend = puddleCover * smoothstep(0.10, 0.85, rainStrength) * 0.42
                                  * saturate(1.0 - footprint * 8.0);
                puddleN = normalize(mix(flatPuddleN, rippleN, rippleBlend));
            }
        }
    }
#endif

#ifdef DEBUG_LPV
  #ifdef COLORED_LIGHTING
    { float dfade; outColor = vec4(sampleLPV(lpvSampler1, scenePos, cameraPosition, geomNW, dfade), 1.0); return; }
  #endif
#endif

    float NoL = saturate(dot(N, lightDir));
    vec3 lightCol;
    vec3 shadow;

#if defined WORLD_NETHER
    lightCol = vec3(0.0);
    shadow = vec3(0.0);
    float facing = netherFacing(N);
    vec3 skyLight = netherAmbient(N, fogColor) * facing;
  #ifdef COLORED_LIGHTING
    skyLight += vec3(texture(shadowtex1, vec2(0.5)).r * 1e-8);
  #endif
#elif defined WORLD_END
    lightCol = endLightColor();
    shadow = getShadow(scenePos, N, NoL, dither, shadowModelView, shadowProjection, shadowtex0, shadowtex1, shadowcolor0);
  #ifdef CONTACT_SHADOWS_ACTIVE
    if (NoL > 0.0 && !lodPixel && luminance(shadow) > 0.002)
        shadow *= contactShadow(depthtex0, ivec2(viewWidth, viewHeight), viewPos, mat3(gbufferModelView) * N,
                                normalize(shadowLightPosition), NoL, dither,
                                gbufferProjection, gbufferProjectionInverse);
  #endif
    vec3 skyLight = endAmbient(N);
#else
    lightCol = (sunColor(sunDir.y) + moonColor(-sunDir.y)) * (1.0 - rainStrength * 0.9);
    shadow = getShadow(scenePos, N, NoL, dither, shadowModelView, shadowProjection, shadowtex0, shadowtex1, shadowcolor0);
  #ifdef CONTACT_SHADOWS_ACTIVE
    if (NoL > 0.0 && !lodPixel && luminance(shadow) > 0.002)
        shadow *= contactShadow(depthtex0, ivec2(viewWidth, viewHeight), viewPos, mat3(gbufferModelView) * N,
                                normalize(shadowLightPosition), NoL, dither,
                                gbufferProjection, gbufferProjectionInverse);
  #endif
  #ifdef CLOUD_SHADOWS
    if (NoL > 0.0 && shadow.g > 0.001)
        shadow *= cloudShadow(scenePos + cameraPosition, cameraPosition, lightDir, frameTimeCounter, rainStrength);
  #endif
    vec3 skyLight = skyAmbientDirectional(N, sunDir, rainStrength) * pow(lm.y, 2.2);
    skyLight += lightCol * 0.05 * saturate(0.6 - 0.4 * N.y) * pow(lm.y, 2.2);
  #ifdef AURORA_ACTIVE
    if (auroraW > 0.002)
        skyLight += auroraAmbientWash(auroraW, albedo) * (0.55 + 0.45 * N.y) * pow(lm.y, 2.2);
  #endif
#endif
    vec3 blockLight = blockLightAt(scenePos, geomNW, lm.x);
#ifdef WORLD_NETHER
    blockLight *= facing;
#endif
    vec3 minAmb = vec3(0.010, 0.011, 0.014) * MIN_AMBIENT * (1.0 + nightVision * 6.0);

    vec3 directShadow = shadow * pomShadow;
    vec3 diffuse = lightCol * NoL * directShadow + (skyLight + minAmb) * ao + blockLight * mix(ao, 1.0, 0.5);
#ifdef THUNDER_ACTIVE
    diffuse += thunderLight(scenePos, N, lm.y, lightningBoltPosition) * mix(ao, 1.0, 0.5);
#endif

#if defined PBR_MATERIALS || REFLECTION_MODE > 0
    bool matteFoliage = isMatteFoliageMaterial(roughness, f0raw);
    bool metal = !matteFoliage && isMetal(f0raw);
    roughness = materialSpecularRoughness(roughness, N);
    if (metal) {
      #if REFLECTION_MODE > 0
        diffuse = vec3(0.0);
      #else
        diffuse *= 0.12;
      #endif
    }
#endif

    vec3 color = albedo * diffuse;
    color += albedo * sqrt(albedo) * (emission * EMISSION_STRENGTH * EMISSION_SCALE);

#ifdef SNOW_ACTIVE
    if (snowCover > 0.001 && dynamicSurface < 0.5)
        color += snowSparkle(worldPos, N, -dirW, lightDir, length(scenePos)) * lightCol * directShadow * snowCover;
#endif

#ifdef SUBSURFACE_SCATTERING
    if (sss > 0.0) {
        vec3 sssShadow = mix(directShadow, vec3(1.0), 0.65);
        color += subsurfaceTransmission(albedo, N, dirW, lightDir, sss) * lightCol * sssShadow;
    }
#endif

#if defined PBR_MATERIALS || REFLECTION_MODE > 0
    vec3 V = -dirW;
    float smoothness = saturate(1.0 - sqrt(saturate(roughness)));
    vec3 lobeN = materialLobeNormal(geomNW, N, roughness, clearcoat);
    vec3 f0 = matteFoliage ? vec3(0.0) : materialF0(f0raw, albedo);
    float directSpecWeight = metal ? 1.0 : mix(0.08, 0.60, smoothstep(0.10, 0.85, smoothness));
    directSpecWeight = max(directSpecWeight, clearcoat * 0.35);
    if (!matteFoliage && directSpecWeight > 0.0) {
        vec3 directSpecular = materialDiscLightSpecular(lobeN, V, lightDir, SUN_GLINT_RADIUS, roughness, f0, anisotropy, clearcoat, thinFilm) * lightCol * directShadow * directSpecWeight * PBR_GLINT_STRENGTH;
        color += compressMaterialHighlight(directSpecular);
    }

  #if REFLECTION_MODE > 0
    float NoV = saturate(dot(V, lobeN));
    float reflectionRoughness = materialReflectionRoughness(roughness, clearcoat);
    vec3 F = matteFoliage ? vec3(0.0) : materialReflectionFresnel(NoV, f0raw, albedo, clearcoat, thinFilm);
    float reflWeight = metal ? mix(0.45, 1.0, 1.0 - saturate(reflectionRoughness))
                             : pow2(1.0 - saturate(reflectionRoughness));
    bool envReflect = !matteFoliage && reflWeight > 0.002;
    bool ssrReflect = envReflect && (metal || reflectionRoughness < SSR_ROUGH_LIMIT);
    if (envReflect) {
        vec3 reflDirW = reflect(dirW, lobeN);
    #if defined WORLD_NETHER || defined WORLD_END
        float skyVis = 1.0;
    #else
        float skyVis = pow2(lm.y);
    #endif
        vec3 refl = dimensionSkyReflection(reflDirW, sunDir, fogColor, frameTimeCounter, rainStrength) * skyVis;
        if (ssrReflect) {
        #ifdef SSR_ACCUM_ACTIVE
            reflDirW = ssrLobeDir(reflDirW, lobeN, reflectionRoughness, ssrLobeRandom(noisetex, gl_FragCoord.xy, frameCounter));
            vec3 histColor;
            float histEdge;
            bool hasHist = ssrHistory(scenePos, viewTexel, histColor, histEdge);
            if (hasHist && histEdge > 0.98 && !ssrTraceThisFrame(gl_FragCoord.xy, frameCounter, reflectionRoughness)) {
                refl = histColor;
            } else {
                refl = ssrReflection(viewPos, reflDirW, refl, dither, ssrJitter, viewTexel, ssrRayQuality(reflectionRoughness));
                if (hasHist) refl = mix(refl, histColor, SSR_ACCUM_BLEND * histEdge);
            }
            outSSR = vec4(refl, length(scenePos));
        #else
            refl = ssrReflection(viewPos, reflDirW, refl, dither, ssrJitter, viewTexel, ssrRayQuality(reflectionRoughness));
        #endif
        }
        color += refl * F * reflWeight;
    }
  #endif
#endif

#if defined RAIN_PUDDLES && !defined WORLD_NETHER && !defined WORLD_END
    if (puddleCover > 0.001) {
        vec3 Vw = -dirW;
        float NoV = saturate(dot(puddleN, Vw));
        float fres = 0.02 + 0.98 * pow5(1.0 - NoV);
        vec3 reflDir = reflect(dirW, puddleN);
        float skyVis = pow2(lm.y);

  #if RAIN_PUDDLE_REFLECTIONS == 0
        vec3 env = skyAmbient(sunDir, rainStrength) * (0.7 + 0.6 * skyVis);
  #else
        vec3 env = dimensionSkyReflection(reflDir, sunDir, fogColor, frameTimeCounter, rainStrength) * skyVis;
    #if RAIN_PUDDLE_REFLECTIONS == 2
        env = ssrReflection(viewPos, reflDir, env, dither, ssrJitter, viewTexel, 1.0);
      #ifdef SSR_ACCUM_ACTIVE
        env = ssrAccumulate(env, scenePos, viewTexel);
        outSSR = vec4(env, length(scenePos));
      #endif
    #endif
  #endif
        float glintAA = 1.0 / (1.0 + footprint * footprint * 45.0);
        vec3 glint = discLightSpecular(puddleN, Vw, lightDir, SUN_GLINT_RADIUS, 0.015, vec3(0.02))
                   * lightCol * directShadow * (SUN_GLINT_STRENGTH * 0.6 * glintAA);

        float reflMix = puddleCover * fres;
        color = mix(color, env, reflMix) + glint * puddleCover;
    }
#endif

#ifdef LOD_ACTIVE
    if (lodPixel) {
        vec4 clouds = texture(colortex13, cloudRegionUV(uv, viewTexel));
        color = color * clouds.a + clouds.rgb;
    }
#endif

#ifdef VOXY
    {
        vec4 lodLayer = texture(colortex8, uv);
        if (lodLayer.a > 0.001) {
            float lodTransDepth = texture(lodDepthTex0, uv).r;
            if (lodTransDepth < 1.0) {
                float lodDist = length(screenToView(vec3(uv, lodTransDepth), lodProjectionInverse));
                float vanillaDist = depth < 1.0
                    ? length(screenToView(vec3(uv, depth), gbufferProjectionInverse))
                    : 1e9;
                if (lodDist < vanillaDist)
                    color = color * (1.0 - lodLayer.a) + lodLayer.rgb;
            }
        }
    }
#endif

#ifdef REFRAX_GLINT_BUFFER
    color += texture(colortex7, uv).rgb;
#endif

    outColor = lineOverlay(vec4(color, 1.0));
}
