/********************************************************
    © 2020 Continuum Graphics LLC. All Rights Reserved
 ********************************************************/

#include "/../ContinuumLib/Syntax.glsl"

#define Composite2_glsl 32
#define DynamicShaderStage Composite2_glsl


flat varying vec3 lightVector;
flat varying vec3 wLightVector;

flat varying vec3 sunPositionNorm;
flat varying vec3 moonPositionNorm;
flat varying vec3 wSunVector;
flat varying vec3 wMoonVector;

varying vec3 skyIlluminanceVert;
varying vec3 sunIlluminanceVert;

varying vec3 skyIlluminanceClouds;
varying vec3 sunIlluminanceClouds;

varying vec4 shR;
varying vec4 shG;
varying vec4 shB;

varying vec2 texcoord;

flat varying float timeSunrise;

flat varying int isNight;


/***********************************************************************/
#if defined vsh

uniform sampler3D colortex7;

uniform mat4 gbufferProjection;
uniform mat4 gbufferProjectionInverse;
uniform mat4 gbufferModelView;
uniform mat4 gbufferModelViewInverse;

uniform mat4 shadowModelView;
uniform mat4 shadowModelViewInverse;

uniform vec3 sunPosition;
uniform vec3 shadowLightAngles;

uniform float fov;
uniform float eyeAltitude;
uniform float wetness;

uniform vec3 cameraPosition;

uniform int worldTime;

#include "/../ContinuumLib/Debug.glsl"

#include "/../ContinuumLib/Utilities.glsl"

#include "/../ContinuumLib/Uniform/ShadowMatrices.glsl"
#include "/../ContinuumLib/Uniform/ProjectionMatrices.glsl"

#include "/../ContinuumLib/Utilities/SphericalHarmonics.glsl"

#include "/../ContinuumLib/Common/PrecomputedSky.glsl"

void CalculateSH(out vec4 shR, out vec4 shG, out vec4 shB) {
    shR = shZero();
    shG = shZero();
    shB = shZero();

    vec3 kCamera = vec3(0.0, 0.05 + cameraPosition.y / 1000.0 + ATMOSPHERE.bottom_radius, 0.0);

    const float axisSampleCount = 8.0;
    
    // Accumulate coefficients according to surounding direction/color tuples.
    for (float az = 0.5; az < axisSampleCount; az += 1.0) {
        for (float ze = 0.5; ze < axisSampleCount; ze += 1.0) {
            vec3 rayDir = shGetUniformSphereSample(az / axisSampleCount, ze / axisSampleCount);

            vec3 transmit = vec3(0.0);
            vec3 SkyRadianceMoon = vec3(0.0);
            vec3 color = GetSkyRadiance(ATMOSPHERE, colortex7, colortex7, colortex7, kCamera, rayDir, 0.0, wSunVector, transmit, SkyRadianceMoon) * SKY_SPECTRAL_RADIANCE_TO_LUMINANCE;
            SkyRadianceMoon = SKY_SPECTRAL_RADIANCE_TO_LUMINANCE_MOON * SkyRadianceMoon;
            color += SkyRadianceMoon;

            vec4 sh = shEvaluateCosineLobe(rayDir);
            shR = shAdd(shR, shScale(sh, color.r));
            shG = shAdd(shG, shScale(sh, color.g));
            shB = shAdd(shB, shScale(sh, color.b));
        }
    }
    
    // integrating over a sphere so each sample has a weight of 4*PI/samplecount (uniform solid angle, for each sample)
    float shFactor = 4.0 * PI / (axisSampleCount * axisSampleCount);
    shR = shScale(shR, shFactor );
    shG = shScale(shG, shFactor );
    shB = shScale(shB, shFactor );

}

void main() {
    texcoord = gl_Vertex.xy;

    #if defined WORLD0
    sunPositionNorm = sunPosition * 0.01;
    moonPositionNorm = -sunPosition * 0.01;
    wSunVector = mat3(gbufferModelViewInverse) * sunPositionNorm;
    wMoonVector = mat3(gbufferModelViewInverse) * moonPositionNorm;

    lightVector = (worldTime > 23075 || worldTime < 12925 ? sunPositionNorm : moonPositionNorm);
    wLightVector = mat3(gbufferModelViewInverse) * lightVector;

    isNight = int(lightVector == moonPositionNorm);
    
    vec3 moon_irradiance = vec3(0.0);
    vec3 sky_irradiance_moon = vec3(0.0);

    sunIlluminanceClouds = SUN_SPECTRAL_RADIANCE_TO_LUMINANCE * GetSunAndSkyIrradiance(ATMOSPHERE, colortex7, colortex7, vec3(0.0, volumetric_cloudHeight / 1000.0 + ATMOSPHERE.bottom_radius, 0.0), vec3(0.0, 1.0, 0.0), wSunVector, wMoonVector, skyIlluminanceClouds, sky_irradiance_moon, moon_irradiance);
	moon_irradiance = MOON_SPECTRAL_RADIANCE_TO_LUMINANCE * moon_irradiance;
    sunIlluminanceClouds += moon_irradiance;

    skyIlluminanceClouds *= SKY_SPECTRAL_RADIANCE_TO_LUMINANCE;
    sky_irradiance_moon *= SKY_SPECTRAL_RADIANCE_TO_LUMINANCE_MOON;
    skyIlluminanceClouds += sky_irradiance_moon;

    sunIlluminanceVert = SUN_SPECTRAL_RADIANCE_TO_LUMINANCE * GetSunAndSkyIrradiance(ATMOSPHERE, colortex7, colortex7, vec3(0.0, volumetric_cloudHeight / 1000.0 + ATMOSPHERE.bottom_radius, 0.0), vec3(0.0, 1.0, 0.0), wSunVector, wMoonVector, skyIlluminanceVert, sky_irradiance_moon, moon_irradiance);
	moon_irradiance = MOON_SPECTRAL_RADIANCE_TO_LUMINANCE * moon_irradiance;
    sunIlluminanceVert += moon_irradiance;

    skyIlluminanceVert *= SKY_SPECTRAL_RADIANCE_TO_LUMINANCE;
    sky_irradiance_moon *= SKY_SPECTRAL_RADIANCE_TO_LUMINANCE_MOON;
    skyIlluminanceVert += sky_irradiance_moon;

    float localTime = float(worldTime);
    timeSunrise = clamp01((localTime - 22500.0) / 600.0) + (1.0 - clamp01((localTime - 200.0) / 2000.0));

    CalculateSH(shR, shG, shB);
    #endif
    
    CalculateProjectionMatrices();
    CalculateShadowMatrices();

    gl_Position = ftransform();
}

#endif
/***********************************************************************/



/***********************************************************************/
#if defined fsh

#include "/../ContinuumLib/Debug.glsl"

uniform sampler2D colortex0;
uniform sampler2D colortex1; //Translucent albedo
uniform sampler2D colortex2; //Translucent data
uniform sampler2D colortex3; //Skysphere

uniform sampler3D colortex7;

uniform sampler2D noisetex;

uniform sampler2D depthtex0;
uniform sampler2D depthtex1;

uniform sampler2D shadowtex0;
uniform sampler2D shadowtex1;
uniform sampler2D shadowcolor;
uniform sampler2D shadowcolor1;

uniform mat4 gbufferModelView;
uniform mat4 gbufferModelViewInverse;
uniform mat4 gbufferProjection;
uniform mat4 gbufferProjectionInverse;

uniform mat4 shadowModelView;
uniform mat4 shadowProjection;
uniform mat4 shadowModelViewInverse;
uniform mat4 shadowProjectionInverse;

uniform vec3 cameraPosition;

uniform vec2 viewDimensions;
uniform vec2 pixelSize;
uniform vec2 taaJitter;

uniform float frameTimeCounter;
uniform float eyeAltitude;
uniform float wetness;
uniform float fov;

uniform float near;
uniform float far;

uniform int isEyeInWater;
uniform int frameCounter;

uniform ivec2 eyeBrightnessSmooth;

//uniform float aspectRatio;

#include "/ShaderConstants.glsl"

/* DRAWBUFFERS:0 */
layout (location = 0) out vec4 Buffer0;
#define LAYOUT_0 Buffer0
#include "/../ContinuumLib/Exit.glsl"

#include "/../ContinuumLib/Utilities.glsl"

#include "/../ContinuumLib/Uniform/ShadowMatrices.glsl"
#include "/../ContinuumLib/Uniform/ProjectionMatrices.glsl"

/*******************************************************************************
 - Sample Functions
******************************************************************************/

float GetDepth(vec2 coord) {
    return texture(depthtex0, coord).x;
}

/*******************************************************************************
 - Space Conversions
******************************************************************************/

vec3 CalculateViewSpacePosition(vec3 screenPos) {
    #ifdef TAA
        screenPos -= vec3(taaJitter, 0.0) * 0.5;
    #endif
    screenPos = screenPos * 2.0 - 1.0;

    return projMAD(projInverseMatrix, screenPos) / (screenPos.z * projInverseMatrix[2].w + projInverseMatrix[3].w);
}

vec3 CalculateViewSpacePosition(vec2 coord) {
    #ifdef TAA
        coord -= taaJitter * 0.5;
    #endif
    vec3 screenPos = vec3(coord, GetDepth(coord)) * 2.0 - 1.0;

    return projMAD(projInverseMatrix, screenPos) / (screenPos.z * projInverseMatrix[2].w + projInverseMatrix[3].w);
}

vec3 CalculateViewSpacePosition(vec2 coord, float depth) {
    #ifdef TAA
        coord -= taaJitter * 0.5;
    #endif
    vec3 screenPos = vec3(coord, depth) * 2.0 - 1.0;

    return projMAD(projInverseMatrix, screenPos) / (screenPos.z * projInverseMatrix[2].w + projInverseMatrix[3].w);
}

vec3 CalculateWorldSpacePosition(vec3 viewPos) {
    return mat3(gbufferModelViewInverse) * viewPos + gbufferModelViewInverse[3].xyz;
}

float ScreenToViewSpaceDepth(float p) {
    p = p * 2.0 - 1.0;
    vec2 x = projInverseMatrix[2].zw * p + projInverseMatrix[3].zw;

    return x.x / x.y;
}

vec3 ViewSpaceToScreenSpace(vec3 viewPos) {
    return ((projMAD(projMatrix, viewPos) / -viewPos.z)) * 0.5 + 0.5;
}

/*******************************************************************************
 - Includes
******************************************************************************/

#include "/../ContinuumLib/Common/PrecomputedSky.glsl"

#include "/../InternalLib/Fragment/VolumetricClouds.fsh"
#include "/../InternalLib/Fragment/PlanarClouds.fsh"

#include "/../InternalLib/Fragment/DiffuseLighting.fsh"
#include "/../InternalLib/Fragment/WaterVolume.fsh"

#include "/../InternalLib/Fragment/VolumetricLighting.fsh"
#include "/../InternalLib/Fragment/ScreenspaceRaytracer.fsh"

#include "/../ContinuumLib/Common/MatID.glsl"

float CalculateSkyVolumeOcclusion(float skyLightmap){
    float occlusion = isEyeInWater == 1 ? eyeBrightnessSmooth.y * (1.0 / 255.0) : skyLightmap;

    return occlusion * occlusion;
}

vec2 sphereToCart(vec3 dir) {
    vec2 lonlat = vec2(atan(-dir.x, dir.z), acos(dir.y));
    return lonlat * vec2(rTAU, rPI) + vec2(0.5, 0.0);
}

vec3 CalculateSunLimbDarkening(vec3 SkyRadiance, vec3 viewVector, vec3 transmit) {
    const vec3 u = vec3(1.0, 1.0, 1.0);
    const vec3 a = vec3(0.397, 0.503, 0.652);

    float VoL = dot(viewVector, sunPositionNorm);
    vec2 sunSize = vec2(tan(ATMOSPHERE.sun_angular_radius), cos(ATMOSPHERE.sun_angular_radius));

    if (VoL > sunSize.y) {
        float centerToEdge = clamp01((1.0 - clamp01(VoL)) / (1.0 - sunSize.y));
        float mu = sqrt(1.0 - centerToEdge * centerToEdge);
        
        vec3 factor = 1.0 - u * (1.0 - pow(vec3(mu) , a));

        SkyRadiance = SkyRadiance + transmit * GetSolarRadiance() * factor;
    }

    return SkyRadiance;
}

vec3 CalculateMoon(vec3 SkyRadiance, vec3 viewVector, vec3 transmit) {
    float VoL = dot(viewVector, -sunPositionNorm);
    float moonAngle = cos(ATMOSPHERE.moon_angular_radius);

    if (VoL > moonAngle) {
        vec3 lunarRadiance = GetLunarRadiance();
        float centerToEdge = (1.0 - clamp01(VoL)) / (1.0 - moonAngle);
        float mu = sqrt(1.0 - centerToEdge * centerToEdge);

        SkyRadiance += lunarRadiance * transmit * mu;
    }
    

    return SkyRadiance;
}

vec2 hammersley(uint i, float numSamples) {
    uint bits = i;
    bits = (bits << 16) | (bits >> 16);
    bits = ((bits & 0x55555555) << 1) | ((bits & 0xAAAAAAAA) >> 1);
    bits = ((bits & 0x33333333) << 2) | ((bits & 0xCCCCCCCC) >> 2);
    bits = ((bits & 0x0F0F0F0F) << 4) | ((bits & 0xF0F0F0F0) >> 4);
    bits = ((bits & 0x00FF00FF) << 8) | ((bits & 0xFF00FF00) >> 8);

    return vec2(i / numSamples, bits * exp2(-32));
}

vec3 sampleGGXVNDF(vec3 Ve, float alpha, vec2 Xi) {
    // Section 3.2: transforming the view direction to the hemisphere configuration
    vec3 Vh = normalize(vec3(alpha * Ve.x, alpha * Ve.y, Ve.z));

    // Section 4.1: orthonormal basis (with special case if cross product is zero)
    float lensq = Vh.x * Vh.x + Vh.y * Vh.y;
    vec3 T1 = lensq > 0.0 ? vec3(-Vh.y, Vh.x, 0.0) * inversesqrt(lensq) : vec3(1.0, 0.0, 0.0);
    vec3 T2 = cross(Vh, T1);

    // Section 4.2: parameterization of the projected area
    float r = sqrt(Xi.y);
    float phi = TAU * Xi.x;

    float s = 0.5 * (1.0 + Vh.z);

    float t1 = r * cos(phi);
    float t2 = r * sin(phi);
        t2 = (1.0 - s) * sqrt(1.0 - t1 * t1) + s * t2;

    // Section 4.3: reprojection onto hemisphere
    vec3 Nh = t1 * T1 + t2 * T2 + sqrt(max0(1.0 - t1 * t1 - t2 * t2)) * Vh;

    // Section 3.4: transforming the normal back to the ellipsoid configuration
    vec3 Ne = normalize(vec3(alpha * Nh.x, alpha * Nh.y, max0(Nh.z)));

    return Ne;
}

vec3 CalculateSpecularReflections(vec3 albedo, vec3 position, vec3 wViewVector, vec3 wNormal, float depth, float roughness, float f0, mat2x3 metalIOR, float dither, float skyLightmap, bool isMetal) {
    if (roughness == 1.0) return vec3(0.0);

    float alpha = roughness * roughness;
    float alpha2 = alpha * alpha;

    vec3 V = -wViewVector;
    vec3 N = wNormal;

    vec3 specular = vec3(0.0);

    const int samples = SPECULAR_QUALITY;

    mat3 tangentToWorld;
    tangentToWorld[0] = normalize(cross(gbufferModelView[1].xyz, N));
    tangentToWorld[1] = cross(N, tangentToWorld[0]);
    tangentToWorld[2] = N;

    float NoV = max(1e-12, dot(N, V));

    float G1V = G1Smith(alpha2, NoV);

    float skyLightmap2 = clamp01(skyLightmap * 1.05);
          skyLightmap2 = skyLightmap2 * skyLightmap2;

    vec3 clipSpacePosition = vec3(texcoord, depth);

    for(int i = 0; i < samples; ++i) {
        vec2 Xi = hammersley(uint(i + 1), float(samples));
        vec3 H = tangentToWorld * sampleGGXVNDF(V * tangentToWorld, alpha, fract(Xi + vec2(dither, 0.0)));
        vec3 L = -reflect(V, H);

        float NoL = max(1e-12, dot(N, L));

        float VoH = max0(dot(V, H));

        float G2Smith = G2Smith(alpha2, NoL, NoV);
        vec3 F = FMaster(f0, VoH, metalIOR, albedo);

        /*
        vec2 hitPixel;
        vec3 csHitPoint;
        float stepCount;

        bool wasHit = traceScreenSpaceRay(
                    position,
                    mat3(gbufferModelView) * L,
                    dither,
                    hitPixel,
                    csHitPoint);
        

        if(wasHit) {
            specular += DecodeRGBE8(texture(colortex0, hitPixel)) * (F * G2Smith / G1V) * clamp01(sign(NoL * NoV));
        } else {
            specular += DecodeRGBE8(texture(colortex3, sphereToCart(L) * 0.5)) * (F * G2Smith / G1V) * clamp01(sign(NoL * NoV)) * skyLightmap2; // G1V handled in importance sample
        }
        */

        vec3 reflection = rayTrace(
                    position, 
                    mat3(gbufferModelView) * L,
                    NoV,
                    dither, 
                    clipSpacePosition,
                    DecodeRGBE8(texture(colortex3, sphereToCart(L) * 0.5)), 
                    skyLightmap2);

        specular += reflection * (F * G2Smith / G1V) * clamp01(sign(NoL * NoV));
    }

    specular /= samples;

    if(isMetal) specular *= albedo;

    return max0(specular);
}

vec4 SampleClouds(vec2 coord){
    vec2 noise = texture(noisetex, texcoord * 0.25 * 64.0).xy * 2.0 - 1.0;

    coord += noise * pixelSize;

    vec2 minCoord = vec2(0.0, 0.5) + pixelSize * 2.0;
    vec2 maxCoord = vec2(0.5, 1.0) - pixelSize;
    coord = clamp(coord, minCoord, maxCoord);

    vec4 clouds = texture(colortex3, coord);

    return clouds * vec2(100.0, 1.0).xxxy;
}

#define VF_MULTISCAT_A 0.5
#define VF_MULTISCAT_B 0.5
#define VF_MULTISCAT_C 0.5

#define VF_MULTISCAT_QUALITY 8

float CalculateFogPhase(float vDotL){
    const float mixer = 0.5;

    float g1 = PhaseG(vDotL, 0.6);
    float g2 = PhaseG(vDotL, -0.5);

    return mix(g2, g1, mixer);
}

float CalculateMorningFogDepth(vec3 rayPosition){
    float k = abs(rayPosition.y - VOLUMETRIC_FOG_HEIGHT) / VOLUMETRIC_FOG_FALLOFF;

    float fogDis = exp2(-pow(k, 0.75));

    vec3 fogCloudPos = rayPosition * 0.025;

    #ifdef VOLUMETRIC_FOG_NOISE
        float fogClouds = texture(noisetex, (fogCloudPos.xz * vec2(1.0, 0.4) - TIME * 0.1) * (1.0 / 64.0)).x;
            fogClouds = fogClouds * fogClouds * (3.0 - 2.0 * fogClouds);
            fogClouds += Calculate3DNoise(fogCloudPos * 4.0 - TIME * 0.1) * (1.0 / 2.0);
            fogClouds += Calculate3DNoise(fogCloudPos * 8.0 - TIME * 0.1) * (1.0 / 4.0);
            fogClouds += Calculate3DNoise(fogCloudPos * 16.0 - TIME * 0.1) * (1.0 / 4.0);
    #else
        float fogClouds = 1.0;
    #endif
    
    fogClouds = (fogClouds * 0.5 + 0.5) * timeSunrise * fogDis;

    #ifdef VOLUMETRIC_FOG_NOISE
        fogClouds = clamp01(fogClouds * 2.0 - 1.0);
    #endif

    return fogClouds * VOLUMETRIC_FOG_DENSITY * timeSunrise;
}

float CalculateMorningFogDepth(vec3 rayPosition, vec3 direction, const int steps){
    float stepLength = 2.0;

    float totalDepth = 0.0;

    for (int i = 0; i < steps; ++i, rayPosition += direction * stepLength){
        totalDepth += CalculateMorningFogDepth(rayPosition) * stepLength;

        stepLength *= 1.5;
    }

    return totalDepth;
}

void IntegrateFogScattering(vec3 rayPosition, vec3 shadowRayPosition, float stepTransmittance, float integral, float shadow, float sunDepth, float phase, float powder, float an, float bn, inout float transmittance, inout float directScattering, inout float indirectScattering){
    float directShadowing = shadow * exp2(-sunDepth * rLOG2 * bn);

    directScattering += integral * transmittance * an * directShadowing * phase * powder;
    indirectScattering += integral * transmittance * an;
}

void IntegrateFogScattering(vec3 rayPosition, vec3 shadowRayPosition, float stepTransmittance, float stepDepth, float VoL, float phases[VF_MULTISCAT_QUALITY], inout float transmittance, inout float directScattering, inout float indirectScattering){
    float integral = 1.0 - stepTransmittance;
    float sunDepth = CalculateMorningFogDepth(rayPosition, wLightVector, 10);
    float shadow = CalculateVolumeInShadow(shadowRayPosition);

    float powderLight  = CalculatePowderEffect(sunDepth);
    float powderView = CalculatePowderEffect(stepDepth);

    float powder = CalculatePowderEffect(powderLight, VoL) *
                   CalculatePowderEffect(powderView, VoL);

    float an = 1.0, bn = 1.0;

    for (int i = 0; i < VF_MULTISCAT_QUALITY; ++i){
        float phase = phases[i];

        IntegrateFogScattering(rayPosition, shadowRayPosition, stepTransmittance, integral, shadow, sunDepth, phase, powder, an, bn, transmittance, directScattering, indirectScattering);

        an *= VF_MULTISCAT_A;
        bn *= VF_MULTISCAT_B;
    }

    transmittance *= stepTransmittance;
}

void CalculateMultipleScatteringFogPhases(float vDotL, inout float phases[VF_MULTISCAT_QUALITY]){
    float cn = 1.0;

    for (int i = 0; i < VF_MULTISCAT_QUALITY; ++i){
        phases[i] = CalculateFogPhase(vDotL * cn);

        cn *= VF_MULTISCAT_C;
    }
}

vec3 CalculateMorningFog(vec3 backGroundCol, vec3 start, vec3 end, float VoL, float dither){
    #ifndef VOLUMETRIC_FOG
        return backGroundCol;
    #endif

    if (timeSunrise <= 0.0) return backGroundCol;
    
    const int steps = VOLUMETRIC_FOG_QUALITY;
    
    vec3 increment = (end - start) / steps;
    vec3 rayPosition = increment * dither + start + cameraPosition;

    vec3 shadowStart = WorldSpaceToShadowSpace(start);
    vec3 shadowEnd = WorldSpaceToShadowSpace(end);

    vec3 shadowIncrement = (shadowEnd - shadowStart) / steps;
    vec3 shadowRayPosition = shadowIncrement * dither + shadowStart;

    float rayLength = length(increment);

    float transmittance = 1.0;
    
    float directScattering = 0.0;
    float indirectScattering = 0.0;

    float[VF_MULTISCAT_QUALITY] phases;
    CalculateMultipleScatteringFogPhases(VoL, phases);

    float depthCompensation = sqrt(steps / (rayLength * 1.73205080757)); // 1.0 / sqrt(3) for alignment

    for (int i = 0; i < steps; ++i, rayPosition += increment, shadowRayPosition += shadowIncrement){
        if (transmittance < 0.001) {transmittance = 0.0; break;}

        float stepDepth = CalculateMorningFogDepth(rayPosition) * rayLength;
        if (stepDepth <= 0.0) continue;

        float stepTransmittance = exp2(-stepDepth * rLOG2);
        
        IntegrateFogScattering(rayPosition, shadowRayPosition, stepTransmittance, stepDepth * depthCompensation, VoL, phases, transmittance, directScattering, indirectScattering);
    }

    float skyOcclude = eyeBrightnessSmooth.y * (1.0 / 255.0);
          skyOcclude = skyOcclude * skyOcclude;

    vec3 totalScatteringSun = sunIlluminanceVert * directScattering;
    vec3 totalScatteringSky = skyIlluminanceVert * 0.25 * rPI * indirectScattering * skyOcclude;

    vec3 scattering = totalScatteringSun + totalScatteringSky;

    return backGroundCol * transmittance + scattering;
}

vec3 calculateNetherFog(vec3 background, vec3 viewPos) {
    const vec3 scatterColor = vec3(0.02, 0.006, 0.001);

    float od = length(viewPos);

    vec3 transmit = exp(-netherAbsorptionCoeff * od);
    vec3 scatter = 1.0 - transmit;
         scatter *= blackbody(LIGHT_TEMP) * LIGHT_LUM * scatterColor;
         scatter *= 0.25 * rPI;

    return background * transmit + scatter;
}

vec3 calculateEndFog(vec3 background, vec3 viewPos) {
    float od = length(viewPos);

    vec3 transmit = exp(-endAbsorptionCoeff * od);
    vec3 scatter = 1.0 - transmit;
         scatter *= END_AMBIENT_LUM;
         scatter *= 0.25 * rPI;

    return background * transmit + scatter;
}

float calculateAerialPerspective(vec3 worldDir, vec2 planetSphere, vec3 cloudHitPos, float dither) {
    const int steps = 16;
    const float rSteps = 1.0 / steps;

    vec3 startPos = gbufferModelViewInverse[3].xyz;
    
    float endDis = planetSphere.y > 0.0 ? planetSphere.x * 1000.0 : length(cloudHitPos);
    vec3 endPos = worldDir * min(endDis, 64000.0) + startPos;

    vec3 increment = (endPos - startPos) * rSteps;
    vec3 rayPosition = increment * dither + startPos;

    float rayLength = length(increment);
    
    float totDis = 0.0;

    for (int i = 0; i < steps; i++) {
        totDis += CalculateCloudShadows(rayPosition + cameraPosition, wSunVector, 16);

        rayPosition += increment;
    }

    return (-totDis * rSteps + 1.0) * length(endPos) * 0.001;
}

/*******************************************************************************
 - Main
******************************************************************************/

void main() {
    //This file takes in semis and the lit world and optionally blends them, if it doesnt blend them it handles all effects from the front geometry to the camera
    //This file also handles specular.
	
	float depth0 = texture(depthtex0, texcoord).x;
    float depth1 = texture(depthtex1, texcoord).x;

    mat2x3 position0;
    position0[0] = CalculateViewSpacePosition(vec3(texcoord, depth0));
    position0[1] = CalculateWorldSpacePosition(position0[0]);
    
    mat2x3 position1;
    position1[0] = CalculateViewSpacePosition(vec3(texcoord, depth1));
    position1[1] = CalculateWorldSpacePosition(position1[0]);

    float dither  = bayer64(gl_FragCoord.xy);
	float dither_2 = dither;	//FrameCounter helps to resolve noise better for some functions. Mostly for volumetrics and importance sampling.
	
	#ifdef TAA
	    dither  = fract(frameTimeCounter * (1.0 / 7.0) + dither);
		dither_2 = fract(frameCounter * (1.0 / 7.0) + dither_2);
    #endif

    vec3 vDir = normalize(position0[0]);
    vec3 wDir = mat3(gbufferModelViewInverse) * vDir;

    vec3 flatVdir = vDir;   // For stuff on water and translucent surfaces
    vec3 flatWdir = wDir;   // For stuff on water and translucent surfaces

    bool isTransparent = depth1 > depth0;

    vec4 materialData = textureRaw(colortex2, texcoord);
	vec3 worldNormal = DecodeNormal(materialData.x);
    vec3 normal = mat3(gbufferModelView) * worldNormal;

    vec2 coord = texcoord;
    
    #ifdef REFRACTION
        if (isTransparent) {
            vec3 flatNormal = clamp(normalize(cross(dFdx(position0[0]), dFdy(position0[0]))), -1.0, 1.0);
            vec3 rayDirection = refract(vDir, normal - flatNormal, 0.75);
            vec3 refractedPosition = rayDirection * abs(distance(position0[1], position1[1])) + position0[0];
                    refractedPosition = ViewSpaceToScreenSpace(refractedPosition);
                    refractedPosition.z = texture2D(depthtex1, refractedPosition.xy).x;

            if(refractedPosition.z > texture2D(depthtex0, refractedPosition.xy).x) {
                coord = refractedPosition.xy;
                depth1 = refractedPosition.z;

                position1[0] = CalculateViewSpacePosition(refractedPosition);
                position1[1] = CalculateWorldSpacePosition(position1[0]);

                float normFactor = inversesqrt(dot(position1[0], position1[0]));
                vDir = normFactor * position1[0];

                wDir = mat3(gbufferModelViewInverse) * vDir;
            }
        }
    #endif

    vec3 SkyRadiance = vec3(0.0);

    float VoL = dot(vDir, lightVector);

    #if defined WORLD0
	vec3 kCamera = vec3(0.0, 0.05 + cameraPosition.y / 1000.0 + ATMOSPHERE.bottom_radius, 0.0);
	vec3 kPoint  = kCamera + position1[1] / 1000.0;
	
    float airVolumeShadowing = CalculateLitAirVolume(gbufferModelViewInverse[3].xyz, position0[1], dither_2);
	      airVolumeShadowing = (1-airVolumeShadowing) * length(position0[1]) / 1000.0;
    
	/*
        vec2 sphereCoord = sphereToCart(normalize(wDir));

        Buffer3 = texture(colortex3, sphereCoord * 0.5);
        exit();
        return;
    */

    if (depth1 >= 1.0) {
        vec3 transmit = vec3(0.0);
        vec3 SkyRadianceMoon = vec3(0.0);
        SkyRadiance = SKY_SPECTRAL_RADIANCE_TO_LUMINANCE * GetSkyRadiance(ATMOSPHERE, colortex7, colortex7, colortex7, kCamera, wDir, airVolumeShadowing, wSunVector, transmit, SkyRadianceMoon);
		SkyRadianceMoon = SKY_SPECTRAL_RADIANCE_TO_LUMINANCE_MOON * SkyRadianceMoon;
        SkyRadiance += SkyRadianceMoon;

        SkyRadiance = CalculateSunLimbDarkening(SkyRadiance, vDir, transmit);
        SkyRadiance = CalculateMoon(SkyRadiance, vDir, transmit);
        SkyRadiance = calculateStars(SkyRadiance, wDir, wMoonVector, transmit);

        vec2 planetSphere = rsi(kCamera, wDir, ATMOSPHERE.bottom_radius);

        #ifdef PLANET_SURFACE
            if (planetSphere.y > 0.0) {
                vec3 planetSurfacePosition = kCamera + wDir * planetSphere.x;
                vec3 normal = normalize(planetSurfacePosition);

                // Planet Lighting
                vec3 skyIrradiance = vec3(0.0);
                vec3 moon_irradiance = vec3(0.0);
                vec3 sky_irradiance_moon = vec3(0.0);
                vec3 sunIrradiance = GetSunAndSkyIrradiance(ATMOSPHERE, colortex7, colortex7, planetSurfacePosition , normal, wSunVector, wMoonVector, skyIrradiance, sky_irradiance_moon, moon_irradiance) * SUN_SPECTRAL_RADIANCE_TO_LUMINANCE;
                moon_irradiance = MOON_SPECTRAL_RADIANCE_TO_LUMINANCE * moon_irradiance;
                sunIrradiance += moon_irradiance;

                skyIrradiance *= SKY_SPECTRAL_RADIANCE_TO_LUMINANCE;
                sky_irradiance_moon *= SKY_SPECTRAL_RADIANCE_TO_LUMINANCE_MOON;
                skyIrradiance += sky_irradiance_moon;

                const vec3 planetAlbedo = vec3(0.05, 0.1, 0.15);
                const float waterRoughness = 0.2;

                const float alpha = waterRoughness * waterRoughness;
                const float alpha2 = alpha * alpha;

                const float planetf0 = 0.0201;

                float NoL = clamp01(dot(normal, wLightVector));
                vec3 planet = sunIrradiance * sqrt(NoL) + skyIrradiance;
                     planet *= planetAlbedo * rPI;

                vec3 transmit;
                vec3 in_scatter_moon;
                vec3 in_scatter = GetSkyRadianceToPoint(ATMOSPHERE, colortex7, colortex7, colortex7, kCamera, planetSurfacePosition, 0.0, wSunVector, transmit, in_scatter_moon) * SKY_SPECTRAL_RADIANCE_TO_LUMINANCE;
                in_scatter_moon *= SKY_SPECTRAL_RADIANCE_TO_LUMINANCE_MOON;
                in_scatter += in_scatter_moon;
                
                planet = planet * transmit + in_scatter;
                
                SkyRadiance = planet;
            }
        #endif
        
        #ifdef PLANAR_CLOUDS
            SkyRadiance = calculatePlanarClouds(SkyRadiance, wDir, wLightVector, planetSphere, dither_2, VoL, ATMOSPHERE.bottom_radius * 1000.0);
        #endif
        
        float cloudTransmit = 1.0;
		vec3 cloud = vec3(0.0);

        //vec4 clouds = SampleClouds((texcoord + vec2(0.0, 1.0)) * 0.5);
		
		calculateVolumetricClouds(cloud, cloudTransmit, wDir, wLightVector, position1[1], planetSphere, dither_2, VoL, ATMOSPHERE.bottom_radius * 1000.0, VC_QUALITY, VC_SUNLIGHT_QUALITY, 1);
		SkyRadiance = SkyRadiance * cloudTransmit + cloud;
        SkyRadiance = CalculateMorningFog(SkyRadiance, gbufferModelViewInverse[3].xyz, wDir * far * 1.2, VoL, dither_2);
        
        if(depth0 >= 1.0 && isEyeInWater == 0) {
            Buffer0 = EncodeRGBE8(max0(SkyRadiance));

            exit();
            return;
        }
        
    }
    #endif

    vec3 sample1 = depth1 >= 1.0 ? SkyRadiance : DecodeRGBE8(texture(colortex0, coord));
    vec4 sample0 = vec4(0.0);
	
    vec2 decode2y = Decode2x8(materialData.y);
    vec2 decode2z = Decode2x8(materialData.z);

    mat2x3 metalIOR = mat2x3(0.0);

    float roughness, reflectivity, skyLightmap, torchLightmap, materialID;
    roughness = decode2y.x; torchLightmap = decode2z.x; skyLightmap = decode2z.y;
    UnpackMaterialID(decode2y.y, materialID, reflectivity, metalIOR);

    vec3 metalAlbedo = sample1;

    bool isWater = materialID > 4.5 && materialID < 5.5;
    bool isMetal = metalIOR[0].x > 0.0 || reflectivity >= 1.0;
    bool isHand    = materialID == 12.0;

    vec3 waterTransmit = vec3(1.0);
    vec3 waterScattering = vec3(0.0);

    #if defined WORLD0 || defined WORLD1
    if (isWater || isEyeInWater == 1) {
        float skylightOcclusion = CalculateSkyVolumeOcclusion(skyLightmap);
        sample1.rgb = CalculateWaterVolume(sample1.rgb, isEyeInWater == 0 ? position0[1] : gbufferModelViewInverse[3].xyz, isEyeInWater == 0 ? position1[1] : position0[1], dither_2, VoL, skylightOcclusion, waterScattering, waterTransmit);
    }
    #endif

    // Single layered diffuse lighting on transparents
    if (isTransparent || isMetal) {
        sample0 = texture(colortex1, isWater ? coord : texcoord);
        sample0.rgb = srgbToLinear(sample0.rgb);
        sample0.rgb = dot(sample0.rgb, sample0.rgb) <= 0.0 ? vec3(1.0) : sample0.rgb;
        sample0.a = pow3(sample0.a);
        
        vec3 shadows;
        vec3 scatteredLit = CalculateDiffuseLighting(position0, vec3(0.0), sample0.rgb, normal, worldNormal, -flatVdir, depth1, roughness, reflectivity, metalIOR, vec2(torchLightmap, skyLightmap), dither, 1.0, false, false, false, shadows);
    
        vec3 F = FMaster(reflectivity, clamp01(dot(normal, -flatVdir)), metalIOR, metalAlbedo);

        //Blend translucent albedo with color
        if (isTransparent) {
            sample1 -= waterScattering;
            sample1 = sample1 * (1.0 - F) * (sample0.rgb * (1.0 - sample0.a)) + scatteredLit * sample0.a;
            sample1 = isEyeInWater == 1 ? sample1 * waterTransmit + waterScattering : sample1 + waterScattering * (1.0 - F);
        }

        sample1 *= float(!isMetal);

        sample1 += CalculateSpecularHighlight(isTransparent ? sample0.rgb : metalAlbedo, lightVector, normal, -flatVdir, shadows, roughness, reflectivity, metalIOR) * (isWater ? vec3(1.0) : waterTransmit);
    }

    // Aerial Perspective
    /*
    vec3 transmit;
    vec3 in_scatter = GetSkyRadianceToPoint(ATMOSPHERE, colortex7, colortex7, colortex7, kCamera, kPoint, airVolumeShadowing, wSunVector, transmit) * SKY_SPECTRAL_RADIANCE_TO_LUMINANCE;
	
    sample1 = sample1 * transmit + in_scatter;
    */
    
    float eyeInWaterFloat = float(isEyeInWater == 1);
    sample1 -= waterScattering * eyeInWaterFloat;
    sample1 = isMetal ? max0(sample1) : sample1;
    sample1 += CalculateSpecularReflections(metalAlbedo, position0[0], flatWdir, worldNormal, isHand ? 0.7 : depth0, roughness, reflectivity, metalIOR, dither_2, skyLightmap * float(isEyeInWater == 0), isMetal) * mix(vec3(1.0), waterTransmit, eyeInWaterFloat);
    sample1 += waterScattering * eyeInWaterFloat;
    
    #if defined WORLD0
    if (isEyeInWater == 0) {
        sample1 = CalculateMorningFog(sample1, gbufferModelViewInverse[3].xyz, position0[1], VoL, dither_2);
    }
    #endif

    #if defined WORLD_1
        sample1 = calculateNetherFog(sample1, position0[0]);
    #endif

    #if defined WORLD1
        sample1 = calculateEndFog(sample1, position0[0]);
    #endif

    /*
    vec2 hitPixel;
    vec3 csHitPoint;
    float stepCount;

    bool wasHit = traceScreenSpaceRay(
                position0[0],
                reflect(vDir, normal),
                dither,
                hitPixel,
                csHitPoint);

    vec2 tsPResult = hitPixel;

    sample1 = DecodeRGBE8(texture(colortex0, tsPResult) * float(wasHit));
    */
    
    sample1 = any(isinf(sample1)) ? vec3(0.0) : sample1;
    sample1 = any(isnan(sample1)) ? vec3(0.0) : sample1;
    
    Buffer0 = EncodeRGBE8(max0(sample1));
	exit();
}

#endif
/***********************************************************************/
