Shader "Azure[Sky]/Fog Scattering"
{
	Properties
	{
		_MainTex ("Texture", 2D) = "white" {}
	}
	SubShader
	{
		// No culling or depth
		Cull Off ZWrite Off ZTest Always

		Pass
		{
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#pragma target 3.0
			#include "UnityCG.cginc"

		//  Start: LuxWater
			#pragma multi_compile __ LUXWATER_DEFERREDFOG

			#if defined(LUXWATER_DEFERREDFOG)
				sampler2D _UnderWaterMask;
				float4 _LuxUnderWaterDeferredFogParams; // x: IsInsideWatervolume?, y: BelowWaterSurface shift, z: EdgeBlend
			#endif
		//  End: LuxWater

			uniform sampler2D _MainTex;
			uniform sampler2D_float _CameraDepthTexture;
			uniform float4x4  _FrustumCorners;
			uniform float4    _MainTex_TexelSize;

			uniform int         _Azure_SunsetColorMode;
			uniform float       _Azure_Pi316, _Azure_Pi14, _Azure_Scattering, _Azure_Pi, _Azure_Exposure, _Azure_NightIntensity, _Azure_LightSpeed, _Azure_MoonBrightRange, _Azure_MoonEmission,
								_Azure_FogBlend, _Azure_FogDensity, _Azure_FogDistance, _Azure_FogScale, _Azure_HeightFogBlend, _Azure_HeightFogDensity, _Azure_HeightFogDistance, _Azure_HeightFogStart,
								_Azure_HeightFogEnd, _Azure_MieDepth, _Azure_Kr, _Azure_Km;
			uniform float3      _Azure_SunDirection, _Azure_MoonDirection, _Azure_Br, _Azure_Bm, _Azure_MieG;
			uniform float4      _Azure_RayleighColor, _Azure_MieColor, _Azure_MoonColor, _Azure_MoonBrightColor;
			uniform float4x4    _Azure_SunMatrix, _Azure_MoonMatrix, _Azure_StarMatrix, _Azure_NoiseMatrix, _Azure_UpMatrix;

			struct appdata
			{
				float4 vertex   : POSITION;
				float4 texcoord : TEXCOORD0;
			};

			struct v2f
			{
				float4 Position        : SV_POSITION;
    			float2 uv 	           : TEXCOORD0;
				float4 interpolatedRay : TEXCOORD1;
				float2 uv_depth        : TEXCOORD2;
			};

			v2f vert (appdata v)
			{
				v2f Output;
    			UNITY_INITIALIZE_OUTPUT(v2f, Output);

				v.vertex.z = 0.1;
				Output.Position = UnityObjectToClipPos(v.vertex);
				Output.uv       = v.texcoord.xy;
				Output.uv_depth = v.texcoord.xy;
				#if UNITY_UV_STARTS_AT_TOP
				if (_MainTex_TexelSize.y < 0)
					Output.uv.y = 1 - Output.uv.y;
				#endif

				//Based on Unity5.6 GlobalFog.
				//--------------------------------
				int index = v.texcoord.x + (2.0 * Output.uv.y);
				Output.interpolatedRay   = _FrustumCorners[index];
				Output.interpolatedRay.xyz = mul((float3x3)_Azure_UpMatrix, Output.interpolatedRay.xyz);
				Output.interpolatedRay.w = index;

				return Output;
			}

			float4 frag (v2f IN) : SV_Target
			{
				//Initializations.
				//--------------------------------
				float3 inScatter = float3(0.0, 0.0, 0.0);
				float3 nightSky  = float3(0.0, 0.0, 0.0);
				float3 fex = float3(0.0, 0.0, 0.0);
				float  r = length(float3(0.0, _Azure_LightSpeed, 0.0));

				//Original scene.
				//--------------------------------
				float3 screen = tex2D(_MainTex, UnityStereoTransformScreenSpaceTex(IN.uv)).rgb;

				//Reconstruct world space position and direction towards this screen pixel.
			    //--------------------------------
			    float depth = Linear01Depth(UNITY_SAMPLE_DEPTH(tex2D(_CameraDepthTexture,UnityStereoTransformScreenSpaceTex(IN.uv_depth))));
			    if(depth == 1.0) return float4(screen, 1.0);
                float3 viewDir = normalize(depth * IN.interpolatedRay.xyz);
				//float3 viewDir = normalize(mul((float3x3)_Azure_UpMatrix, depth * IN.interpolatedRay.xyz));
				float  sunCosTheta = dot(viewDir, _Azure_SunDirection);

				float mieDepth = saturate(lerp(1.0, depth * 4, _Azure_MieDepth));

				if(_Azure_SunsetColorMode == 0)
				{
					//Optical Depth.
					//--------------------------------
					//float zenith = acos(saturate(dot(float3(0.0, 1.0, 0.0), viewDir)));
					float zenith = acos(saturate(dot(float3(-1.0, 1.0, -1.0), depth))) * _Azure_FogScale;
					float z      = cos(zenith) + 0.15 * pow(93.885 - ((zenith * 180.0) / _Azure_Pi), -1.253);
					float SR     = _Azure_Kr / z;
					float SM     = _Azure_Km / z;

					//Total Extinction.
					//--------------------------------
					fex = exp(-(_Azure_Br*SR  + _Azure_Bm*SM));
					float  sunset = clamp(dot(float3(0.0, 1.0, 0.0), _Azure_SunDirection), 0.0, 0.5);
					float3 extinction = lerp(fex, (1.0 - fex), sunset);

					//Scattering.
					//--------------------------------
					//float  rayPhase = 1.0 + pow(sunCosTheta, 2.0);										 //Preetham rayleigh phase function.
					float  rayPhase = 2.0 + 0.5 * pow(sunCosTheta, 2.0);									 //Rayleigh phase function based on the Nielsen's paper.
					float  miePhase = _Azure_MieG.x / pow(_Azure_MieG.y - _Azure_MieG.z * sunCosTheta, 1.5); //The Henyey-Greenstein phase function.

					float sunRise   = saturate(dot(float3(0.0, 500.0, 0.0), _Azure_SunDirection) / r);

					float3 BrTheta  = _Azure_Pi316 * _Azure_Br * rayPhase * _Azure_RayleighColor.rgb * extinction;
					float3 BmTheta  = _Azure_Pi14  * _Azure_Bm * miePhase * _Azure_MieColor.rgb * extinction * sunRise;
						   BmTheta *= mieDepth;
					float3 BrmTheta = (BrTheta + BmTheta) / (_Azure_Br + _Azure_Bm);

					inScatter  = BrmTheta * _Azure_Scattering * (1.0 - fex);
					inScatter *= sunRise;

					//Night Sky.
					//--------------------------------
					BrTheta  = _Azure_Pi316 * _Azure_Br * rayPhase * _Azure_RayleighColor.rgb;
					BrmTheta = (BrTheta) / (_Azure_Br + _Azure_Bm);
					nightSky = BrmTheta * _Azure_NightIntensity * (1.0 - fex);
				}
				else
					{
						//Optical Depth
						//--------------------------------
						float zenith = acos(length(viewDir.y));
						//float zenith = acos(saturate(dot(float3(-1.0, 1.0, -1.0), depth))) * _Azure_FogScale;
						float z      = cos(zenith) + 0.15 * pow(93.885 - ((zenith * 180.0) / _Azure_Pi), -1.253);
						float SR     = _Azure_Kr / z;
						float SM     = _Azure_Km / z;

						//Total Extinction.
						//--------------------------------
						fex = exp(-(_Azure_Br*SR  + _Azure_Bm*SM));

						//Scattering.
						//--------------------------------
						float  rayPhase = 2.0 + 0.5 * pow(sunCosTheta, 2.0);
						float  miePhase = _Azure_MieG.x / pow(_Azure_MieG.y - _Azure_MieG.z * sunCosTheta, 1.5);

						float sunRise   = saturate(dot(float3(0.0, 500.0, 0.0), _Azure_SunDirection) / r);

						float3 BrTheta  = _Azure_Pi316 * _Azure_Br * rayPhase * _Azure_RayleighColor.rgb;
						float3 BmTheta  = _Azure_Pi14  * _Azure_Bm * miePhase * _Azure_MieColor.rgb * sunRise;
							   BmTheta *= mieDepth;
						float3 BrmTheta = (BrTheta + BmTheta) / (_Azure_Br + _Azure_Bm);

						inScatter  = BrmTheta * _Azure_Scattering * (1.0 - fex);
						inScatter *= sunRise;
						//inScatter *= pow(max(0.5, depth-0.5), 2.0);

						//Night Sky.
						//--------------------------------
						BrmTheta = (BrTheta) / (_Azure_Br + _Azure_Bm);
						nightSky = BrmTheta * _Azure_NightIntensity * (1.0 - fex);
					}

				//Moon Bright.
				//--------------------------------
				float  moonRise    = saturate(dot(float3(0.0, 500.0, 0.0), _Azure_MoonDirection) / r);
				float  bright      = 1.0 + dot(viewDir, -_Azure_MoonDirection);
				float3 moonBright  = 1.0 / (1.0  + bright * _Azure_MoonBrightRange) * _Azure_MoonBrightColor.rgb;
					   moonBright += 1.0 / (_Azure_MoonEmission  + bright * 200.0) * _Azure_MoonColor.rgb;
					   moonBright  = moonBright * moonRise * mieDepth;

				//Output.
				//--------------------------------
				float3 OutputColor  = inScatter + nightSky + moonBright;

				//Tonemapping.
				OutputColor  = saturate(1.0 - exp(-_Azure_Exposure * OutputColor));

				//Color Correction.
				OutputColor = pow(OutputColor, 2.2);
			    #ifdef UNITY_COLORSPACE_GAMMA
			    OutputColor = pow(OutputColor, 0.4545);
				#else
				OutputColor = OutputColor;
    			#endif

				//return float4(OutputColor.rgb, 1.0);

				//Calcule Fog Distance.
				//float dpt = saturate(depth * (_ProjectionParams.z / 5000.0));
				float fog = smoothstep(-_Azure_FogBlend, 1.25, depth * _ProjectionParams.z / _Azure_FogDistance) * _Azure_FogDensity;
				float heightFogDistance = smoothstep(-_Azure_HeightFogBlend, 1.25, depth * _ProjectionParams.z / _Azure_HeightFogDistance);

				//Calcule Height Fog.
				float3 worldSpaceDirection = mul((float3x3)_Azure_UpMatrix, _WorldSpaceCameraPos) + depth * IN.interpolatedRay.xyz;
				float heightFog = saturate((worldSpaceDirection.y - _Azure_HeightFogStart) / (_Azure_HeightFogEnd + _Azure_HeightFogStart));
	                  heightFog = 1.0 - heightFog;
	                  heightFog *= heightFog;
					  heightFog *= heightFogDistance;
					  fog = saturate(fog + heightFog * _Azure_HeightFogDensity);

			//  Start: LuxWater
            	#if defined(LUXWATER_DEFERREDFOG)
					half4 fogMask = tex2D(_UnderWaterMask, UnityStereoTransformScreenSpaceTex(IN.uv));
					float watersurfacefrombelow = DecodeFloatRG(fogMask.ba);

				//	Get distance and lower it a bit in order to handle edge blending artifacts (edge blended parts would not get ANY fog)
					float dist = (watersurfacefrombelow - depth) + _LuxUnderWaterDeferredFogParams.y * _ProjectionParams.w;
				//	Fade fog from above water to below water
					float fogFactor = saturate ( 1.0 + _ProjectionParams.z * _LuxUnderWaterDeferredFogParams.z * dist ); // 0.125 
				//	Clamp above result to where water is actually rendered
					fogFactor = (fogMask.r == 1) ? fogFactor : 1.0;
				//  Mask fog on underwarter parts - only if we are inside a volume (bool... :( )
	                if(_LuxUnderWaterDeferredFogParams.x) {
	                    fogFactor *= saturate( 1.0 - fogMask.g * 8.0);
	                    if (dist < -_ProjectionParams.w * 4 && fogMask.r == 0 && fogMask.g < 1.0) {
	                        fogFactor = 1.0;
	                    }
	                }
				//	Tweak fog factor
					fog *= fogFactor;
            	#endif
        	//  End: LuxWater 

				OutputColor.rgb = lerp(screen.rgb, OutputColor.rgb, fog);
				return float4(OutputColor.rgb, 1.0);
			}
			ENDCG
		}
	}
}