﻿Shader "DSS/Raytraced/RaytraceOSS"
{
	Properties
	{
		_Tint ("Tint Color", Color) = (.5, .5, .5, .5)
		[Gamma] _Exposure ("Exposure", Range(0, 8)) = 1.0
		[Gamma] _BackgroundExposure ("Exposure (BG)", Range(0, 8)) = 1
		[Gamma] _StarsExposure ("Exposure (Stars)", Range(0, 8)) = 1
		_Rotation ("RotationY (Longitude)", Range(0, 360)) = 0
		_RotationX ("RotationX (Latitude)", Range(0, 360)) = 0

		_AmbientStr ("Ambient Light Strength", Range(0, 1)) = 0

		[Header(Base Skybox)]
		[Space(5)]
		[NoScaleOffset] _GalaxyPanorama ("Galaxy Panorama", 2D) = "grey" {}

		[Header(Planet 1)]
		[Space(5)]
		_Radius1 ("Radius", float) = 600
		_Pos1 ("Position", Vector) = (0, 0, 0, 0)
		[NoScaleOffset] _Tex1 ("Texture", 2D) = "black" {}
		_Rot1 ("Rotation", Range(0, 1)) = 0
		[Toggle] _HasAtmo1 ("Has Atmosphere", Int) = 0
		_AtmoHeight1 ("Atmo Height", float) = 100
		_Hr1 ("HR", float) = 10
		_Hm1 ("HM", float) = 1.2
		_BetaR1 ("Beta R", Vector) = (0.0057, 0.0135, 0.0331, 1)
		_BetaM1 ("Beta M", Vector) = (0.004, 0.004, 0.004, 1)
		_MieG1 ("Mie G", float) = 0.85

		[Header(Planet 2)]
		[Space(5)]
		_Radius2 ("Radius", float) = 600
		_Pos2 ("Position", Vector) = (0, 0, 0, 0)
		[NoScaleOffset] _Tex2 ("Texture", 2D) = "black" {}
		[NoScaleOffset] _Norm2 ("Normal", 2D) = "black" {}
		_Rot2 ("Rotation", Range(0, 1)) = 0

		[Header(Planet 3)]
		[Space(5)]
		_Radius3 ("Radius", float) = 600
		_Pos3 ("Position", Vector) = (0, 0, 0, 0)
		[NoScaleOffset] _Tex3 ("Texture", 2D) = "black" {}
		[NoScaleOffset] _Norm3 ("Normal", 2D) = "black" {}
		_Rot3 ("Rotation", Range(0, 1)) = 0

		[Header(Planet 4)]
		[Space(5)]
		_Radius4 ("Radius", float) = 600
		_Pos4 ("Position", Vector) = (0, 0, 0, 0)
		[NoScaleOffset] _Tex4 ("Texture", 2D) = "black" {}
		[NoScaleOffset] _Norm4 ("Normal", 2D) = "black" {}
		_Rot4 ("Rotation", Range(0, 1)) = 0

		[Header(Planet 5)]
		[Space(5)]
		_Radius5 ("Radius", float) = 600
		_Pos5 ("Position", Vector) = (0, 0, 0, 0)
		[NoScaleOffset] _Tex5 ("Texture", 2D) = "black" {}
		[NoScaleOffset] _Norm5 ("Normal", 2D) = "black" {}
		_Rot5 ("Rotation", Range(0, 1)) = 0

		[HideInInspector] _CamRot ("Camera rotation", float) = 0
		[HideInInspector] _LightDirectionA ("Atmo light dir", Vector) = (1, 0, 0, 0)
		[HideInInspector] _LightDirectionB ("Atmo light dir", Vector) = (1, 0, 0, 0)

		[Space(5)]
		[Header(Local atmo options)]
		[Space(5)]
		_PlanetRadiusL ("Planet Radius", float) = 600
		_AtmoHeightL ("Atmo Height", float) = 100
		_HrL ("HR", float) = 10
		_HmL ("HM", float) = 1.2
		_BetaRL ("Beta R", Vector) = (0.0057, 0.0135, 0.0331, 1)
		_BetaML ("Beta M", Vector) = (0.004, 0.004, 0.004, 1)
		_MieGL ("Mie G", float) = 0.85
		_HeightOffset ("Height Offset", Range(0.00001, 0.1)) = 0

		[Space(5)]
		[Header(Atmo common options)]
		[Space(5)]
		_Samples ("Samples", Int) = 12
		_SamplesLight ("Light Samples", Int) = 6
		_LightStrength ("Light Strength", Range(1, 200)) = 20
		[Toggle] _ColorCorr ("Color correction", Int) = 0

		[Toggle(VALAYA_SHADOW)] _ValayaShadow ("Valaya shadow cast", Int) = 0

		[Space(2)]
		[Header(Stars)]
		[Space(2)]
		[NoScaleOffset] _StarsTex ("Stars Data Texture", 2D) = "black" {}
		_HaloThreshold ("Halo Threshold Magnitude", float) = 1
		_MaxHalo ("Max Halo Magnitude", float) = 50
		_StarScale ("Star Scale", float) = 2.5
		_HaloBrightness ("Halo Brightness", Range(0, 1)) = 0.1
	}
	SubShader
	{
		Tags { "Queue"="Background" "RenderType"="Background" "PreviewType"="Skybox" }
		Cull Off ZWrite Off

		Pass
		{
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#pragma target 3.0
			#pragma shader_feature __ VALAYA_SHADOW

			#include "UnityCG.cginc"
			#include "usefulMaths.cginc"
			#include "starPass.cginc"

			struct appdata_t {
				float4 vertex : POSITION;
				float2 texcoord : TEXCOORD0;
				UNITY_VERTEX_INPUT_INSTANCE_ID
			};

			struct v2f {
				float4 vertex : SV_POSITION;
				float2 texcoord : TEXCOORD0;
				UNITY_VERTEX_OUTPUT_STEREO
				float3 rd : TEXCOORD1;
				float3 rdi : TEXCOORD2;
				float3 ro : TEXCOORD3;
			};

			half3 renderPlanet(float3 res, float3 rd, float3 pos, sampler2D tex, sampler2D norm, float rot, float3 lightDir, uint noNM);
			float3 computeIncidentLight(float3 a, float3 b, float3 ro, float3 rd, float3 lightDir, float lightStrength, float3 planetPos, float planetRadius, float atmoRadius, float hr, float hm, float mieG, float3 betaR, float3 betaM, int test0, float haziness);
			half3 sampleSkybox(float3 rd);

			half4 _Tint;
			half _Exposure;
			float _Rotation;
			float _RotationX;
			float _AmbientStr;

			float _Radius1;
			float4 _Pos1;
			sampler2D _Tex1;
			float _Rot1;
			int _HasAtmo1;
			float _AtmoHeight1;
			float _Hr1;
			float _Hm1;
			float4 _BetaR1;
			float4 _BetaM1;
			float _MieG1;

			float _Radius2;
			float4 _Pos2;
			sampler2D _Tex2;
			sampler2D _Norm2;
			float _Rot2;

			float _Radius3;
			float4 _Pos3;
			sampler2D _Tex3;
			sampler2D _Norm3;
			float _Rot3;

			float _Radius4;
			float4 _Pos4;
			sampler2D _Tex4;
			sampler2D _Norm4;
			float _Rot4;

			float _Radius5;
			float4 _Pos5;
			sampler2D _Tex5;
			sampler2D _Norm5;
			float _Rot5;

			float _PlanetRadiusL;
			float _AtmoHeightL;
			float _HrL;
			float _HmL;
			float4 _BetaRL;
			float4 _BetaML;
			float _MieGL;
			float _HeightOffset;

			int _Samples;
			int _SamplesLight;
			float _LightStrength;
			int _ColorCorr;

			float _CamRot;
			float4 _LightDirectionA;
			float4 _LightDirectionB;

			float3 RotateAroundYInDegrees(float3 vertex, float degrees) {
				float alpha = degrees * UNITY_PI / 180.0;
				float sina, cosa;
				sincos(alpha, sina, cosa);
				float2x2 m = float2x2(cosa, -sina, sina, cosa);
				return float3(mul(m, vertex.xz), vertex.y).xzy;
			}
			
			float3 RotateAroundXInDegrees(float3 vertex, float degrees) {
				float alpha = degrees * UNITY_PI / 180.0;
				float sina, cosa;
				sincos(alpha, sina, cosa);
				float2x2 m = float2x2(cosa, -sina, sina, cosa);
				return float3(mul(m, vertex.zy), vertex.x).zyx;
			}

			v2f vert (appdata_t v) {
				v2f o;
				UNITY_SETUP_INSTANCE_ID(v);
				UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
				float3 rotated = RotateAroundYInDegrees(v.vertex, _Rotation);
				rotated = RotateAroundXInDegrees(rotated, _RotationX);
				o.vertex = UnityObjectToClipPos(rotated);
				o.texcoord = v.texcoord;
				o.rdi = -normalize(ObjSpaceViewDir(float4(-rotated.xzy, 1)));
				o.rd = RotateAroundYInDegrees(v.vertex.xzy, _CamRot);
				//o.ro = _WorldSpaceCameraPos;
				o.ro = float3(0, 0, -_PlanetRadiusL - _HeightOffset * _AtmoHeightL - max(0, _WorldSpaceCameraPos.y / 1000));
				return o;
			}

			half4 frag(v2f i) : SV_Target {
				i.rd = normalize(i.rd);
				float3 lightDir = -_LightDirectionB;
				half3 col = 0;

				if(abs(i.rd.y) < 0.2) {
					float closest = 10000000000000.0;
					int closestIdx = 0;

					float3 res1 = raySphereIntersection(_Pos1.xyz, i.rd, _Radius1);
					float3 res2 = raySphereIntersection(_Pos2.xyz, i.rd, _Radius2);
					float3 res3 = raySphereIntersection(_Pos3.xyz, i.rd, _Radius3);
					float3 res4 = raySphereIntersection(_Pos4.xyz, i.rd, _Radius4);
					float3 res5 = raySphereIntersection(_Pos5.xyz, i.rd, _Radius5);

					if(res1.z > 0) {
						closest = res1.x;
						closestIdx = 1;
					}
					if(res2.z > 0 && res2.x < closest) {
						closest = res2.x;
						closestIdx = 2;
					}
					if(res3.z > 0 && res3.x < closest) {
						closest = res3.x;
						closestIdx = 3;
					}
					if(res4.z > 0 && res4.x < closest) {
						closest = res4.x;
						closestIdx = 4;
					}
					if(res5.z > 0 && res5.x < closest) {
						closest = res5.x;
						closestIdx = 5;
					}

					switch(closestIdx) {
						default:
							col = sampleSkybox(i.rd) + starPass(i.rd);
							break;
						case 1:
							col = renderPlanet(res1, i.rd, _Pos1, _Tex1, _Tex1, _Rot1, lightDir, 1);
							break;
						case 2:
							col = renderPlanet(res2, i.rd, _Pos2, _Tex2, _Norm2, _Rot2, lightDir, 0);
							break;
						case 3:
							col = renderPlanet(res3, i.rd, _Pos3, _Tex3, _Norm3, _Rot3, lightDir, 0);
							break;
						case 4:
							col = renderPlanet(res4, i.rd, _Pos4, _Tex4, _Norm4, _Rot4, lightDir, 0);
							break;
						case 5:
							col = renderPlanet(res5, i.rd, _Pos5, _Tex5, _Norm5, _Rot5, lightDir, 0);
							break;
					}

					if(_HasAtmo1) {
						float3 res = raySphereIntersection(_Pos1.xyz, i.rd, _Radius1 + _AtmoHeight1);
						if(res.z > 0 && res.x < closest) {
							float3 atmoColor = computeIncidentLight(
								res1,
								res,
								0,
								i.rd,
								lightDir,
								_LightStrength,
								_Pos1.xyz,
								_Radius1,
								_Radius1 + _AtmoHeight1, //Atmo radius
								_Hr1,
								_Hm1,
								_MieG1,
								_BetaR1.xyz * _BetaR1.w,
								_BetaM1.xyz * _BetaM1.w,
								0,
								1
							);
							if(atmoColor.x < 10000) {
								col = (float3(1, 1, 1) - col.rgb) * atmoColor + col;
							}
						}
					}
				}else {
					col = sampleSkybox(i.rd) + starPass(i.rd);
				}

				float3 localAtmoColor = computeIncidentLight(
					0,
					0,
					i.ro + float3(0, 0, -0.01),
					i.rdi,
					-_LightDirectionA,
					_LightStrength * 2,
					0,
					_PlanetRadiusL,
					_PlanetRadiusL + _AtmoHeightL, //Atmo radius
					_HrL,
					_HmL,
					_MieGL,
					_BetaRL.xyz * _BetaRL.w,
					_BetaML.xyz * _BetaML.w,
					1,
					0
				);
				float3 isecta = raySphereIntersection(-i.ro, i.rdi, _PlanetRadiusL);
				if(isecta.z > 0) {
					col = float3(0.741,0.776,0.784);
					float3 normal = -i.ro + isecta.x * i.rdi;
					normal = normalize(normal);
					col *= max(0, dot(normal, _LightDirectionA));
				}

				if(localAtmoColor.x < 10000) {
					if(_ColorCorr) {
						localAtmoColor.r = log(localAtmoColor.r + 1);
						localAtmoColor.g = log(localAtmoColor.g + 1);
						localAtmoColor.b = log(localAtmoColor.b + 1);
					}
					col = (float3(1, 1, 1) - col.rgb) * localAtmoColor + col;
				}

				return half4(col * _Tint.rgb * unity_ColorSpaceDouble.rgb, 1);
			}

			half3 renderPlanet(float3 res, float3 rd, float3 pos, sampler2D tex, sampler2D norm, float rot, float3 lightDir, uint noNM) {
				half3 isect = normalize((res.x * rd) - pos.xyz);

				float2 uv = xyzToLatlon(isect).yx / float2(UNITY_PI * 2, UNITY_PI);
				uv.x += rot;

				half3 col = tex2D(tex, uv);
				float3 normal;
				if(noNM) normal = isect;
				else {
					normal = tex2D(norm, uv) * 2 - 1;

					float alpha = rot * UNITY_PI * 2;
					float sina, cosa;
					sincos(alpha, sina, cosa);
					float wxn = normal.x * cosa + normal.z * sina;
					normal.z = -normal.x * sina + normal.z * cosa;
					normal.x = wxn;
				}

				half lightLevel = dot(normal, lightDir);
				lightLevel = max(_AmbientStr, lightLevel);
				col *= lightLevel;

#ifdef VALAYA_SHADOW
				if(!noNM) {
					float3 isectPos = res.x * rd;
					float3 isectValaya = raySphereIntersection(_Pos1 - isectPos, lightDir, _Radius1);
					if(isectValaya.z > 0) col = 0;
				}
#endif

				return col;
			}

			float3 computeIncidentLight(float3 a, float3 b, float3 ro, float3 rd, float3 lightDir, float lightStrength, float3 planetPos, float planetRadius, float atmoRadius, float hr, float hm, float mieG, float3 betaR, float3 betaM, int test0, float haziness) {
				float tmin = 0;
				float tmax = 10000000.0;

				rd = normalize(rd);
				float3 temp3 = planetPos - ro;
				uint hitsSurface = 0;
				if(a.z == 0) a = raySphereIntersection(temp3, rd, planetRadius);
				if(a.z > 0 && a.y > 0) {
					tmax = max(0, a.x);
					hitsSurface = 1;
				}

				if(b.z == 0) b = raySphereIntersection(temp3, rd, atmoRadius);
				if(b.z < 0 || b.y < 0) return float3(10100, 10100, 10100);
				if(b.x > tmin && b.x > 0) tmin = b.x;
				if(b.y < tmax) tmax = b.y;

				float segmentLength = (tmax - tmin) / _Samples;
				float segmentLengthLight;
				float tCurrent = tmin;
				float tCurrentLight;
				float3 sumR = 0;
				float3 sumM = 0;
				float opticalDepthR = 0;
				float opticalDepthM = 0;

				float temp2 = mieG * mieG;
				float mu = dot(lightDir, rd);
				float temp = 1 + mu * mu;
				float phaseR = 3.0 / (16.0 * UNITY_PI) * temp;
				float phaseM = 3.0 / (8.0 * UNITY_PI) * ((1.0 - temp2) * temp) / ((2.0 + temp2) * pow(1.0 + temp2 - 2.0 * mieG * mu, 1.5));
				float3 pos, posLight;
				float Hr_a = hr;
				float Hm_a = hm;
				betaR = betaR * 0.01;
				betaM = betaM * 0.01;

				int j;
				for(int i = 0; i < _Samples; i++) {
					pos = ro + (tCurrent + segmentLength * 0.5) * rd;

					a = raySphereIntersection(planetPos - pos, lightDir, atmoRadius);
					if(a.z < 0) {
						tCurrent += segmentLength;
						continue;
					}
					segmentLengthLight = a.y / _SamplesLight, tCurrentLight = 0;
					float opticalDepthLightR = 0;
					float opticalDepthLightM = 0;
					float3 startPosLight = pos;
					for(j = 0; j < _SamplesLight; j++) {
						posLight = startPosLight + (tCurrentLight + segmentLengthLight * 0.5) * lightDir;
						float height = length(planetPos - posLight) - planetRadius;
						if(height < 0){ j = -5; break; }

						float hrLight = exp(-height / Hr_a) * segmentLengthLight;
						float hmLight = exp(-height / Hm_a) * segmentLengthLight;
						opticalDepthLightR += hrLight;
						opticalDepthLightM += hmLight;

						tCurrentLight += segmentLengthLight;
					}
					if(j != -5) {
						float height = length(planetPos - pos) - planetRadius;
						float hrr = exp(-height / Hr_a) * segmentLength;
						float hmm = exp(-height / Hm_a) * segmentLength;
						opticalDepthR += hrr;
						opticalDepthM += hmm;
						float3 tau = betaR * (opticalDepthLightR * 700 + opticalDepthR) + betaM * (opticalDepthLightM * 700 + opticalDepthM);
						tau = exp(-tau);
						sumR += tau * hrr;
						sumM += tau * hmm;
					}
					tCurrent += segmentLength;
				}
				if(hitsSurface) lightStrength *= haziness + 1;
				return float3(sumR * betaR * phaseR + sumM * betaM * phaseM) * lightStrength * 10.0;
			}

			sampler2D _GalaxyPanorama;
			half4 _GalaxyPanorama_HDR;
			half _BackgroundExposure;

			half3 sampleSkybox(float3 rd) {
				half3 cg;

				//rd = normalize(rd);
				float2 uv = float2(
					atan2(rd.z, rd.x) / (2 * UNITY_PI) + 0.5,
					acos(rd.y) / UNITY_PI
				);

				half4 tex = tex2D(_GalaxyPanorama, uv);
				cg = DecodeHDR(tex, _GalaxyPanorama_HDR);

				cg *= _BackgroundExposure;

				return cg;
			}
			ENDCG
		}
	}
}
