Shader "Nature/OceanAdvanced"
{
	Properties
	{
		_BaseColor ("Base color", COLOR)  = ( .54, .95, .99, 0.5)
		_WaterColor ("Water color", COLOR)  = ( .54, .95, .99, 0.5)
		_ReflectionColor ("Reflection color", COLOR)  = ( .54, .95, .99, 0.5)
		_SpecularColor ("Specular color", COLOR)  = ( .72, .72, .72, 1)
		[NoScaleOffset] _Foam ("Foam texture", 2D) = "white" {}
		[HideInInspector] world_light_dir("", VECTOR) = (0.0, 1.0, 0.8, 0.0)
	}
	
	CGINCLUDE
	#include "UnityCG.cginc"
	
	uniform float4 _BaseColor;
	uniform float4 _WaterColor;
	uniform float4 _ReflectionColor;
	uniform float4 _SpecularColor;
	
	#define NB_WAVE 5
	float4 waves_p[NB_WAVE];
	float4 waves_d[NB_WAVE];
	
	#define NB_INTERACTIONS 64
	#define WAVE_DURATION 4.0
	#define WAVE_SPEED 3.0
	#define MAX_WAVE_AMP 0.5
	float4 interactions[NB_INTERACTIONS];

	uniform float4 world_light_dir;
	uniform float4 sun_color;

	uniform sampler2D _Foam;
	uniform sampler2D _RefractionTex;
	uniform sampler2D _CameraDepthTexture;
	uniform sampler2D _CameraDepthNormalsTexture;
	
	#define PI 3.14159234


	float evaluateWave(float4 wave_param, float4 wave_dir, float2 pos, float t)
	{
	  return wave_param.y * sin( dot(wave_dir.xy, pos) * wave_param.x + t * wave_param.z);
	}

	float get_water_height(float3 p)
	{
		float height = 0.0;
		for(int i = 0; i < NB_WAVE; i++)
			height += evaluateWave(waves_p[i], waves_d[i], p.xz, _Time.y);
		return height;
	}

	float3 get_water_normal(float3 a)
	{
		const float eps = 0.01;
		float3 b = a + float3(eps, 0.0, 0.0);
		float3 c = a + float3(0.0, 0.0, eps);

		a.y = get_water_height(a);
		b.y = get_water_height(b);
		c.y = get_water_height(c);
		
		float3 n = normalize(cross(c - a, b - a));
		return n;
	}

	float hash(float2 p )
	{
		float h = dot(p,float2(127.1,311.7));	
		return frac(sin(h)*43758.5453123);
	}
	
	float noise2(in float2 p )
	{
		float2 i = floor(p);
		float2 f = frac(p);	
		float2 u = f*f*(3.0-2.0*f);
		return -1.0+2.0 * lerp( lerp( hash( i + float2(0.0,0.0) ), 
						 hash( i + float2(1.0,0.0) ), u.x),
					lerp( hash( i + float2(0.0,1.0) ), 
						 hash( i + float2(1.0,1.0) ), u.x), u.y);
	}
	
	float sea_octave(float2 uv, float choppy)
	{
		uv += noise2(uv);        
		float2 wv = 1.0 - abs(sin(uv));
		float2 swv = abs(cos(uv));    
		wv = lerp(wv,swv,wv);
		return pow(1.0 - pow(wv.x * wv.y,0.65),choppy);
	}

	float map_detailed(float3 p)
	{
		float freq = 0.16;
		float amp = 0.6;
		float choppy = 4.0;
		float2 uv = p.xz;
		uv.x *= 0.75;
		
		float d, h = 0.0;
		for(int i = 0; i < 5; i++)
		{
			d = sea_octave((uv + _Time.yy) * freq, choppy);
			d += sea_octave((uv - _Time.yy) * freq, choppy);
			h += d * amp;
			uv = float2(uv.x * 1.6 + 1.2 * uv.y, uv.x * -1.2 + 1.6 * uv.y);
			freq *= 1.9;
			amp *= 0.22;
			choppy = lerp(choppy,1.0,0.2);
		}
		
		for (int j = 0; j < NB_INTERACTIONS; j++)
		{
			half dist = distance(p.xz, interactions[j].xy);
			half elapsed = (_Time.y - interactions[j].w);
			half computed_distance = elapsed * WAVE_SPEED;
			half power = 1.0 - saturate(pow(abs(computed_distance - dist), 2.0) * 0.3);
			power *= 1.0 - saturate(elapsed / WAVE_DURATION);
			dist += 2.0;
			p.y += power * interactions[j].z;
		}
		
		return p.y - h;
	}
	
	float map(float3 p)
	{
		float freq = 0.16;
		float amp = 0.6;
		float choppy = 4.0;
		float2 uv = p.xz;
		uv.x *= 0.75;
		
		float d, h = 0.0;
		for(int i = 0; i < 3; i++)
		{
			d = sea_octave((uv + _Time.yy) * freq, choppy);
			d += sea_octave((uv - _Time.yy) * freq, choppy);
			h += d * amp;
			uv = float2(uv.x * 1.6 + 1.2 * uv.y, uv.x * -1.2 + 1.6 * uv.y);
			freq *= 1.9;
			amp *= 0.22;
			choppy = lerp(choppy,1.0,0.2);
		}
		return p.y - h;
	}
	
	float3 get_detailed_normal(float3 a, float eps)
	{
		float3 b = a + float3(eps, 0.0, 0.0);
		float3 c = a + float3(0.0, 0.0, eps);
		
		a.y = map_detailed(a);
		b.y = map_detailed(b);
		c.y = map_detailed(c);
		
		float3 n = normalize(cross(b - a, c - a));
		n.y *= -1;
		return n;
	}

	float3 getSkyColor(float3 e)
	{
		e.y = max(e.y, 0.0);
		return float3(pow(1.0 - e.y,2.0), 1.0 - e.y, 0.6 + (1.0 - e.y) * 0.4);
	}
	
	float diffuse(float3 n,float3 l,float p)
	{
		return pow(dot(n,l) * 0.4 + 0.6,p);
	}
	
	float specular(float3 n,float3 l,float3 e,float s)
	{    
		float nrm = (s + 8.0) / (3.1415 * 8.0);
		return pow(max(dot(reflect(e,n),l),0.0),s) * nrm;
	}

	inline void ComputeScreenAndGrabPassPos (float4 pos, out float4 screenPos, out float4 grabPassPos) 
	{
		#if UNITY_UV_STARTS_AT_TOP
			float scale = -1.0;
		#else
			float scale = 1.0f;
		#endif
		
		screenPos = ComputeScreenPos(pos); 
		grabPassPos.xy = ( float2( pos.x, pos.y*scale ) + pos.w ) * 0.5;
		grabPassPos.zw = pos.zw;
	}

	struct appdata
	{
		float4 vertex : POSITION;
	};
	
	struct v2f
	{
		float4 pos : SV_POSITION;
		float3 normal : NORMAL;
		float3 world_position : TEXCOORD0;
		float4 ref : TEXCOORD1;
		float4 grabPassPos : TEXCOORD2;
		UNITY_FOG_COORDS(5)
	};
	
	v2f vert(appdata v)
	{
		v2f o;
		
		float4 world_position = mul(unity_ObjectToWorld, v.vertex);
		world_position.y = get_water_height(world_position.xyz);
		
		float interactive = 0.0;
		for (int i = 0; i < NB_INTERACTIONS; i++)
		{
			half dist = distance(world_position.xz, interactions[i].xy);
			half elapsed = (_Time.y - interactions[i].w);
			half computed_distance = elapsed * WAVE_SPEED;
			half power = 1.0 - saturate(pow(abs(computed_distance - dist), 2.0) * 0.3);
			power *= 1.0 - saturate(elapsed / WAVE_DURATION);
			dist += 2.0;
			interactive += power * interactions[i].z;
		}
		world_position.y += clamp(interactive, -MAX_WAVE_AMP, MAX_WAVE_AMP);
		
		o.world_position = world_position;
		o.normal = get_water_normal(world_position.xyz);
		o.pos = mul(UNITY_MATRIX_VP,  world_position);
		
		ComputeScreenAndGrabPassPos(o.pos, o.ref, o.grabPassPos);
		UNITY_TRANSFER_FOG(o,o.pos);
		
		return o;
	}

	#define REFLECTION
	#define FOAM
	#define SPECULAR
	#define REFRACTION
	
	half4 frag( v2f i ) : SV_Target
	{
		float3 eye_vector = i.world_position.xyz - _WorldSpaceCameraPos;
		float eye_distance = length(eye_vector);
		float3 eye = normalize(eye_vector);
		
		half3 detail_normal = get_detailed_normal(i.world_position, 0.01 * pow(length(eye_vector), 0.8));
		half3 vertex_normal = i.normal;
		half3 normal = normalize(detail_normal * 1.0 + vertex_normal * 0.0);
		
		float depth = LinearEyeDepth(tex2Dproj(_CameraDepthTexture, UNITY_PROJ_COORD(i.ref)).r);
		half4 distort = half4(i.normal.xz * 0.5 + detail_normal.xz * 0.6, 0.0, 0.0);
		distort *=  pow(saturate((depth - i.ref.z) * 0.3), 2.0);
		
		float sceneZ = LinearEyeDepth(tex2Dproj(_CameraDepthTexture, UNITY_PROJ_COORD(i.ref + distort * 0.2)).r);
		float objectZ = i.ref.z;
		float depthFactor = 1.0 - saturate(abs(sceneZ - objectZ) / 4.0);
		
		float3 light_direction = normalize(world_light_dir);
		half4 baseColor;
		baseColor.a = 1.0;
		
		//baseColor.rgb = getSeaColor(i.world_position, normal, normalize(light_direction), normalize(eye_vector), eye_vector);
		
		#ifdef REFLECTION
			float3 l = normalize(light_direction);
			
			float fresnel = clamp(1.0 - pow(dot(normal,-eye), 1.0), 0.0, 1.0);
			fresnel = pow(fresnel,3.0) * 0.65;
			fresnel = pow(fresnel, 0.8) * 0.8;
			
			float3 reflected = getSkyColor(reflect(eye,normal)) * _ReflectionColor;
			
			/* SSR test */
			#ifdef SSR
				half4 refl = i.ref + distort;
				float3 decodedNormal;
				float decodedDepth;
				DecodeDepthNormal( tex2D( _CameraDepthNormalsTexture, i.uv), decodedDepth, normal);
				float4 pixelPosition = float4(i.cameraRay * decodedDepth, 0.0);
				//float3 reflected = tex2Dproj(_RefractionTex, UNITY_PROJ_COORD(pixelPosition)) * _ReflectionColor; 
			#endif

			
			float3 refracted = _BaseColor + diffuse(normal, l, 80.0) * _WaterColor * 0.12; 
			
			baseColor.rgb = lerp( refracted, reflected, fresnel);
		#endif
		
		#ifdef REFRACTION
			half3 refraction = tex2Dproj(_RefractionTex, UNITY_PROJ_COORD(i.grabPassPos + distort));
			refraction *= lerp(_ReflectionColor, _BaseColor, 1.0 - depthFactor * 2.0);
			baseColor.a = 1.0 - (saturate(depthFactor * 2.0) * saturate(1.2 - 0.6));
		#endif
		
		
		/* Possible depth color */
		float atten = max(1.0 - dot(eye, eye) * 0.001, 0.0);
		baseColor.rgb += _WaterColor * (i.world_position.y + 0.6) * 0.18 * atten;
			
		#ifdef SPECULAR
			float spec = specular(vertex_normal * 0.2 + detail_normal * 0.8, l, eye, 60.0);
			baseColor.rgb += sun_color * spec;
		#endif
			
		
		#ifdef FOAM
			half3 foamColor = tex2D(_Foam, i.world_position.xz * 0.1 + _Time.xx * 0.5 + distort).rgb;
			//baseColor.rgb += foamColor * smoothstep(0.2, 1.5, depthFactor);
			baseColor.a += foamColor.r * 0.05;
			
			float wave_foam = clamp(smoothstep(-0.1, 1.0, 1.0 - detail_normal.y), 0.0, 0.5);
			baseColor.a += wave_foam;
			baseColor = saturate(baseColor);
			baseColor.rgb += foamColor * clamp(wave_foam + smoothstep(0.2, 1.5, depthFactor), 0.0, 0.5);
		#endif
		
		UNITY_APPLY_FOG(i.fogCoord, baseColor);

		baseColor = saturate(baseColor);
		baseColor.rgb = lerp(refraction.rgb, baseColor.rgb, clamp(baseColor.a, 0.0, 0.8));
		baseColor.a = 1.0;
		return saturate(baseColor);
	}
	
	ENDCG
	Subshader
	{
		Tags {"RenderType"="Transparent" "Queue"="Transparent"}
		Lod 300
		//ColorMask RGBA
		
		GrabPass { "_RefractionTex" }
		
		Pass
		{
			Blend SrcAlpha OneMinusSrcAlpha
			//ZTest LEqual
			//ZWrite Off
			//Cull Off
		
			CGPROGRAM
		
			#pragma target 3.0
			#pragma vertex vert
			#pragma fragment frag
			#pragma multi_compile_fog
			#pragma multi_compile_fwdbase
			#pragma fragmentoption ARB_fog_exp2
			#pragma fragmentoption ARB_precision_hint_fastest
		
			ENDCG
		}
	}
	Fallback "Transparent/Diffuse"
}