// Stylized Water 3 by Staggart Creations (http://staggart.xyz)
// COPYRIGHT PROTECTED UNDER THE UNITY ASSET STORE EULA (https://unity.com/legal/as-terms)
//    • Copying or referencing source code for the production of new asset store, or public, content is strictly prohibited!
//    • Uploading this file to a public repository will subject it to an automated DMCA takedown request.

#ifndef WATER_LIGHTING_INCLUDED
#define WATER_LIGHTING_INCLUDED

#include "Common.hlsl"
#include "Reflections.hlsl"

#define SPECULAR_POWER_RCP 0.01562 // 1.0/32
#define SPECULAR_STEP_THRESHOLD 0.2

//Reusable for every light
struct TranslucencyData
{
	bool directionalLight;
	float3 subsurfaceColor;
	float3 lightColor;
	float3 lightDir;
	float3 viewDir;
	float3 normal;
	float curvature;
	float mask; //Actually the 'thickness'
	float strength;
	float strengthIncident;
	float exponent;

};

TranslucencyData PopulateTranslucencyData(float3 subsurfaceColor, float3 lightDir, float3 lightColor, float3 viewDir, float3 WorldNormal, float3 worldTangentNormal, float mask, float strength, float incidentStrength, float exponent, float offset, bool directionalLight)
{
	TranslucencyData d = (TranslucencyData)0;
	d.directionalLight = directionalLight;
	d.subsurfaceColor = subsurfaceColor;
	d.lightColor = lightColor;
	d.lightDir = lightDir;

	#if _ADVANCED_SHADING
	//Slightly include high frequency details
	d.normal = normalize(WorldNormal + (worldTangentNormal * 0.2));
	#else
	d.normal = WorldNormal;
	#endif
	d.curvature = offset;
	d.mask = mask; //Shadows, foam, intersection, etc
	d.strength = strength;
	d.strengthIncident = incidentStrength;
	d.viewDir = viewDir;
	d.exponent = exponent;

	return d;
}

//Single channel overlay
float BlendOverlay(float a, float b)
{
	return (b < 0.5) ? 2.0 * a * b : 1.0 - 2.0 * (1.0 - a) * (1.0 - b);
}

//RGB overlay
float3 BlendOverlay(float3 a, float3 b)
{
	return float3(BlendOverlay(a.r, b.r), BlendOverlay(a.g, b.g), BlendOverlay(a.b, b.b));
}

//In URP light intensity is pre-multiplied with the HDR color, extract via magnitude of color "vector"
float GetLightIntensity(float3 lightColor)
{
	//Luminance equals HDR output
	return (lightColor.r * 0.3 + lightColor.g * 0.59 + lightColor.b * 0.11);
}

float GetLightIntensity(Light light) { return GetLightIntensity(light.color); }

void ApplyTranslucency(float3 subsurfaceColor, float3 lightDir, float3 lightColor, float3 viewDir, float3 normal, float occlusion, float strength, float incidentStrength, float exponent, float offset, bool directionalLight, inout float3 emission)
{
	//Coefficient describing how much the surface orientation is between the camera and the direction of/to the light  
	half transmittance = saturate(dot(-viewDir, lightDir));
	//Exponentiate to tighten the falloff
	transmittance = saturate(pow(transmittance, exponent)) * strength;
	
	half incident = 0;
	if(directionalLight)
	{
		incident = saturate(dot(lightDir, normal)) * incidentStrength;
	}

	//Mask by normals facing away from the light (backfaces, in light-space)
	const half curvature = saturate(lerp(1.0, dot(normal, -lightDir), offset));
	transmittance *= curvature;

	const float lightIntensity = saturate(GetLightIntensity(lightColor));

	half attenuation = (transmittance + incident) * occlusion * lightIntensity;

#if _ADVANCED_SHADING
	if(directionalLight)
	{
		//Fade the effect out as the sun approaches the horizon (80 to 90 degrees)
		half sunAngle = saturate(dot(float3(0, 1, 0), lightDir));
		half angleMask = saturate(sunAngle * 10); /* 1.0/0.10 = 10 */
		attenuation *= angleMask;
	}
	
	//Modulate with light color to better match dynamic lighting conditions
	subsurfaceColor = BlendOverlay(saturate(lightColor), subsurfaceColor);
	
	emission += subsurfaceColor * attenuation;
#else //Simple shading
	emission += lerp(emission, subsurfaceColor, attenuation);
#endif
}

void ApplyTranslucency(TranslucencyData translucencyData, inout float3 emission)
{
	ApplyTranslucency(translucencyData.subsurfaceColor, translucencyData.lightDir, translucencyData.lightColor, translucencyData.viewDir, translucencyData.normal, translucencyData.mask, translucencyData.strength, translucencyData.strengthIncident, translucencyData.exponent, translucencyData.curvature, translucencyData.directionalLight, emission);
}

void AdjustShadowStrength(inout Light light, float strength, float vFace)
{
	light.shadowAttenuation = saturate(light.shadowAttenuation + (1.0 - (strength * vFace)));
}

//Specular Blinn-phong reflection in world-space
float3 SpecularReflection(Light light, float3 viewDirectionWS, float3 geometryNormalWS, float3 normalWS, float perturbation, float exponent, float intensity, bool sharp)
{
	//Blend between geometry/wave normals and normals from normal map (aka distortion)
	normalWS = lerp(geometryNormalWS, normalWS, perturbation);

	const float3 halfVec = normalize(light.direction + viewDirectionWS + (normalWS * perturbation));
	half NdotH = saturate(dot(geometryNormalWS, halfVec));

	float specular = pow(NdotH, exponent);
	
	if(sharp)
	{
		specular = step(SPECULAR_STEP_THRESHOLD, specular);
		intensity *= 0.5;
	}
	
	//Attenuation includes shadows, if available
	const float3 attenuatedLightColor = light.color * (light.distanceAttenuation * light.shadowAttenuation);

	//Mask reflection by surfaces "visible" by the light only
	float viewFactor = saturate(dot(geometryNormalWS, light.direction));

	#if _ADVANCED_SHADING
	//Create a linear gradient a little before the cutoff point, in order to maintain HDR values properly
	viewFactor = smoothstep(0.0, 0.15, viewFactor);
	#endif

	float3 specColor = attenuatedLightColor * specular * intensity * viewFactor;
	
	#if UNITY_COLORSPACE_GAMMA
	specColor = LinearToSRGB(specColor);
	#endif

	return specColor;
}

//Based on UniversalFragmentBlinnPhong (no BRDF)
float3 ApplyLighting(inout SurfaceData surfaceData, inout float3 sceneColor, Light mainLight, InputData inputData, WaterSurface water, TranslucencyData translucencyData, float shadowStrength, float vFace, bool isMatchingLightLayer)
{
	if(isMatchingLightLayer) ApplyTranslucency(translucencyData, surfaceData.emission.rgb);

	#if _CAUSTICS
	float causticsAttentuation = 1.0;
	#endif
	
	half3 diffuseColor = 0;
#if !defined(_UNLIT)
	#if _CAUSTICS && !defined(LIGHTMAP_ON)
	if(isMatchingLightLayer)
	{
		causticsAttentuation = GetLightIntensity(mainLight) * (mainLight.distanceAttenuation * mainLight.shadowAttenuation);
	}
	#endif
	
	MixRealtimeAndBakedGI(mainLight, water.diffuseNormal, inputData.bakedGI, shadowStrength.xxxx);

	/*
	//PBR shading
	BRDFData brdfData;
	InitializeBRDFData(surfaceData.albedo, surfaceData.metallic, surfaceData.specular, surfaceData.smoothness, surfaceData.alpha, brdfData);

	half3 diffuseColor = GlobalIllumination(brdfData, inputData.bakedGI, shadowStrength, inputData.water.diffuseNormal, inputData.viewDirectionWS);
	diffuseColor += LightingPhysicallyBased(brdfData, mainLight, water.diffuseNormal, inputData.viewDirectionWS);
	*/

	half3 directLight = 0;
	if(isMatchingLightLayer)
	{
		//Allow shadow strength to be overridden.
		AdjustShadowStrength(mainLight, shadowStrength, vFace);
		
		half3 attenuatedLightColor = mainLight.color * (mainLight.distanceAttenuation * mainLight.shadowAttenuation);
		directLight = LightingLambert(attenuatedLightColor, mainLight.direction, water.diffuseNormal);
	}
	diffuseColor = inputData.bakedGI + directLight;
	
#if _ADDITIONAL_LIGHTS //Per pixel lights
	#ifndef _SPECULARHIGHLIGHTS_OFF
	half specularPower = (_PointSpotLightReflectionSize * SPECULAR_POWER_RCP);
	specularPower = lerp(8.0, 1.0, _PointSpotLightReflectionSize) * _PointSpotLightReflectionStrength;
	#endif
	
	uint pixelLightCount = GetAdditionalLightsCount();
	#if _LIGHT_LAYERS
	uint meshRenderingLayers = GetMeshRenderingLayer();
	#endif

	#if _TRANSLUCENCY
	float translucencyStrength = translucencyData.strength;
	float translucencyExp = translucencyData.exponent;
	#endif
	
	LIGHT_LOOP_BEGIN(pixelLightCount)

		Light light = GetAdditionalLight(lightIndex, inputData.positionWS, shadowStrength.xxxx);	

		#if _LIGHT_LAYERS
		if (IsMatchingLightLayer(light.layerMask, meshRenderingLayers))
		#endif
		{
			#if _ADVANCED_SHADING
			#if _CAUSTICS && !_LIGHT_COOKIES && defined(_ADDITIONAL_LIGHT_CAUSTICS) //Actually want to skip this when using cookies. Since they can be used for caustics instead
			//Light attenuation adds caustics, mask by shadows
			causticsAttentuation += GetLightIntensity(light) * (light.distanceAttenuation * light.shadowAttenuation) * _PointSpotLightReflectionStrength * (1-water.fog);
			#endif
			
			#if _TRANSLUCENCY && defined(_ADDITIONAL_LIGHT_TRANSLUCENCY)
			//Keep settings from main light pass, but override these
			translucencyData.directionalLight = false;
			if(water.vFace > 0)
			{
				translucencyData.lightDir = light.direction;
				translucencyData.lightColor = light.color * light.distanceAttenuation;
				translucencyData.strength = translucencyStrength * light.shadowAttenuation * (water.fog);
				translucencyData.exponent = translucencyExp * light.distanceAttenuation;
				
				ApplyTranslucency(translucencyData, surfaceData.emission.rgb);
			}
			#endif
			#endif

			#if _ADDITIONAL_LIGHT_SHADOWS //URP 11+
			AdjustShadowStrength(light, shadowStrength, vFace);
			#endif

			half3 attenuatedLightColor = light.color * (light.distanceAttenuation * light.shadowAttenuation);
			diffuseColor += LightingLambert(attenuatedLightColor, light.direction, water.diffuseNormal);

			#ifndef _SPECULARHIGHLIGHTS_OFF
			//Note: View direction fetched again using the function that takes orthographic projection into account
			surfaceData.specular += SpecularReflection(light, normalize(GetWorldSpaceViewDir(inputData.positionWS)), water.waveNormal, water.tangentWorldNormal, _PointSpotLightReflectionDistortion, lerp(4096, 64, _PointSpotLightReflectionSize), specularPower, _PointSpotLightReflectionSharp);
		#endif
	}
	LIGHT_LOOP_END
#endif

#ifdef _ADDITIONAL_LIGHTS_VERTEX //Previous calculated in vertex stage
	diffuseColor += inputData.vertexLighting;
#endif

#else //Unlit
	diffuseColor = 1.0;
#endif

	#if _CAUSTICS
	surfaceData.emission.rgb += water.caustics * causticsAttentuation * vFace;
	#endif

	float3 color = (surfaceData.albedo.rgb * diffuseColor) + surfaceData.emission.rgb + surfaceData.specular;
	
	#ifndef _ENVIRONMENTREFLECTIONS_OFF
	//Reflections blend in on top of everything
	color = lerp(color, water.reflections.rgb, water.reflectionMask * water.reflectionLighting * vFace);
	#endif

	#if _REFRACTION
	//Ensure the same effects are applied to the underwater scene color. Otherwise not visible on clear water
	sceneColor += (surfaceData.emission.rgb + surfaceData.specular) * vFace;
	#endif
	
	//Debug
	//return float4(surfaceData.emission.rgb, 1.0);	
	
	return color;
}

//Color of light ray passing through the water, hitting the sea floor (extinction)
//This applies to the scene color
float LightExtinction(float verticalDepth, float viewDepth, float density)
{
	return exp(-density * (verticalDepth + viewDepth));
}

//Energy loss of ray, as it travels deeper and scatters (absorption)
//This applies to the color of the underwater fog
float LightAbsorption(float absorption, float viewDepth)
{
	return saturate(exp(-absorption * viewDepth));
}
#endif