uniform sampler2D 	wavesNormal,
					wavesColor,
					wavesMat,
					wavesPos;
uniform sampler2D 	bg,
					depthBG;
uniform samplerCube	ambientBG;		

#ifdef RIVER				
	uniform samplerCube	reflBG;
	uniform float invAmbientRange;
	uniform vec3 ambientPos;
#else
	uniform sampler2D	reflBG;
#endif

uniform float reflSaturation;

//uniform vec3 scatterPalette[8];

// SHADOW MAP ---------------------------------
uniform sampler2DShadow 	shadowmap;
uniform sampler2DShadow 	shadowmap2;
uniform sampler2DShadow 	shadowmap4;
uniform sampler2DShadow 	shadowmap8;
uniform sampler2DShadow 	shadowmapTerrain;

uniform float 		lightrange;
uniform float 		lightrange2;
uniform float 		lightrange4;
uniform float 		lightrange8;

uniform float		cOfs;

uniform vec4		LTM0,
					LTM1,
					LTM2,
					LTM3;

uniform vec4		LTM0b,
					LTM1b,
					LTM2b,
					LTM3b;
					
uniform vec4		LTM0c,
					LTM1c,
					LTM2c,
					LTM3c;
			
uniform vec4		LTM0d,
					LTM1d,
					LTM2d,
					LTM3d;	
						
uniform vec4		LTM0f,
					LTM1f,
					LTM2f,
					LTM3f;
					
uniform mat4		reprojTM2;
uniform mat4		reprojTM4;
uniform mat4		reprojTM8;

// ---------------------------------------------

//uniform sampler2D	brdfLUT;

uniform float		cubeLod;

varying vec2 		texcoord;
uniform mat4		eyeToWorld;

uniform vec3		sundir;
uniform vec3  		SUN; 	// = colore del sole
uniform float		srcRadius;

uniform vec3 		moonlightcolor;	// colore luna

uniform vec3		ambientColor;

uniform vec3		oceanDeepColor;
uniform vec3		oceanShallowColor;

//uniform float 		inScatter;
//uniform float       extintion;
uniform vec3		campos;

//uniform float		fogMultiplier;
uniform float		reflMul;
uniform float		reflLevel;
uniform float		specPow;
uniform float		specMul;
uniform float		fresnelPow;

//uniform float		density;
uniform float		refrbump;
uniform float		reflbump;

uniform float 		sssMultiplier;
uniform float 		foamDensityMultiplier;

//uniform float		foamWidth;

varying vec2		VPOS;
//uniform float 		wFar;

float Pi=3.14159265359;

// ------------ ATMOSPHERE ------------------------------

uniform float 		length_unit;
uniform vec3 		earth_center;

vec3 GetSunIrradiance(vec3 p, vec3 sun_direction);

// -------------------------------------------------------

// ------------------ PBR ---------------------------------
float phong_diffuse()
{
	return (1.0 / Pi);
}

vec3 fresnel_factor(vec3 f0, float product)
{
	return f0 + (vec3(1.0) - f0) * pow(1.0 - product, 5.0);
	//return mix(f0, vec3(1.0), pow(1.01 - product, 5.0));
}

float D_GGX(float a2, float NdH)
{
	//float m = roughness * roughness;
	float d = (NdH * a2 - NdH) * NdH + 1.0;
	return a2 / (Pi*d*d);
}

float G_schlick(float roughness, float NdV, float NdL)
{
	float r=(roughness+1.0)/2.0;
	float k=r*r*0.5;//0.125;
	float V = NdV * (1.0 - k) + k;
	float L = NdL * (1.0 - k) + k;
	return /*1.0/*/0.25/(V*L);
}

float G_Smith( float a2, float NdV, float NdL )
{
	float V = NdV + sqrt( NdV * (NdV - NdV * a2) + a2 );
	float L = NdL + sqrt( NdL * (NdL - NdL * a2) + a2 );
	return 1.0/( V*L );
}

float G_SmithJointApprox( float a, float NdV, float NdL )
{
	float V = NdL * ( NdV * ( 1 - a ) + a );
	float L = NdV * ( NdL * ( 1 - a ) + a );
	return 0.5 /( V+L );
}

vec3 CookTorrance(float NdL, float NdV, float NdH, vec3 F, float rough, float a)
{
	float a2=a*a;
	float D = D_GGX(a2, NdH);
	float G = G_schlick(rough, NdV, NdL);
//	float G = G_SmithJointApprox(a,NdV, NdL);
//	float G = G_Smith(a2,NdV, NdL);//G_schlick(rough, NdV, NdL);
	return max(D*G,0.0)*F; 
} 
// -----------------------------------------------------------------

vec4 RGBMDecode( vec4 rgbm ) {
  return vec4(6.0 * rgbm.rgb * rgbm.a,1.0);
}

float ComputeScatter( vec3 cameraToWorldPos, float L, float ext )
{
	vec3 dir=normalize(cameraToWorldPos);
	return ( ( exp((dir.y-1.0)*ext*L) - 1.0 ) / ((dir.y-1.0) * ext))*0.01;
}

vec4 decode(vec4 enc)
{
    vec2 fenc = enc.xy*4.0-2.0;
    float f = dot(fenc,fenc);
    float g = sqrt(1.0-f/4.0);
    vec4 n;
    n.xy = fenc*g;
    n.z = 1.0-f/2.0;
	n.w=enc.w;
    return n;
}

float blend1D(float f1, float f2, float a1, float a2, float alpha)
{
	float depth=0.2;
	float ma=max(a1+(1.0-alpha),a2+alpha)-depth;
	
	float b1=max(a1 + (1.0-alpha) - ma,0.0);
	float b2=max(a2 + alpha - ma,0.0);
	
	return (f1*b1 + f2*b2)/(b1+b2);
	//return (a1+(1.0-alpha)) > (a2+alpha) ? f1 : f2;
}

float hgPhase(float x, float g)
{
    float g2 = g*g;
	return 0.25 * ((1.0 - g2) * pow(1.0 + g2 - 2.0*g*x, -1.5));
}

float phase2Lobes(float x)
{
   // const float m = 1.0;
   // const float gm = 0.9;
    
	float lobe1 = hgPhase(x, 0.55 /* gm*/);
   // float lobe2 = hgPhase(x, -0.5 * gm);
    
    return lobe1;//mix(lobe2, lobe1, m);
}

float ComputeShadow(vec4 eyepos, vec3 N, float perturb)
{
	// SHADOW MAP CODE
	float 	occ=1.0;
	float 	dOfs = cOfs*0.5;
	vec3 	shadowcoords;
	vec4	XYproj;	
	vec3 	projcoords;
	vec4	reprojPos;
	
	float shadowOcc=1.0;
	
	vec4 perturbedpos=eyepos+(vec4(N,0.0)*perturb*400.0);
	
	XYproj.x = dot(perturbedpos,LTM0f);
	XYproj.y = dot(perturbedpos,LTM1f);
	XYproj.z = dot(perturbedpos,LTM2f);
	XYproj.w = dot(perturbedpos,LTM3f);

	projcoords.xy=XYproj.xy;
	projcoords.z=XYproj.z-0.0025;
			
	if( (clamp(projcoords.x,0.0,1.0)==projcoords.x)&&(clamp(projcoords.y,0.0,1.0)==projcoords.y) )
	{	
		shadowcoords.z=projcoords.z;
		shadowcoords.xy=projcoords.xy;
		shadowOcc=shadow2D(shadowmapTerrain,shadowcoords).r;

		float lightrange32=lightrange8*12.0;
		float lrangeMin=lightrange32-5000.0;
		shadowOcc=mix(shadowOcc,1.0,( clamp( (-eyepos.z-lrangeMin)/(lightrange32-lrangeMin),0.0,1.0) ));
		
		if(shadowOcc>0.0)
		{
			//////////////////////////////////////////////////////////////////////
			/*XYproj.x = dot(perturbedpos,LTM0e);
			XYproj.y = dot(perturbedpos,LTM1e);
			XYproj.z = dot(perturbedpos,LTM2e);
			XYproj.w = dot(perturbedpos,LTM3e);

			vec3 gProjcoords=vec3(XYproj.xy,XYproj.z-0.0025);
	
			if( (clamp(gProjcoords.x,0.0,1.0)==gProjcoords.x)&&(clamp(gProjcoords.y,0.0,1.0)==gProjcoords.y) )
			{*/	
				float lightrange16=lightrange8*3.0;
				if( -eyepos.z/lightrange <= 1.0 )
				{
					// COMPUTE LIGHT TEXTURE PROJECTION + ATTENUATION
					XYproj.x = dot(perturbedpos,LTM0);
					XYproj.y = dot(perturbedpos,LTM1);
					XYproj.z = dot(perturbedpos,LTM2);
					XYproj.w = dot(perturbedpos,LTM3);

					projcoords.xy=XYproj.xy;
					projcoords.z=XYproj.z-0.00005;
					///////////////////////////////////
					
					dOfs = cOfs*0.5;
					occ=0.0;

					shadowcoords.z=projcoords.z;

					shadowcoords.xy=projcoords.xy+vec2(-dOfs,dOfs);
					occ+=shadow2D(shadowmap,shadowcoords).r;

					shadowcoords.xy=projcoords.xy+vec2(dOfs,dOfs);
					occ+=shadow2D(shadowmap,shadowcoords).r;

					shadowcoords.xy=projcoords.xy+vec2(dOfs,-dOfs);
					occ+=shadow2D(shadowmap,shadowcoords).r;

					shadowcoords.xy=projcoords.xy+vec2(-dOfs,-dOfs);
					occ+=shadow2D(shadowmap,shadowcoords).r;
					
					occ *= 0.25;
					shadowOcc*=occ;
				}
				else
				{	
					if( -eyepos.z/lightrange2 <= 1.0 )
					{
						// COMPUTE LIGHT TEXTURE PROJECTION + ATTENUATION
						reprojPos=reprojTM2*perturbedpos;
						XYproj.x = dot(reprojPos,LTM0b);
						XYproj.y = dot(reprojPos,LTM1b);
						XYproj.z = dot(reprojPos,LTM2b);
						XYproj.w = dot(reprojPos,LTM3b);

						projcoords.xy=XYproj.xy;
						projcoords.z=XYproj.z-0.0004;
						///////////////////////////////////

						dOfs = cOfs*0.5;
						occ=0.0;

						shadowcoords.z=projcoords.z;

						shadowcoords.xy=projcoords.xy+vec2(-dOfs,dOfs);
						occ+=shadow2D(shadowmap2,shadowcoords).r;

						shadowcoords.xy=projcoords.xy+vec2(dOfs,dOfs);
						occ+=shadow2D(shadowmap2,shadowcoords).r;

						shadowcoords.xy=projcoords.xy+vec2(dOfs,-dOfs);
						occ+=shadow2D(shadowmap2,shadowcoords).r;

						shadowcoords.xy=projcoords.xy+vec2(-dOfs,-dOfs);
						occ+=shadow2D(shadowmap2,shadowcoords).r;
						
						occ *= 0.25;
						shadowOcc*=occ;
					}
					else
					{
						if( -eyepos.z/lightrange4 <= 1.0 )
						{
							// COMPUTE LIGHT TEXTURE PROJECTION + ATTENUATION
							reprojPos=reprojTM4*perturbedpos;
							XYproj.x = dot(reprojPos,LTM0c);
							XYproj.y = dot(reprojPos,LTM1c);
							XYproj.z = dot(reprojPos,LTM2c);
							XYproj.w = dot(reprojPos,LTM3c);

							projcoords.xy=XYproj.xy;
							projcoords.z=XYproj.z-0.0006;
							///////////////////////////////////

							dOfs = cOfs*0.5;
							occ=0.0;

							shadowcoords.z=projcoords.z;

							shadowcoords.xy=projcoords.xy+vec2(-dOfs,dOfs);
							occ+=shadow2D(shadowmap4,shadowcoords).r;

							shadowcoords.xy=projcoords.xy+vec2(dOfs,dOfs);
							occ+=shadow2D(shadowmap4,shadowcoords).r;

							shadowcoords.xy=projcoords.xy+vec2(dOfs,-dOfs);
							occ+=shadow2D(shadowmap4,shadowcoords).r;

							shadowcoords.xy=projcoords.xy+vec2(-dOfs,-dOfs);
							occ+=shadow2D(shadowmap4,shadowcoords).r;
						
							occ *= 0.25;
							
							float lrangeMin=lightrange4-300.0;
							occ=mix(occ,1.0,( clamp( (-eyepos.z-lrangeMin)/(lightrange4-lrangeMin),0.0,1.0) ));
							shadowOcc*=occ;
						}
						else
						{
							if( -eyepos.z/lightrange8 <= 1.0 )
							{
								reprojPos=reprojTM8*perturbedpos;
								XYproj.x = dot(perturbedpos,LTM0d);
								XYproj.y = dot(perturbedpos,LTM1d);
								XYproj.z = dot(perturbedpos,LTM2d);
								XYproj.w = dot(perturbedpos,LTM3d);

								dOfs = cOfs*0.5;
								occ=0.0;
								shadowcoords.z=projcoords.z;

								shadowcoords.xy=projcoords.xy+vec2(-dOfs,dOfs);
								occ+=shadow2D(shadowmap8,shadowcoords).r;

								shadowcoords.xy=projcoords.xy+vec2(dOfs,dOfs);
								occ+=shadow2D(shadowmap8,shadowcoords).r;

								shadowcoords.xy=projcoords.xy+vec2(dOfs,-dOfs);
								occ+=shadow2D(shadowmap8,shadowcoords).r;

								shadowcoords.xy=projcoords.xy+vec2(-dOfs,-dOfs);
								occ+=shadow2D(shadowmap8,shadowcoords).r;

								occ *= 0.25;
								
								float lrangeMin=lightrange8-600.0;
								occ=mix(occ,1.0,( clamp( (-eyepos.z-lrangeMin)/(lightrange8-lrangeMin),0.0,1.0) ));
								shadowOcc*= occ;
							}
						}
					}
				}
			//}
		}
	}
	////////////////////////////////////////
	return shadowOcc;
}

////////////////////////////
void main()
{
	vec4 deepColor=texture2D(wavesColor,texcoord.st);
	vec4 oceanMat=texture2D(wavesMat,texcoord.st);
	
	vec4 encWavNormal=texture2D(wavesNormal,texcoord.st);
	vec4 wavNormal=decode(encWavNormal);
	float wavDepth=texture2D(wavesPos,texcoord.st).r;
	vec4 wavPos;
	wavPos.z=wavDepth;
	
	// compute position in eye space
	wavPos.xy=VPOS*-wavPos.z;
	wavPos.w=1.0;
	
	vec2 UV=texcoord.xy;
	
	// compute normals
	vec3 normal=wavNormal.xyz;
	vec3 UVnormal=normal;
	UVnormal.z=normal.y;
	UVnormal.x=-normal.z;
	UVnormal.y=-normal.x;
	UVnormal=normalize(UVnormal);
	
	// transform pos into world space
	vec4 Wpos=eyeToWorld*wavPos;
	vec3 Cam2WorldPos=Wpos.xyz-campos.xyz;
	
	vec3 tvdir=normalize(-Cam2WorldPos.xyz);
	float dotLE=dot(sundir,tvdir.xyz);

	// PROVA DIFFUSO ////////////
	vec3 R=reflect(-tvdir,normal.xyz);
	vec3 L=sundir;
	float r= sin(srcRadius);
	float c= cos(srcRadius);
	float DdotR=dot(L,R);
	vec3 S = R-DdotR*L;
	L = DdotR < c ? normalize(c*L+normalize(S)*r) : R;
		
	vec3 H = L+tvdir;
	H=normalize(H);
	
	float NdotL=clamp(dot(L,normal),0.0,1.0);
	float NdotH = clamp(dot(H,normal),0.0,1.0);
	float EdotH = clamp(dot(tvdir,H),0.0,1.0);
	float NdotE = clamp(dot(normal,tvdir),0.0,1.0);
	float dNdotL= clamp(dot(sundir,normal),0.0,1.0);
	
	// --------------------------------------- COOK TORRANCE --------------------------------------
	vec3 REFL=vec3(encWavNormal.z/*specMul*/);
	float rough=sqrt(oceanMat.y/*specPow*/);
			
	// specular
	vec3 specfresnel=vec3(0.0,0.0,0.0);
	vec3 spec=vec3(0.0,0.0,0.0);		
	
	specfresnel=fresnel_factor(REFL, EdotH)*encWavNormal.z/*specMul*/;
	spec = CookTorrance(NdotL, NdotE, NdotH, specfresnel, rough, rough*rough) * NdotL;
	// -----------------------------------------------------------------------------------------
	
	float shadowOcc=ComputeShadow(wavPos, normal, 0.1);
	
	/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	#ifdef RIVER
		vec3 reflection = reflect(tvdir,normal);
		reflection= ((ambientPos-Wpos.xyz)*invAmbientRange) + reflection;
	#else
		vec2 reflectOffset=clamp(UV + UVnormal.xy*reflbump,0.0,1.0);
	#endif	
	vec2 perturbation=UVnormal.xy*refrbump;
		
	vec2 newUV=UV.xy+perturbation;
	float refractedDepth=texture2D(depthBG,newUV).r;
	float zeroRefr=clamp((wavPos.z-refractedDepth)+0.01,0.0,1.0);
	perturbation=perturbation*zeroRefr*step(vec2(0.0),newUV)*step(newUV,vec2(1.0));
	
	// chromatic aberration
	vec2 UVred=UV+perturbation*0.85;
	vec2 UVgreen=UV+perturbation;
	vec2 UVblue=UV+perturbation*1.15;
	
	// copmpute attenuation
	float density=deepColor.a*0.01;
	UV+=perturbation;
	UV=clamp(UV,0.0,1.0);
	vec4 bgPos;
	bgPos.z=texture2D(depthBG,UV).r;
	bgPos.xy=VPOS*-bgPos.z;
	bgPos.w=1.0;
	bgPos=eyeToWorld*bgPos;
	float d=length(bgPos.xyz-Wpos.xyz);//abs(depth-wavPos.z);
	float att=exp(-d*density);

	// WATER COLOR //////////////////////////////////
	float wScatter=ComputeScatter(Cam2WorldPos,d,density);
	
	// SUN LIGHT /////////////////////////////
	vec3 sunLightColor = GetSunIrradiance((Wpos.xyz*length_unit)-earth_center.xyz, sundir)*SUN + moonlightcolor;
	/////////////////////////////////////////
	
	#ifdef RIVER
	//	vec3 shallowColor=pow(scatterPalette[int(oceanMat.x*256.0)],vec3(2.2));
		vec3 shallowColor=vec3(1.0,1.0,1.0);//mix(shallowColor,vec3(1.0,1.0,1.0),att/*att*/);
		float glossLod = rough*8.0;
		vec3 reflcolor = RGBMDecode(textureCubeLod(reflBG,reflection,glossLod)).xyz;
	#else
		vec3 shallowColor=mix(oceanShallowColor*sunLightColor,vec3(1.0,1.0,1.0),att/*att*/);
		float glossLod=min((1.0-min(length(Cam2WorldPos)*0.0001,1.0))*2.0,2.0);	
		vec3 reflcolor=texture2DLod(reflBG,reflectOffset,glossLod).xyz;
	#endif
	
	// DESATURATE refl
	float lum=dot(reflcolor.rgb,vec3(0.3,0.59,0.11));
	reflcolor.rgb=mix(vec3(lum),reflcolor.rgb,reflSaturation);
	/////////////////////////////////////////////////
	
	float fresnelres=pow((1.0-NdotE),fresnelPow)*reflLevel;
	
	float sss=phase2Lobes(-dotLE)*(1.0-dot(vec3(0.0,1.0,0.0),normal));//(/*0.1+*/pow(max(-dotLE,0.0),2.0))*-normal.z;//(1.0-NdotL);//*encWavNormal.z;
	#ifdef RIVER
		sss=(max(sss*encWavNormal.a,0.0));
		//sss*=0.25;
	#else
		sss=(max(sss*sssMultiplier,0.0));
	#endif
	
	float foamDensity=0.0;
	vec3 foamColor=vec3(0.5,0.5,0.5);//
	vec3 bgColor=vec3(texture2D(bg,UVred).r,texture2D(bg,UVgreen).g,texture2D(bg,UVblue).b);
	float thinWaterMul=0.0;
	#ifdef RIVER
	#else
		//thinWaterMul=max(Wpos.y*0.0001*encWavNormal.a,0.0);//clamp(Wpos.y*0.0001*encWavNormal.a/*sssMultiplier*/,0.0,1.0);
	#endif	
	vec3 waterFinalColor= bgColor*att*shallowColor + ((deepColor.xyz*wScatter*(1.0+dNdotL)*oceanDeepColor + oceanShallowColor*(sss/* + thinWaterMul*/))*shadowOcc)*sunLightColor;
	
	vec4 bgPos2;
	bgPos2.z=texture2D(depthBG,texcoord.xy).r;
	bgPos2.xy=VPOS*-bgPos2.z;
	bgPos2.w=1.0;
	bgPos2=eyeToWorld*bgPos2;
	float d2=exp(-length(bgPos2.xyz-Wpos.xyz)*0.005);
	float fdist=1.0-(clamp(d2,0.0,1.0));
	float coastDist=clamp(fdist,0.0,1.0);
	if(-wavPos.z<200000.0)
	{
		foamDensity=(1.0-smoothstep(/*foamWidth*/oceanMat.r-0.5,/*foamWidth*/oceanMat.r,fdist));
		foamDensity = clamp(foamDensity,0.0,1.0);
		foamDensity = blend1D(0.0, oceanMat.a, 0.5, oceanMat.a, foamDensity);
		foamDensity *= coastDist;
	
		float t=clamp( (-wavPos.z-170000.0)/100000.0,0.0,1.0);
		foamDensity=mix(foamDensity,0.0,t)*2.0;
		
		float ambientLum=dot(RGBMDecode(textureCubeLod(ambientBG,-normal.xyz,cubeLod)).xyz,vec3(0.3,0.59,0.11));
		foamColor=(ambientColor*ambientLum+(dNdotL+fresnelres)*sunLightColor/Pi*shadowOcc)*foamColor;
		
		// spume /////
		float spumeDensity=oceanMat.z;
		spumeDensity = mix(0.0,oceanMat.a,spumeDensity);
		foamDensity=foamDensity + spumeDensity;
		foamDensity=clamp(foamDensity,0.0,1.0);
	}
	
	waterFinalColor=mix(waterFinalColor,reflcolor*reflMul,clamp(fresnelres,0.0,1.0)) + spec*sunLightColor*shadowOcc;//*specMul;
	waterFinalColor=waterFinalColor+foamColor*foamDensity;
	
	gl_FragColor.xyz=max(waterFinalColor,0.0);
	gl_FragColor.w=min(coastDist*25.0, 1.0);
}
