#version 150

// 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 200

// 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

// Computes lighting on cpu and lighting term is stored as 
// alpha component in volume texture
#define VOLUME_LIGHTING

// 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

// Number of noise octaves
#define NOISE_OCTAVES 2

// Make sure noise octave loop is not slowing us down
#define UNWIND_OCTAVE_LOOP 1

// 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

in vec4 vertColor;
in vec3 vertTexCoord;

out vec4 outFragColor;

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;

in 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.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);
}

vec2 getCloudDensity(in vec3 texCoord, in float t)
{	
	vec3 perturb = vec3(0.0,0.0,0.0);
	vec3 uvw = ((texCoord + noiseOffset.xyz) / viewSampleDimensions.xyz) / (NOISE_FREQUENCY * unitScale.x);

#ifndef UNWIND_OCTAVE_LOOP // Shall I trust the compiler that it will effectively unwind this loop ?    
    float scale = 1.0;
    for( int o = 0; o < NOISE_OCTAVES; o++ ) 
    {
        uvw += uvw;        
        perturb += ( texture(tex3D2, uvw ).xyz - 0.5 ) * scale;
        scale *= 0.5;
    }
#else 
#if ( NOISE_OCTAVES > 0 )
	perturb += 1.0  * texture(tex3D2, 2.0 * uvw).xyz - 0.5;
#endif
#if ( NOISE_OCTAVES > 1 )
	perturb += 0.5  * texture(tex3D2, 4.0 * uvw).xyz - 0.25;    
#endif
#if ( NOISE_OCTAVES > 2 )
    perturb += 0.25 * texture(tex3D2, 8.0 * uvw).xyz - 0.125;    
#endif
#if ( NOISE_OCTAVES > 3 )
    perturb += 0.125 * texture(tex3D2, 16.0 * uvw).xyz - 0.0625;
#endif
#if ( NOISE_OCTAVES > 4 )
    perturb += 0.0625 * texture(tex3D2, 32.0 * uvw).xyz - 0.03125;
#endif
#endif
			
    vec3 tc = texCoord + perturb * jitter.xyz * t;    			
	return texture(tex3D, tc.xzy ).xw;
}

void main()
{
	vec3 texCoord = vertTexCoord.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, 0.0, 0.0);

    float viewInc = (tmaxView - tminView) / MAX_SAMPLES;

    vec3 lightSampleInc = lightSampleDimensions.xyz * lightTexCoords.xyz;
#ifndef VOLUME_LIGHTING
#ifdef SMOOTH_LIGHTING
	vec3 lightDir = normalize(lightSampleInc);
#else
    float extinctionLight = lightWorldDirAndExtinction.w;
#endif
#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;
		
        vec2 sample = getCloudDensity(sampleTexCoords, 1.0 - exp(-DETAIL_FALLOFF * t));
        float texel = sample.x;
		if (texel < EPSILON) continue;
		
    	float fade = computeFade(sampleTexCoords - originTexCoords.xyz);	     
		
        // apply lighting
#ifdef VOLUME_LIGHTING
        vec4 fragSample;
        fragSample.xyz = scattering * vec3( sample.y ) * phase + ambientTerm;
#else
        
#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.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

#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
    
    outFragColor = fragColor * vertColor;
}
