#version 400 compatibility

/*
====================================================================================================

    Copyright (C) 2020 RRe36

    All Rights Reserved unless otherwise explicitly stated.


    By downloading this you have agreed to the license and terms of use.
    These can be found inside the included license-file
    or here: https://rre36.com/copyright-license

    Violating these terms may be penalized with actions according to the Digital Millennium
    Copyright Act (DMCA), the Information Society Directive and/or similar laws
    depending on your country.

====================================================================================================
*/

/*DRAWBUFFERS:012*/
layout(location = 0) out vec4 scene;
layout(location = 1) out uvec4 sceneData;
layout(location = 2) out vec4 rainData;

#include "/lib/head.glsl"
#include "/lib/util/encoders.glsl"
#include "/lib/util/colorspace.glsl"

const int shadowMapResolution   = 3072;     //[512 1024 1536 2048 2560 3072 3584 4096 6144 8192 16384]
const float shadowDistance      = 128.0;

const bool shadowHardwareFiltering = true;

in vec2 coord;

flat in mat4x3 lightColor;

uniform sampler2D colortex0;
uniform usampler2D colortex1;
uniform sampler2D colortex2;
uniform sampler2D colortex3;
uniform sampler2D colortex5;
uniform sampler2D colortex6;

uniform sampler2D depthtex0;
uniform sampler2D depthtex1;

uniform sampler2D noisetex;

uniform sampler2DShadow shadowtex0;
uniform sampler2DShadow shadowtex1;
uniform sampler2D shadowcolor0;

uniform int frameCounter;
uniform int isEyeInWater;
uniform int worldTime;

uniform float eyeAltitude;
uniform float far, near;
uniform float frameTimeCounter;
uniform float lightFlip;
uniform float sunAngle;
uniform float rainStrength, wetness;
uniform float worldAnimTime;

uniform ivec2 eyeBrightness;
uniform ivec2 eyeBrightnessSmooth;

uniform vec2 taaOffset;
uniform vec2 viewSize, pixelSize;
uniform vec2 skyCaptureResolution;

uniform vec3 cameraPosition;
uniform vec3 lightvec, lightvecView;

uniform mat4 gbufferModelView, gbufferModelViewInverse;
uniform mat4 gbufferProjection, gbufferProjectionInverse;
uniform mat4 shadowModelView, shadowModelViewInverse;
uniform mat4 shadowProjection, shadowProjectionInverse;


/* ------ includes ------ */
#define FUTIL_TBLEND
#define FUTIL_MAT16
#include "/lib/fUtil.glsl"

#include "/lib/frag/bluenoise.glsl"

#include "/lib/frag/gradnoise.glsl"

#include "/lib/util/transforms.glsl"

#include "/lib/atmos/air/const.glsl"

#include "/lib/atmos/phase.glsl"

#include "/lib/atmos/waterConst.glsl"

#include "/lib/light/warp.glsl"

#include "/lib/frag/noise.glsl"

vec3 getShadowmapPos(vec3 scenePos, const float bias) {  //shadow 2d
    vec3 pos    = scenePos + vec3(bias) * lightvec;
    float a     = length(pos);
        pos     = viewMAD(shadowModelView, pos);
        pos     = projMAD(shadowProjection, pos);
        pos.z  *= 0.2;
        pos.z  -= 0.0012*(saturate(a/256.0));

        pos.xy  = shadowmapWarp(pos.xy);

    return pos*0.5+0.5;
}
vec3 toShadowmapTexturePos(vec3 shadowPos) {
    //shadowPos.z    *= 0.2;
    shadowPos.xy    = shadowmapWarp(shadowPos.xy) * 0.5 + 0.5;

    return shadowPos;
}

vec3 getShadowcol(sampler2D tex, vec2 coord) {
    vec4 x  = texture(tex, coord);

    if (x.a < 0.97 && x.a > 0.93) {
        const float distance = 20.0;
        float caustic     = (2.5 * x.z * x.z);

        vec3 absorb     = expf(-max0(distance) * waterAbsorbCoeff);
            absorb     *= mix(caustic, 1.0, expf(-max0(distance) * 0.1));
            
        x.rgb = absorb;
    } else {
        x.rgb = sqr(x.rgb);
    }

    return mix(vec3(1.0), x.rgb, x.a);
}

vec3 getShadowcol(sampler2D tex, vec2 coord, float waterDepth) {
    vec4 x  = texture(tex, coord);

    if (x.a < 0.97 && x.a > 0.93) {
        float distance = 20.0 + waterDepth * 20.0;
        float caustic     = (2.5 * x.z * x.z);

        vec3 absorb     = expf(-max0(distance) * waterAbsorbCoeff);
            absorb     *= mix(caustic, 1.0, expf(-max0(distance) * 0.1));
            
        x.rgb = absorb;
    } else {
        x.rgb = sqr(x.rgb);
    }

    return mix(vec3(1.0), x.rgb, x.a);
}

vec3 getShadowcolUW(sampler2D tex, vec2 coord) {
    vec4 x  = texture(tex, coord);

    if (x.a < 0.97 && x.a > 0.93) {
        const float distance = 25.0;
        float caustic     = (2.5 * x.z * x.z);

        float absorb      = mix(caustic, 1.0, expf(-max0(distance) * 0.1));
            
        x.rgb = vec3(absorb);
    } else {
        x.rgb = sqr(x.rgb);
    }

    return mix(vec3(1.0), x.rgb, x.a);
}

/* ------ simple fog ------ */

vec3 simpleFog(vec3 scene, float d, float cave) {
    float density   = d * 1e-5 * fogDensityMult * fogAirScale.x;

    float transmittance = expf(-density);

    vec3 light      = (sunAngle<0.5 ? lightColor[0] : lightColor[2]) * lightFlip * 0.03 * sqr(cave) + lightColor[1] * cave;

    return scene * transmittance + light * density * pi;
}
vec3 waterFog(vec3 scene, float d, vec3 color) {
    float density   = max(0.0, d) * waterDensity;

    vec3 transmittance = expf(-waterAttenCoeff * density);

    const vec3 scatterCoeff = vec3(5e-2, 1e-1, 2e-1);

    vec3 scatter    = 1.0-exp(-density * scatterCoeff);
        scatter    *= max(expf(-waterAttenCoeff * density), expf(-waterAttenCoeff * pi));

    return scene * transmittance + scatter * color * rcp(pi);
}
vec3 lavaFog(vec3 scene, float d) {
    float density   = max(0.0, d);

    float transmittance = expf(-1.0 * density);

    return mix(vec3(1.0, 0.3, 0.02), scene, transmittance);
}


/* ------ volumetric fog ------ */

vec2 airPhaseFunction(float cosTheta) {
    return vec2(rayleighPhase(cosTheta), mieCS(cosTheta, airMieG));
}
float airMieBackscatter(float cosTheta) {
    return mieHG(cosTheta, -airMieG * rcp(pi));
}
float fogMistPhase(float cosTheta, float density) {
    return mix(mieCS(cosTheta, pow(airMieG, 1.0 + density)), airMieBackscatter(cosTheta), 0.19);
}

uniform vec3 fogDensityCoeff;

#define fogMistAltitude 100.0   //[30.0 40.0 50.0 60.0 70.0 80.0 90.0 100.0 110.0 120.0 130.0 140.0 150.0]
#define fogSeaLevel 64.0    //[8.0 16.0 24.0 32.0 40.0 48.0 56.0 64.0 72.0 80.0 88.0 96.0]
#define fogMistFalloff 1.0  //[0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0 1.1 1.2 1.3 1.4 1.5 1.6 1.7 1.8 1.9 2.0]

#ifdef freezeAtmosAnim
    const float fogTime   = float(atmosAnimOffset) * 0.006;
#else
    #ifdef volumeWorldTimeAnim
        float fogTime     = worldAnimTime * 3.6;
    #else
        float fogTime     = frameTimeCounter * 0.006;
    #endif
#endif

float mistDensity(vec3 rPos, float altitude) {
    vec3 wind   = vec3(fogTime, 0.0, fogTime * 0.5);
    float noise = value3D(rPos * 0.03 + wind) + value3D(rPos * 0.06 + wind * pi) * 0.5;

    float mist  = expf(-max0((altitude - fogSeaLevel) * 0.01 * fogMistFalloff));
        mist    = sstep(noise * rcp(1.5) - (1.0 - mist) * 0.5, 0.4, 0.7);
        mist   *= sqr(1.0 - linStep(altitude, fogMistAltitude - 15.0, fogMistAltitude));

    return mist * fogDensityCoeff.z;
}

vec3 fogAirDensity(vec3 rPos, float altitude) {
    rPos   += cameraPosition;

    float maxFade = sqr(1.0 - linStep(altitude, 144.0, 256.0));

    vec2 rm     = expf(-max0((altitude - fogSeaLevel) * fogFalloffScale));
    //rm.y *= 0;
    float mist  = 0.0;
    if (fogDensityCoeff.z > 1e-5) mist = mistDensity(rPos, altitude);

    return vec3(rm * fogAirScale * fogDensityCoeff.xy * maxFade * fogDensityMult, mist);
}

#define airDensity 1.0
#define fogMinSteps 8
#define fogAdaptiveSteps 12     //[4 6 8 10 12 14 16 18]
#define fogClipDist 512.0       //[256.0 384.0 448.0 512.0 768.0 1024.0]
#define fogMistLightingSteps 2  //[2 4 6 8 10 12]

float fogMistLightOD(vec3 pos) {
    float stepsize = 12.0;

    const int steps = fogMistLightingSteps;

    float od = 0.0;

    for(uint i = 0; i < steps; ++i, pos += lightvec * stepsize) {

        if(pos.y > fogMistAltitude || pos.y < 0.0) continue;
        
        float density = mistDensity(pos, pos.y);
        if (density <= 0.0) continue;

            od += density * stepsize;
    }

    return od;
}

mat2x3 volumetricFog(vec3 scenePos, vec3 sceneDir, bool isSky, float vDotL, float dither, float cave) {
    float topDist    = length(sceneDir * ((256.0 - eyeAltitude) * rcp(sceneDir.y)));
    float bottomDist = length(sceneDir * ((-32.0 - eyeAltitude) * rcp(sceneDir.y)));

    float volumeHull = sceneDir.y > 0.0 ? topDist : bottomDist;

    float endDist   = isSky ? min(volumeHull, fogClipDist) : length(scenePos);
    float startDist = eyeAltitude > 256.0 ? topDist : 1.0;

    vec3 startPos   = eyeAltitude > 256.0 ? sceneDir * startDist : vec3(0.0);
        startPos   += gbufferModelViewInverse[3].xyz;
    vec3 endPos     = isSky ? sceneDir * endDist : scenePos;

    float baseStep  = length(endPos - startPos);
    float stepCoeff = saturate(baseStep * rcp(clamp(far, 256.0, 512.0)));

    uint steps      = fogMinSteps + uint(stepCoeff * fogAdaptiveSteps);

    vec3 rStep      = (endPos - startPos) / float(steps);
    vec3 rPos       = startPos + rStep * dither;
    float rLength   = length(rStep);

    vec3 shadowStartPos = viewMAD(shadowModelView, (startPos));
        shadowStartPos  = projMAD(shadowProjection, shadowStartPos);
        shadowStartPos.z *= 0.2;
    vec3 shadowEndPos   = viewMAD(shadowModelView, (endPos));
        shadowEndPos    = projMAD(shadowProjection, shadowEndPos);
        shadowEndPos.z *= 0.2;

    vec3 shadowStep = (shadowEndPos - shadowStartPos) / float(steps);
    vec3 shadowPos  = shadowStartPos + shadowStep * dither;

    mat2x3 scattering = mat2x3(0.0);
    vec3 transmittance = vec3(1.0);

    vec3 phase;
        phase.xy    = airPhaseFunction(vDotL);
        phase.z     = mix(mieCS(vDotL, airMieG * mistMieAnisotropy), airMieBackscatter(vDotL), mistMieAnisotropy / 4.0);
    float phaseIso  = 0.25;

    vec3 sunlight       = (worldTime>23000 || worldTime<12900) ? lightColor[0] : lightColor[2];
        sunlight       *= lightFlip * sqrt2;
    vec3 skylight       = lightColor[1] * cave;

    #ifdef lightleakWorkaroundToggle
        sunlight       *= cave;
    #endif

    float pFade         = saturate(mieHG(vDotL, 0.65));

    for (uint i = 0; i < steps; ++i, rPos += rStep, shadowPos += shadowStep) {
        if (max3(transmittance) < 0.01) break;

        float altitude  = rPos.y + eyeAltitude;

        if (altitude > 256.0) continue;

        vec3 density    = fogAirDensity(rPos, altitude);

        //if (max3(density) < 1e-32) continue;

        vec3 stepRho    = density * rLength;
        vec3 od         = fogExtinctMat * stepRho;

        vec3 stepT      = expf(-od);
        vec3 scatterInt = saturate((stepT - 1.0) * rcp(-max(od, 1e-16)));
        vec3 visScatter = transmittance * scatterInt;

        #ifdef fogMistAdvanced
            if (density.z > 0.0) {
                float mistLightOD = fogMistLightOD(rPos + cameraPosition);

                float mistLighting  = expf(-stepRho.z) * pow(1.0 + 1.0 * 0.7 * mistLightOD, -1.0 / 0.7);
                    mistLighting    = mix(pi * (1.0 - 0.6 * expf(-density.z * 28.0)), 1.0, pFade);
                    mistLighting    = fogMistPhase(vDotL, mistLightOD);
                phase.z     = mistLighting;
            }
        #else
            if (density.z > 0.0) {
                phase.z     = fogMistPhase(vDotL, density.z) * (2.0 - pFade);
            }
        #endif

        vec3 sunScatter = fogScatterMat * (stepRho * phase) * visScatter;
        vec3 skyScatter = fogScatterMat * (stepRho * phaseIso) * visScatter;

        vec3 shadowCoord = vec3(shadowmapWarp(shadowPos.xy), shadowPos.z) * 0.5 + 0.5;

        float shadow0   = texture(shadowtex0, shadowCoord);

        float shadow    = 1.0;
        vec3 shadowCol  = vec3(1.0);
        
        if (shadow0 < 1.0) {
            shadow      = texture(shadowtex1, shadowCoord);

            if (abs(shadow - shadow0) > 0.1) {
                shadowCol   = getShadowcol(shadowcolor0, shadowCoord.xy);
            }
        }

        scattering[0]  += (sunScatter * shadowCol * transmittance) * shadow;
        scattering[1]  += skyScatter * transmittance;

        transmittance  *= stepT;
    }

    vec3 color  = scattering[0] * sunlight + scattering[1] * skylight;

    if (color != color) {   //because NaNs on nVidia don't need a logic cause to happen
        color = vec3(0.0);
        transmittance = vec3(1.0);
    }

    return mat2x3(color, saturate(transmittance));
}

mat2x3 volumetricFog2(vec3 scenePos, vec3 startPos0, vec3 sceneDir, bool isSky, float vDotL, float dither, float cave) {
    float topDist    = length(sceneDir * ((256.0 - eyeAltitude) * rcp(sceneDir.y)));
    float bottomDist = length(sceneDir * ((-32.0 - eyeAltitude) * rcp(sceneDir.y)));

    float volumeHull = sceneDir.y > 0.0 ? topDist : bottomDist;

    float endDist   = isSky ? min(volumeHull, fogClipDist) : length(scenePos);
    //float startDist = eyeAltitude > 256.0 ? topDist : 1.0;

    vec3 startPos   = startPos0;
    vec3 endPos     = isSky ? sceneDir * endDist : scenePos;

    float baseStep  = length(endPos - startPos);
    float stepCoeff = saturate(baseStep * rcp(clamp(far, 256.0, 512.0)));

    uint steps      = 6 + uint(stepCoeff * 8);

    vec3 rStep      = (endPos - startPos) / float(steps);
    vec3 rPos       = startPos + rStep * dither;
    float rLength   = length(rStep);

    vec3 shadowStartPos = viewMAD(shadowModelView, (startPos));
        shadowStartPos  = projMAD(shadowProjection, shadowStartPos);
        shadowStartPos.z *= 0.2;
    vec3 shadowEndPos   = viewMAD(shadowModelView, (endPos));
        shadowEndPos    = projMAD(shadowProjection, shadowEndPos);
        shadowEndPos.z *= 0.2;

    vec3 shadowStep = (shadowEndPos - shadowStartPos) / float(steps);
    vec3 shadowPos  = shadowStartPos + shadowStep * dither;

    mat2x3 scattering = mat2x3(0.0);
    vec3 transmittance = vec3(1.0);

    vec3 phase;
        phase.xy    = airPhaseFunction(vDotL);
        phase.z     = mix(mieCS(vDotL, airMieG * mistMieAnisotropy), airMieBackscatter(vDotL), mistMieAnisotropy / 4.0);
    float phaseIso  = 0.25;

    vec3 sunlight       = (worldTime>23000 || worldTime<12900) ? lightColor[0] : lightColor[2];
        sunlight       *= lightFlip * sqrt2;
    vec3 skylight       = lightColor[1] * cave;

    #ifdef lightleakWorkaroundToggle
        sunlight       *= cave;
    #endif

    float pFade         = saturate(mieHG(vDotL, 0.65));

    for (uint i = 0; i < steps; ++i, rPos += rStep, shadowPos += shadowStep) {
        if (max3(transmittance) < 0.01) break;

        float altitude  = rPos.y + eyeAltitude;

        if (altitude > 256.0) continue;

        vec3 density    = fogAirDensity(rPos, altitude);

        //if (max3(density) < 1e-32) continue;

        vec3 stepRho    = density * rLength;
        vec3 od         = fogExtinctMat * stepRho;

        vec3 stepT      = expf(-od);
        vec3 scatterInt = saturate((stepT - 1.0) * rcp(-max(od, 1e-16)));
        vec3 visScatter = transmittance * scatterInt;

        #ifdef fogMistAdvanced
            if (density.z > 0.0) {
                float mistLightOD = fogMistLightOD(rPos + cameraPosition);

                float mistLighting  = expf(-stepRho.z) * pow(1.0 + 1.0 * 0.7 * mistLightOD, -1.0 / 0.7);
                    mistLighting    = mix(pi * (1.0 - 0.6 * expf(-density.z * 28.0)), 1.0, pFade);
                    mistLighting    = fogMistPhase(vDotL, mistLightOD);
                phase.z     = mistLighting;
            }
        #else
            if (density.z > 0.0) {
                phase.z     = fogMistPhase(vDotL, density.z) * (2.0 - pFade);
            }
        #endif

        vec3 sunScatter = fogScatterMat * (stepRho * phase) * visScatter;
        vec3 skyScatter = fogScatterMat * (stepRho * phaseIso) * visScatter;

        vec3 shadowCoord = vec3(shadowmapWarp(shadowPos.xy), shadowPos.z) * 0.5 + 0.5;

        float shadow0   = texture(shadowtex0, shadowCoord);

        float shadow    = 1.0;
        vec3 shadowCol  = vec3(1.0);
        
        if (shadow0 < 1.0) {
            shadow      = texture(shadowtex1, shadowCoord);

            if (abs(shadow - shadow0) > 0.1) {
                shadowCol   = getShadowcol(shadowcolor0, shadowCoord.xy);
            }
        }

        scattering[0]  += (sunScatter * shadowCol * transmittance) * shadow;
        scattering[1]  += skyScatter * transmittance;

        transmittance  *= stepT;
    }

    vec3 color  = scattering[0] * sunlight + scattering[1] * skylight;

    if (color != color) {   //because NaNs on nVidia don't need a logic cause to happen
        color = vec3(0.0);
        transmittance = vec3(1.0);
    }

    return mat2x3(color, saturate(transmittance));
}

#define waterVolMinSteps 6
#define waterVolAdaptiveSteps 6     //[2 4 6 8 10 12]
#define waterVolClipDist 48.0       //[16.0 24.0 32.0 40.0 48.0 56.0 64.0]

mat2x3 volumetricWater(vec3 scenePos, vec3 startPos0, vec3 sceneDir, bool isSky, float vDotL, float dither, float cave) {
    float endDist   = isSky ? waterVolClipDist : length(scenePos);

    vec3 startPos   = startPos0;
        //startPos   += gbufferModelViewInverse[3].xyz;
    vec3 endPos     = isSky ? sceneDir * waterVolClipDist : scenePos;
        if (distance(startPos, endPos) > waterVolClipDist) endPos = waterVolClipDist * sceneDir;

    float baseStep  = length(endPos - startPos);
    float stepCoeff = saturate(baseStep * rcp(waterVolClipDist));
        //if (isEyeInWater == 1) stepCoeff = 1.0;

    uint steps      = waterVolMinSteps + uint(stepCoeff * waterVolAdaptiveSteps);

    vec3 rStep      = (endPos - startPos) / float(steps);
    vec3 rPos       = startPos + rStep * dither;
    float rLength   = length(rStep);

    vec3 shadowStartPos = viewMAD(shadowModelView, (startPos));
        shadowStartPos  = projMAD(shadowProjection, shadowStartPos);
        shadowStartPos.z *= 0.2;
    vec3 shadowEndPos   = viewMAD(shadowModelView, (endPos));
        shadowEndPos    = projMAD(shadowProjection, shadowEndPos);
        shadowEndPos.z *= 0.2;

    vec3 shadowStep = (shadowEndPos - shadowStartPos) / float(steps);
    vec3 shadowPos  = shadowStartPos + shadowStep * dither;

    mat2x3 scattering = mat2x3(0.0);
    vec3 transmittance = vec3(1.0);

    float phase     = mieHG(vDotL, 0.7);
    float phaseIso  = 0.25 * rcp(pi);
        phase       = mix(phase, phaseIso, 0.3);

    vec3 sunlight       = (worldTime>23000 || worldTime<12900) ? lightColor[0] : lightColor[2];
        sunlight       *= lightFlip;
    vec3 skylight       = lightColor[1] * cave * rpi;

    for (uint i = 0; i < steps; ++i, rPos += rStep, shadowPos += shadowStep) {
        if (distance(startPos, rPos) > waterVolClipDist) continue;
        if (max3(transmittance) < 0.01) break;

        float altitude  = rPos.y + eyeAltitude;

        if (altitude > 256.0) continue;

        float stepRho   = rLength * waterDensity;
        /*vec3 od         = waterAttenCoeff * stepRho;

        vec3 stepT      = expf(-od);
        vec3 scatterInt = saturate((stepT - 1.0) * rcp(-max(od, 1e-16)));
        vec3 visScatter = transmittance * scatterInt;

        vec3 sunScatter = waterScatterCoeff * (stepRho * phase) * visScatter;
        vec3 skyScatter = waterScatterCoeff * (stepRho * phaseIso) * visScatter;
*/
        vec3 shadowCoord = vec3(shadowmapWarp(shadowPos.xy), shadowPos.z) * 0.5 + 0.5;

        float shadow0   = texture(shadowtex0, shadowCoord);

        float shadow    = 1.0;
        vec3 shadowCol  = vec3(1.0);
        
        if (shadow0 < 1.0) {
            shadow      = texture(shadowtex1, shadowCoord);

            if (abs(shadow - shadow0) > 0.1) {
                shadowCol   = getShadowcol(shadowcolor0, shadowCoord.xy, 1.0 - cave);
            }
        }

        scattering[0]  += (shadowCol * transmittance) * shadow;
        scattering[1]  += transmittance;

        transmittance  *= expf(-waterAttenCoeff * stepRho);

        /*scattering[0]  += (sunScatter * shadowCol * transmittance) * shadow;
        scattering[1]  += skyScatter * transmittance;

        transmittance  *= expf(-od);*/
    }

    vec3 attenMul   = (1.0 - expf(-waterAttenCoeff * rLength)) * rcp(waterAttenCoeff);

    scattering[0]  *= waterScatterCoeff * attenMul * phase;
    scattering[1]  *= waterScatterCoeff * attenMul;

    vec3 color  = scattering[0] * sunlight + scattering[1] * skylight;

    if (color != color) {   //because NaNs on nVidia don't need a logic cause to happen
        color = vec3(0.0);
        transmittance = vec3(1.0);
    }

    return mat2x3(color, saturate(transmittance));
}

void applyFogData(inout vec3 color, in mat2x3 data) {
    color = color * data[1] + data[0];
}

/* ------ reflections ------ */

#include "/lib/light/brdf.glsl"

#include "/lib/frag/labPBR.glsl"

#include "/lib/frag/ssr.glsl"

/*
These two functions used for rough reflections are based on zombye's spectrum shaders
https://github.com/zombye/spectrum
*/

mat3 getRotationMat(vec3 x, vec3 y) {
	float cosine = dot(x, y);
	vec3 axis = cross(y, x);

	float tmp = 1.0 / dot(axis, axis);
	      tmp = tmp - tmp * cosine;
	vec3 tmpv = axis * tmp;

	return mat3(
		axis.x * tmpv.x + cosine, axis.x * tmpv.y - axis.z, axis.x * tmpv.z + axis.y,
		axis.y * tmpv.x + axis.z, axis.y * tmpv.y + cosine, axis.y * tmpv.z - axis.x,
		axis.z * tmpv.x - axis.y, axis.z * tmpv.y + axis.x, axis.z * tmpv.z + cosine
	);
}
vec3 ggxFacetDist(vec3 viewDir, float roughness, vec2 xy) {
	/*
    GGX VNDF sampling
	http://www.jcgt.org/published/0007/04/01/
    */

    viewDir     = normalize(vec3(roughness * viewDir.xy, viewDir.z));

    float clsq  = dot(viewDir.xy, viewDir.xy);
    vec3 T1     = vec3(clsq > 0.0 ? vec2(-viewDir.y, viewDir.x) * inversesqrt(clsq) : vec2(1.0, 0.0), 0.0);
    vec3 T2     = vec3(-T1.y * viewDir.z, viewDir.z * T1.x, viewDir.x * T1.y - T1.x * viewDir.y);

	float r     = sqrt(xy.x);
	float phi   = tau * xy.y;
	float t1    = r * cos(phi);
	float a     = saturate(1.0 - t1 * t1);
	float t2    = mix(sqrt(a), r * sin(phi), 0.5 + 0.5 * viewDir.z);

	vec3 normalH = t1 * T1 + t2 * T2 + sqrt(saturate(a - t2 * t2)) * viewDir;

	return normalize(vec3(roughness * normalH.xy, normalH.z));
}

vec2 unprojectSphere(vec3 dir) {
    vec2 lonlat     = vec2(atan(-dir.x, dir.z), acos(dir.y));
    return lonlat * vec2(rcp(tau), rcp(pi)) + vec2(0.5, 0.0);
}
/*
vec3 fresnelTinted(vec3 tint, float VoH) {
    float f0    = v3avg(tint);
        f0      = min(sqrt(f0), 0.99999);
        f0      = (1.0 + f0) * rcp(1.0 - f0);

    float sinThetaI     = sqrt(saturate(1.0 - sqr(VoH)));
    float sinThetaT     = sinThetaI * rcp(f0);
    float cosThetaT     = sqrt(1.0 - sqr(sinThetaT));

    float Rs    = sqr((VoH - (f0 * cosThetaT)) * rcp(VoH + (f0 * cosThetaT)));
    float Rp    = sqr((cosThetaT - (f0 * VoH)) * rcp(cosThetaT + (f0 * VoH)));

    return saturate((Rs + Rp) * 0.5) * tint;
}
*/

/* ------ rainbows ------ */

#ifdef rainbowsEnabled

vec3 hsv2rgb(vec3 c) {
    vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
    vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
    return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
}

vec3 generateRainbow(vec3 viewDir, float falloff) {
    float d     = dot(viewDir, -lightvecView);

    float h1    = clamp(linStep(d, 0.4, 0.5), 0.0, 0.5);
    float f1    = linStep(d, 0.395, 0.41) * sqr(1.0 - linStep(d, 0.43, 0.46));
    float b1    = linStep(d, 0.4, 0.42) * sqr(1.0 - linStep(d, 0.46, 0.75));

    vec3 c1     = hsv2rgb(vec3(h1, 1.0, 1.0)) * f1 + b1 * 0.3;
        c1     *= falloff;

    d  += 0.15;
    float h2    = clamp(0.5 - linStep(d, 0.35, 0.5), 0.0, 0.5);
    float f2    = linStep(d, 0.345, 0.38) * (1.0 - linStep(d, 0.42, 0.44));

    vec3 c2     = hsv2rgb(vec3(h2, 1.0, 1.0)) * f2 * 0.4 * sqr(falloff);

    vec3 lcol   = sunAngle > 0.5 ? lightColor[2] : lightColor[0];

    return (c1 + c2) * lcol * 0.014 * lightFlip;
}

#endif

/* ------ refraction ------ */

vec3 refract2(vec3 I, vec3 N, vec3 NF, float eta) {     //from spectrum by zombye
    float NoI = dot(N, I);
    float k = 1.0 - eta * eta * (1.0 - NoI * NoI);
    if (k < 0.0) {
        return vec3(0.0); // Total Internal Reflection
    } else {
        float sqrtk = sqrt(k);
        vec3 R = (eta * dot(NF, I) + sqrtk) * NF - (eta * NoI + sqrtk) * N;
        return normalize(R * sqrt(abs(NoI)) + eta * I);
    }
}


vec3 unpackAuxAlbedo(in uint ui) {
    vec3 albedo     = vec3(0.0);
        albedo.r    = float(bitfieldExtract(ui, 0, 5)) / 31.0;
        albedo.g    = float(bitfieldExtract(ui, 5, 6)) / 63.0;
        albedo.b    = float(bitfieldExtract(ui, 11, 5)) / 31.0;

    return albedo;
}

vec3 unpackAuxMat(in uint ui) {
    float parallaxShadow    = float(bitfieldExtract(ui, 16, 4)) / 63.0;
    float rain              = float(bitfieldExtract(ui, 20, 4)) / 63.0;
    float wetness           = float(bitfieldExtract(ui, 24, 8)) / 255.0;

    return vec3(parallaxShadow, wetness, rain);
}

void main() {
    vec4 sceneColor   = stex(colortex0);
    //vec4 tex1       = stex(colortex1);
    vec4 tex2       = stex(colortex2);
    vec4 tex3       = stex(colortex3);

    uvec4 tex1      = stex(colortex1);
        vec2 d1     = unpack2x16(tex1.y);
        //vec2 d3     = unpack2x16(tex1.w);
        vec3 auxMat = unpackAuxMat(tex1.w);
    vec2 sceneLmap  = decode2x8(d1.x);

    vec3 sceneNormal = decodeNormal(unpack2x16(tex1.x));
    
    int matID       = decodeMatID16(d1.y);

    float sceneDepth0   = stex(depthtex0).x;
    vec3 viewPos0       = screenToViewSpace(vec3(coord, sceneDepth0));
    vec3 scenePos0      = viewToSceneSpace(viewPos0);

    float sceneDepth1   = stex(depthtex1).x;
    vec3 viewPos1       = screenToViewSpace(vec3(coord, sceneDepth1));
    vec3 scenePos1      = viewToSceneSpace(viewPos1);

    float vDotL     = dot(normalize(viewPos1), lightvecView);
    
    bool translucent = (sceneDepth0<sceneDepth1);

    vec3 translucentAlbedo = vec3(1.0);

    bool water      = false;

    vec4 specularData = unpack4x8(tex1.z);

    #ifdef resourcepackReflectionsEnabled
        materialProperties material = labToGeneric(decodeSpecularTexture(specularData));
    #else
        materialProperties material = materialProperties(0.0001, 0.02, false, false, mat2x3(1.0));
    #endif

    vec3 tex6   = stex(colortex6).rgb;

    if (translucent) {
        sceneNormal = decodeNormal(tex2.xy);
        vec2 d1     = decode2x8(tex2.z);
        matID       = decodeMatID8(d1.y);
        sceneLmap.y = d1.x;

        water       = matID == 102;

        translucentAlbedo = decode3x8(tex6.x);

        if (water) material = materialProperties(0.0001, 0.02, false, false, mat2x3(1.0));
    }

    vec3 viewNormal = normalize(mat3(gbufferModelView) * sceneNormal);

    float caveMult  = linStep(eyeBrightnessSmooth.y/240.0, 0.1, 0.9);

    vec3 fogcol     = (sunAngle<0.5 ? lightColor[0] : lightColor[2]) * lightFlip * mieHG(vDotL, 0.68) * sqr(caveMult);
        fogcol     *= mix(0.33, 1.0, sqrt(abs(lightvec.y)));
        fogcol     += lightColor[1] * caveMult * rcp(pi);

    float bluenoise     = ditherBluenoise();

    if (water || matID == 103){
        vec3 flatNormal     = normalize(cross(dFdx(scenePos0), dFdy(scenePos0)));
        vec3 flatViewNormal = normalize(mat3(gbufferModelView) * flatNormal);

        vec3 normalCorrected = dot(viewNormal, normalize(viewPos1)) > 0.0 ? -viewNormal : viewNormal;

        vec3 refractedDir   = refract2(normalize(viewPos1), normalCorrected, flatViewNormal, rcp(1.33));
        //vec3 refractedDir   = refract(normalize(viewPos1), normalCorrected, rcp(1.33));

        float refractedDist = distance(viewPos0, viewPos1);

        vec3 refractedPos   = viewPos1 + refractedDir * refractedDist;

        vec3 screenPos      = viewToScreenSpace(refractedPos);

        float distToEdge    = max2(abs(screenPos.xy * 2.0 - 1.0));
            distToEdge      = sqr(sstep(distToEdge, 0.7, 1.0));

        screenPos.xy    = mix(screenPos.xy, coord, distToEdge);

        //vec2 refractionDelta = coord - screenPos.xy;

        float sceneDepth    = texture(depthtex1, screenPos.xy).x;

        if (sceneDepth > sceneDepth0) {
            sceneDepth1 = sceneDepth;
            viewPos1    = screenToViewSpace(vec3(screenPos.xy, sceneDepth1));
            scenePos1   = viewToSceneSpace(viewPos1);

            sceneColor.rgb  = texture(colortex0, screenPos.xy).rgb;
        }
    }

    float dist0     = distance(scenePos0, gbufferModelViewInverse[3].xyz);
    float dist1     = distance(scenePos1, gbufferModelViewInverse[3].xyz);

    if (translucent && isEyeInWater==0){
        if (!water) {
            #ifdef fogVolumeEnabled
                mat2x3 waterVol   = volumetricFog2(scenePos1, scenePos0, normalize(scenePos0), !landMask(sceneDepth0), vDotL, bluenoise, caveMult);
                applyFogData(sceneColor.rgb, waterVol);
            #endif
        }

        sceneColor.rgb  = blendTranslucencies(sceneColor.rgb, tex3, translucentAlbedo);
        
        if (water) {
            #ifdef waterVolumeEnabled
            float fogdist   = dist1-dist0;
            if (fogdist > waterVolClipDist) sceneColor.rgb = waterFog(sceneColor.rgb, fogdist - waterVolClipDist, fogcol);

            mat2x3 waterVol   = volumetricWater(scenePos1, scenePos0, normalize(scenePos0), !landMask(sceneDepth0), vDotL, bluenoise, caveMult);
            applyFogData(sceneColor.rgb, waterVol);
            #else
            sceneColor.rgb = waterFog(sceneColor.rgb, dist1-dist0, fogcol);
            #endif
        }
    }

    /* ------ reflections ------ */
    
    #ifdef resourcepackReflectionsEnabled

    if (water || material.roughness < 0.95){
        vec3 viewDir    = normalize(viewPos0);

        vec4 reflection = vec4(0.0);
        float skyOcclusion  = cubeSmooth(sqr(linStep(sceneLmap.y, skyOcclusionThreshold - 0.2, skyOcclusionThreshold)));

        vec3 metalAlbedo = sqr(unpackAuxAlbedo(tex1.w));

        vec3 fresnel    = vec3(0.0);

        #ifdef roughReflectionsEnabled

        if (material.roughness < 0.0002 || water) {
            vec3 reflectDir = reflect(viewDir, viewNormal);

            if (dot(viewDir, viewNormal) > 0.0) viewNormal = -viewNormal;

            vec3 viewNormalSSR  = viewNormal;

            if (dot(viewDir, viewNormalSSR) > 0.0) viewNormalSSR = -viewNormalSSR;

            reflection          = screenRT(viewPos0, viewNormalSSR, bluenoise, translucent);

            if (reflection.a < 0.5 && skyOcclusion > 0.0) {
                vec2 sphereCoord    = unprojectSphere(mat3(gbufferModelViewInverse) * reflectDir);
                    sphereCoord    *= rcp(skyCaptureResolution);
                    sphereCoord.y  += exp2(-SKY_RENDER_LOD) * 2.0;

                reflection.rgb      = texture(colortex5, sphereCoord).rgb;
                reflection.a        = 1.0;
                reflection         *= skyOcclusion;
            }

            fresnel   = BRDFfresnel(-viewDir, viewNormal, material, metalAlbedo);
        } else {
            float ssrAlpha  = 1.0 - sstep(material.roughness, roughSSR_maxR - 0.05, roughSSR_maxR);
            
            mat3 rot    = getRotationMat(vec3(0, 0, 1), viewNormal);
            vec3 tangentV = viewDir * rot;
            float noise = bluenoise;
            float dither = ditherGradNoiseTemporal();

            const uint steps    = roughReflectionSamples;
            const float rSteps  = 1.0 / float(steps);

            for (uint i = 0; i < steps; ++i) {
                vec2 xy         = vec2(fract((i + noise) * sqr(32.0) * phi), (i + noise) * rSteps);
                vec3 roughNrm   = rot * ggxFacetDist(-tangentV, material.roughness, xy);

                if (dot(viewDir, roughNrm) > 0.0) roughNrm = -roughNrm;

                vec3 reflectDir = reflect(viewDir, roughNrm);

                vec3 viewNormalSSR  = roughNrm;

                if (dot(viewDir, viewNormalSSR) > 0.0) viewNormalSSR = -viewNormalSSR;

                vec4 currReflection     = vec4(0.0);

                if (ssrAlpha > 0.0) currReflection = screenRT_Fast(viewPos0, viewNormalSSR, dither, translucent);
                
                if (currReflection.a < 0.5 && skyOcclusion > 0.0) {
                    vec3 worldDir       = mat3(gbufferModelViewInverse) * reflectDir;
                    vec2 sphereCoord    = unprojectSphere(worldDir);
                        sphereCoord    *= rcp(skyCaptureResolution);
                        sphereCoord.y  += exp2(-SKY_RENDER_LOD) * 2.0;

                    currReflection.rgb  = texture(colortex5, sphereCoord).rgb;
                    currReflection.a    = 1.0;
                    currReflection     *= expf(-max0(-worldDir.y * pi)) * 0.66 + 0.34;
                    currReflection     *= skyOcclusion;
                }
                reflection += currReflection;
                fresnel    += BRDFfresnel(-viewDir, roughNrm, material, metalAlbedo);
            }
            reflection *= rSteps;
            fresnel    *= rSteps;
            reflection.a *= 1.0 - linStep(material.roughness, roughReflectionsThreshold * 0.66, roughReflectionsThreshold);
        }

        #else

            vec3 reflectDir = reflect(viewDir, viewNormal);

            vec3 viewNormalSSR  = viewNormal;

            if (dot(viewDir, viewNormalSSR) > 0.0) viewNormalSSR = -viewNormalSSR;

            reflection          = screenRT(viewPos0, viewNormalSSR, bluenoise, translucent);

            if (reflection.a < 0.5 && skyOcclusion > 0.0) {
                vec2 sphereCoord    = unprojectSphere(mat3(gbufferModelViewInverse) * reflectDir);
                    sphereCoord    *= rcp(skyCaptureResolution);
                    sphereCoord.y  += exp2(-SKY_RENDER_LOD) * 2.0;

                reflection.rgb      = texture(colortex5, sphereCoord).rgb;
                reflection.a        = 1.0;
                reflection         *= skyOcclusion;
            }

            reflection.a *= 1.0 - linStep(material.roughness, roughReflectionsThreshold * 0.66, roughReflectionsThreshold);

            fresnel   = BRDFfresnel(-viewDir, viewNormal, material, metalAlbedo);

        #endif
        
        //vec3 fresnel   = BRDFfresnel(-viewDir, viewNormal, material, metalAlbedo);

        if (material.conductor && material.conductorComplex) reflection.rgb *= sqrt(metalAlbedo);
        
        if (material.conductor) {
            float diffuseLum    = getLuma(sceneColor.rgb);
            float reflectionLum = getLuma(reflection.rgb * fresnel);

            float darknessComp  = saturate(-(reflectionLum - diffuseLum / pi) / max(diffuseLum, reflectionLum));
                darknessComp   *= 0.25;

            sceneColor.rgb = mix(sceneColor.rgb, reflection.rgb * fresnel + sceneColor.rgb * darknessComp, reflection.a);
        }
        else sceneColor.rgb = mix(sceneColor.rgb, reflection.rgb, vec3(reflection.a) * fresnel);

        if (water || material.conductor) {
            vec3 shadowPos  = getShadowmapPos(scenePos0, 0.08);
            float shadow    = texture(shadowtex1, shadowPos);

            #ifdef lightleakWorkaroundToggle
                shadow     *= sstep(sceneLmap.y, 0.1, 0.2);
            #endif

            if (material.conductor) shadow *= auxMat.x;

            if (shadow > 1e-2) {
                vec3 directCol  = (sunAngle<0.5 ? lightColor[0] : lightColor[2] * 0.1) * lightFlip;
                if (water) sceneColor.rgb += BRDF_Beckmann(-viewDir, lightvecView, viewNormal, material) * directCol;
                //else sceneColor.rgb += BRDF(-viewDir, lightvecView, viewNormal, material, metalAlbedo) * directCol;
            }
        }

        #if DEBUG_VIEW == 6
            sceneColor.rgb = reflection.rgb;
        #endif
    }

    #else

    if (water || (auxMat.y > 1e-2 && landMask(sceneDepth1)) || matID == 103){
        bool waterIce   = water || matID == 103;
        vec3 viewDir    = normalize(viewPos0);

        vec4 reflection = vec4(0.0);
        float skyOcclusion  = cubeSmooth(sqr(linStep(sceneLmap.y, 0.7, 0.9)));
        vec3 reflectDir = reflect(viewDir, viewNormal);

        if (dot(viewDir, viewNormal) > 0.0) viewNormal = -viewNormal;

        reflection          = screenRT(viewPos0, viewNormal, bluenoise, translucent);

        if (reflection.a < 0.5) {
            vec2 sphereCoord    = unprojectSphere(mat3(gbufferModelViewInverse) * reflectDir);
                sphereCoord    *= rcp(skyCaptureResolution);
                sphereCoord.y  += exp2(-SKY_RENDER_LOD) * 2.0;

            reflection.rgb      = texture(colortex5, sphereCoord).rgb;
            reflection.a        = 1.0 * skyOcclusion;
        }
        
        vec3 fresnel   = BRDFfresnel(-viewDir, viewNormal, material);
        if (!waterIce) fresnel *= sqr(auxMat.y);
        if (matID == 103) fresnel *= 0.41;
        
        sceneColor.rgb = mix(sceneColor.rgb, reflection.rgb, vec3(reflection.a) * fresnel);

        if (waterIce) {
            vec3 shadowPos  = getShadowmapPos(scenePos0, 0.08);
            float shadow    = texture(shadowtex1, shadowPos);

            #ifdef lightleakWorkaroundToggle
                shadow     *= sstep(sceneLmap.y, 0.1, 0.2);
            #endif

            if (shadow > 1e-2) {
                vec3 directCol  = (sunAngle<0.5 ? lightColor[0] : lightColor[2] * 0.1) * lightFlip;
                sceneColor.rgb += BRDF_Beckmann(-viewDir, lightvecView, viewNormal, material) * directCol;
            }
        }

        #if DEBUG_VIEW == 6
            sceneColor.rgb = reflection.rgb;
        #endif
    }

    #endif


    if (isEyeInWater==1) {
        if (water) {
            #ifdef fogVolumeEnabled
                mat2x3 waterVol   = volumetricFog2(scenePos1, scenePos0, normalize(scenePos0), !landMask(sceneDepth0), vDotL, bluenoise, caveMult);
                applyFogData(sceneColor.rgb, waterVol);
            #endif
        }
        sceneColor.rgb  = blendTranslucencies(sceneColor.rgb, tex3, translucentAlbedo);
        
        #ifdef waterVolumeEnabled
        if (dist0 > waterVolClipDist) sceneColor.rgb = waterFog(sceneColor.rgb, dist0 - waterVolClipDist, fogcol);

        mat2x3 waterVol   = volumetricWater(scenePos0, gbufferModelViewInverse[3].xyz, normalize(scenePos0), !landMask(sceneDepth0), vDotL, bluenoise, caveMult);
        applyFogData(sceneColor.rgb, waterVol);
        #else
        sceneColor.rgb = waterFog(sceneColor.rgb, dist0, fogcol);
        #endif
    }

    if (isEyeInWater == 2) {
        sceneColor.rgb  = blendTranslucencies(sceneColor.rgb, tex3, translucentAlbedo);
        sceneColor.rgb = lavaFog(sceneColor.rgb, dist0);
    }

    if (isEyeInWater==0) {
        #ifdef fogVolumeEnabled
        mat2x3 fogData  = volumetricFog(scenePos0, normalize(scenePos0), !landMask(sceneDepth0), vDotL, bluenoise, caveMult);

        applyFogData(sceneColor.rgb, fogData);
        #else
        if (landMask(sceneDepth0)) sceneColor.rgb  = simpleFog(sceneColor.rgb, length(viewPos0), caveMult);
        #endif
    }
    

    #ifdef rainbowsEnabled
        float dist      = length(scenePos0);
        float falloff   = landMask(sceneDepth0) ? sstep(dist, 128.0, 192.0) : 1.0;
        float fade      = saturate((wetness - rainStrength) * 1.33 - 0.02);

        if (falloff > 0.0 && fade > 0.0) {
            sceneColor.rgb  += generateRainbow(normalize(viewPos0), falloff) * fade;
        }
    #endif

    #ifdef compNAN
    if (isnan3(sceneColor.rgb) || isinf3(sceneColor.rgb)) sceneColor.g = 100.0;
    #endif

    //sceneColor.rgb = vec3(float(decodeMatID16(tex6.z) == 3) * tau);

    //sceneColor.rgb = sqr(sceneNormal * 0.5 + 0.5);

    scene  = makeDrawbuffer(sceneColor);
    sceneData = tex1;

    rainData = vec4(auxMat.z, 0.0, 0.0, 1.0);
}