#version 420

// Texture array containing front & back surface normals
uniform sampler2DArray normalTex;

// Texture containing the lightprobe for the surrounding env map.
uniform samplerCube    bgTex;

// Index of refraction
uniform float idxRefr;

// Rotation of the background
uniform mat4 rotation;

// The projection matrix used to create the normal texture.
uniform mat4 project;

// Input from vert shader, eye-space view vector
in vec3 viewVec;

// Input from vert shader, tex coord in [0..1] for a full screen texture
in vec2 fragTexCoord;

// Output refractive color for this surface
out vec4 result;

// Approximate the Fresnel coefficients using Schlick's approximation
vec2 SchlickFresnelApprox( float cosAng )
{
	float oneMinus = 1.0-cosAng;
	float approx = 0.05 + 0.95*(oneMinus*oneMinus*oneMinus*oneMinus*oneMinus);
	return vec2( approx, 1.0-approx );
}

// Take an eye-space position and project it into a 2D image coordinate
vec2 ProjectToTexCoord( vec4 eyeSpacePos )
{
	vec4 projLoc = project * eyeSpacePos;
	return ( 0.5*(projLoc.xy / projLoc.w) + 0.5 );
}

// A refraction function similar to the GLSL built-in, except this gives a
//    useful approximation vector when total internal reflection occurs.
vec4 refraction( vec3 incident, vec3 normal, float ni_nt )
{
    vec4 returnVal;
    float ni_nt_sqr = ni_nt*ni_nt;
    float tmp = 1.0;
	float IdotN = dot( -incident, normal );
	float cosSqr = 1.0 - ni_nt_sqr*(1.0 - IdotN*IdotN);
	return ( cosSqr <= 0.0 ? 
			 vec4( normalize(reflect( incident, normal )), -1.0 ) :
			 vec4( normalize( ni_nt * incident + (ni_nt* IdotN - sqrt( cosSqr )) * normal ), 1.0 ) );
}


void main( void ) 
{
    vec4 frontNorm = texture( normalTex, vec3(fragTexCoord, 0) );
    
    // If we didn't hit the object ,stop now.  Really... This should be < 0,
    //    but any sample where we're interpolating and don't have a value
    //    above 0.1 won't be able to be re-normalized into a useful normal vector.
    if ( dot(frontNorm.xyz,frontNorm.xyz) <= 0.1 )
		discard;
		
	// Find the distances to the front and back surface along the view ray
	float distFront = frontNorm.w;
	float distBack  = texture( normalTex, vec3(fragTexCoord, 1) ).w;
		
	// Find the viewing vector.  We already have this, but it's interpolated,
	//    so re-normalize it.
	vec3 V   = normalize( viewVec );
		
	// Find the front facing position.  The w coef of the normal texture stores 
	//    eye-space depth of the front fragment.
	vec4 P_1 = vec4( distFront * V, 1.0 );
    
	// Re-normalize front-face surface normal.
	vec3 N_1 = normalize( frontNorm.xyz );
	float NdotV = dot( -V, N_1 );
	
	// Find the distances to the front and back surface along the view ray
	float deltaDist = distBack - distFront;
	
	// Get our Fresnel terms
	vec2 fresnel   = SchlickFresnelApprox( NdotV );
	
	// Compute our reflection direction & reflection color
	vec4 dirRefl   = rotation * vec4( reflect( V, N_1 ), 0.0 );
	vec4 reflColor = texture( bgTex, normalize(dirRefl.xyz) );
    
	// Compute our refraction direction
	vec3 T_1 = normalize( refract( V, N_1, 1.0/idxRefr ).xyz );
	
	// Compute our approximate exitant location & surface normal
	vec4 P_2_tilde = vec4( T_1 * deltaDist + P_1.xyz, 1.0 );
	vec3 N_2       = texture( normalTex, vec3(ProjectToTexCoord(P_2_tilde), 1) ).xyz;
	float dotN2    = dot( N_2.xyz, N_2.xyz );
	
	// Did we hit a black texel in the back normal map? (deltaDist was too big! Oops)
	//     Conceptually, this means we pass through the side of the object (not front/back)
	//     In this case, we'll use a surface 'normal' perpindicular to the view direction (but 
	//     generally along the same direction as our refracted T_1)
	N_2 = ( dot( N_2.xyz, N_2.xyz ) <= 0.0 ) ?          // Hit black pixel?
	      vec3( T_1.xy, 0.0 ) / length(T_1.xy) :        // Yes.  Use approximation.
	      N_2;                                          // No.   Use stored normal.
	
	// Refract at the second surface
	vec4 T_2 = refraction( T_1, -normalize( N_2 ), idxRefr );
	
	// Lookup the environment map color in the refraction direction
	vec4 dirRefr   = rotation * vec4( T_2.xyz, 0.0 );
	vec4 refrColor = texture( bgTex, normalize(dirRefr.xyz) );
    
    // Output our final result
    result = fresnel.x * reflColor + fresnel.y * refrColor;
}