// Copyright 2010 Sundog Software, LLC. All rights reserved worldwide.

// The following parameters may be adjusted to tweak appearance:

// Maximum number of slices to render through the cloud volume.
// Increase this to reducing sampling artifacts. Decrease it
// to increase performance.
#define MAX_SAMPLES 150

// The rate at which detail is added to the clouds with distance
// from the camera. This is needed to counteract sampling artifacts
// from high frequency detail near the camera.
#define DETAIL_FALLOFF 2.0

// Number of samples to take for lighting each slice. Increase for
// more lighting accuracy, decrease for more performance. Only used
// if SMOOTH_LIGHTING is not defined
#define LIGHTING_DEPTH 3

// Uncomment to enable Reinhard tone mapping - tends to make things too 
// transparent though
//#define TONE_MAP

// Smooth lighting approximates the cloud's lighting by just looking
// at the sample's position within the cloud layer relative to the 
// light source. It's faster and produces less detail in the lighting
// which actually looks better.
#define SMOOTH_LIGHTING

// Controls how light linearly falls off as a function of depth into
// the cloud layer
#define SMOOTH_LIGHTING_BRIGHTNESS 2.0

// The frequency of the noise used to add detail
#define NOISE_FREQUENCY 2500

// Enable or disable gamma correction
#define GAMMA_CORRECTION

// Gamma value
#define GAMMA 2.2

// Exposure
#define EXPOSURE 1.5

// Opacity boost
#define OPACITY 1.0

// Opacity threshold at which we do early ray termination
#define OPACITY_THRESHOLD 0.9

// Epsilon value at which we discard the fragment
#define EPSILON 0.001

// Whether to disable tone mapping and gamma correction
//#define HDR

uniform sampler3D tex3D;
uniform sampler3D tex3D2;
uniform vec4 lightColor;
uniform vec4 lightObjectDirAndConstTerm;
uniform vec4 lightTexCoords;
uniform vec4 viewSampleDimensions;
uniform vec4 lightSampleDimensions;
uniform vec4 voxelDimensions;
uniform vec4 lightWorldDirAndExtinction;
uniform vec4 cameraTexCoords;
uniform vec4 skyLightColor;
uniform vec4 originTexCoords;
uniform vec4 fadeFlag;
uniform vec4 fogColorAndDensity;
uniform vec4 multipleScatteringTerm;
uniform vec4 noiseOffset;
uniform vec4 extinctionCoefficient;
uniform vec4 jitter;
uniform vec4 unitScale;

varying float eyeDepth;

void getVolumeIntersection(in vec3 pos, in vec3 dir, out float tNear, out float tFar)
{
    // Intersect the ray with each plane of the box
    vec3 invDir = 1.0 / dir;
    vec3 tBottom = -pos * invDir;
    vec3 tTop = (1.0 - pos) * invDir;

    // Find min and max intersections along each axis
    vec3 tMin = min(tTop, tBottom);
    vec3 tMax = max(tTop, tBottom);

    // Find largest min and smallest max
    vec2 t0 = max(tMin.xx, tMin.yz);
    tNear = max(t0.x, t0.y);
    t0 = min(tMax.xx, tMax.yz);
    tFar = min(t0.x, t0.y);

    // Clamp negative intersections to 0
    tNear = max(0.0, tNear);
    tFar = max(0.0, tFar);
}

float henyeyGreenstein(in float cosa, in float g)
{
    float g2 = g * g;

    return (1.0 - g2) / pow((1.0 + g2 - 2.0 * g * cosa), 1.5);
}

float phaseFunction(in float cosa)
{
    // Although a double-lobed Henyey-Greenstein phase function is the closest thing to
    // Mie scattering physically, Rayleigh scattering just looks better and it's faster.

    // Rayleigh function
    return 0.75 * (1.0 + cosa * cosa);

    // Double HG:
    //return mix(henyeyGreenstein(cosa, -0.75), henyeyGreenstein(cosa, 0.9), 0.95);

    // Single HG:
    //return henyeyGreenstein(cosa, 0.875);
}

void toneMap(inout vec4 c)
{
    c *= vec4(EXPOSURE, EXPOSURE, EXPOSURE, OPACITY);

    // Simplified Reinhard tone mapping operator
#ifdef TONE_MAP
    c = (c / (c + 1.0));
#endif

#ifdef GAMMA_CORRECTION
    const float gamma = 1.0 / GAMMA;
    c = pow(c, vec4(gamma, gamma, gamma, gamma));
#endif

	c = clamp(c, 0.0, 1.0);
}

float computeFade(in vec3 posWithinVolume)
{
    if (fadeFlag.x > 0)
    {
        float distFromCenter = length(posWithinVolume.xz - vec2(0.5, 0.5)) * 2.0;
        return 1.0 - exp(-1.0 * fadeFlag.z * distFromCenter * distFromCenter * distFromCenter);
    }
    else
    {
        return 0.0;
    }
}

void computeFog(inout vec4 c, in float depth)
{
    float fogExponent = fogColorAndDensity.w * depth;
    float f = exp(-abs(fogExponent));
    f = clamp(f, 0.0, 1.0);
    c.xyz = mix(fogColorAndDensity.xyz, c.xyz, f);
}

float getCloudDensity(in vec3 texCoord, in float t)
{	
	vec3 perturb = vec3(0,0,0);
	vec3 uvw = ((texCoord + noiseOffset.xyz) / viewSampleDimensions.xyz) / (NOISE_FREQUENCY * unitScale.x);
	perturb += 1.0    * texture3D(tex3D2,  2.0 * uvw).xyz - 0.5;
	perturb += 0.5    * texture3D(tex3D2,  4.0 * uvw).xyz - 0.25;
			
	return texture3D(tex3D, texCoord + perturb * jitter.xyz * t).x;
}

void main()
{
    vec3 texCoord = gl_TexCoord[0].xyz;
	
    vec3 view = texCoord - cameraTexCoords.xyz;
    vec3 viewDir = normalize(view);
    
    // Find the intersections of the volume with the viewing ray
    float tminView, tmaxView;
    getVolumeIntersection(cameraTexCoords.xyz, viewDir, tminView, tmaxView);
        
    vec3 vv = voxelDimensions.xyz;
    vv = vv * viewDir;
    float sampleSize = length((tmaxView - tminView) * vv);
    sampleSize /= MAX_SAMPLES;
    float opticalDepth = extinctionCoefficient.x * sampleSize;
    float correctedOpacity = 1.0 - exp(-opticalDepth);
    
    float constTerm = lightObjectDirAndConstTerm.w;

    vec4 fragColor = vec4(0, 0, 0, 0);

    float viewInc = (tmaxView - tminView) / MAX_SAMPLES;

    vec3 lightSampleInc = lightSampleDimensions.xyz * lightTexCoords.xyz;
#ifdef SMOOTH_LIGHTING
	vec3 lightDir = normalize(lightSampleInc);
#else
    float extinctionLight = lightWorldDirAndExtinction.w;
#endif

    vec3 ambientTerm = skyLightColor.xyz + multipleScatteringTerm.xyz;

    float t = tminView;

    float cosa = dot(normalize( vv ), lightTexCoords.xyz);
    float phase = phaseFunction(cosa);

    vec3 scattering = lightColor.xyz * constTerm;

    // eyeDepth is a depth of this fragment on cloud box face. 
    // these are front faces when camera looks from outside (tminView > 0) and
    // back faces when camera is inside the box (tminView == 0). 
    float depthFactor = eyeDepth / ( tminView > 0. ? tminView : tmaxView );

    for (int sampleNum = 0; sampleNum < MAX_SAMPLES; sampleNum++)
    {
        vec3 sampleTexCoords = viewDir * t + cameraTexCoords.xyz + originTexCoords.xyz;
        t += viewInc;

        float texel = getCloudDensity(sampleTexCoords, 1.0 - exp(-DETAIL_FALLOFF * t));
        if (texel < EPSILON) continue;

        float fade = computeFade(sampleTexCoords - originTexCoords.xyz);

        // apply lighting
#ifndef SMOOTH_LIGHTING
        vec4 accumulatedColor = lightColor;
        vec3 samplePos = sampleTexCoords + lightSampleInc * LIGHTING_DEPTH;

        vec3 scattering = lightColor.xyz * constTerm;

        for (int i = 0; i < LIGHTING_DEPTH; i++)
        {
            float lightSample = texture3D(tex3D, samplePos).x;

            if (lightSample != 0)
            {
                vec4 srcColor;
                srcColor.xyz = accumulatedColor.xyz * scattering;
                srcColor.w = extinctionLight;
                srcColor *= lightSample;

                accumulatedColor = srcColor + (1.0 - srcColor.w) * accumulatedColor;
            }

            samplePos -= lightSampleInc;
        }

        vec4 fragSample;
        fragSample.xyz = accumulatedColor.xyz * phase + ambientTerm;
#else   
        float tNear, tFar;
        getVolumeIntersection(sampleTexCoords - originTexCoords.xyz, lightDir, tNear, tFar);
        float cdepth = (1.0 - tFar) * SMOOTH_LIGHTING_BRIGHTNESS;
        vec4 fragSample;
        fragSample.xyz = scattering * cdepth * phase + ambientTerm;
#endif
   
        fragSample.w = correctedOpacity;

        // apply fog
        float depth = t * depthFactor;
        computeFog(fragSample, depth);

        // apply fading
        fragSample *= (1.0 - fade);

        // apply texture
        fragSample *= texel;

        // Under operator for compositing:
        fragColor = (1.0 - fragColor.w) * fragSample + fragColor;

        // Early ray termination!
        if (fragColor.w > OPACITY_THRESHOLD)
        {
            break;
        }

    }

#ifndef HDR
    toneMap(fragColor);
#endif

    gl_FragColor = fragColor * gl_Color;
}
