Skip to content
Snippets Groups Projects
WaterShader.shader 8.46 KiB
Newer Older
Oliphan's avatar
Oliphan committed
Shader "Custom/WaterShader"
Liam Robinson's avatar
Liam Robinson committed
{
    Properties
    {
		_DeepColor("Deep Color", Color) = (1,1,1,1)
		_ShallowColor("Shallow Color", Color) = (1,1,1,1)
		_ShallowThreshold("Shallow Threshold", Range(0,5)) = 0.5
		_ShallowTint("Shallow Tint", Range(0,1)) = 0.5
		_FoamColor("Foam Color", Color) = (1,1,1,1)
		_FoamTex("Foam Texture", 2D) = "white" {}
		_FoamSpeed("Foam Speed", Vector) = (0.005, 0.02, -0.04 ,0.07)
		_FoamThreshold("Foam Threshold", Range(-5,5)) = 0.5
		_FoamHardness("Foam Hardness", Range(0,25)) = 0.5
		_CausticsTex("Caustics Texture", 2D) = "white" {}
		_CausticsSpeed("Caustics Speed", Vector) = (0.005, 0.02, -0.04 ,0.07)
		_WaveSpeed("Wave Speed", Range(-5,5)) = 0.5
Oliphan's avatar
Oliphan committed
		_WaveHeight("Wave Height", Range(-5,5)) = 1
		_WaveA("Wave A (dir, steepness, wavelength)", Vector) = (1,0,0.5,10)
		_WaveB("Wave B", Vector) = (0,1,0.25,20)
		_WaveC("Wave C", Vector) = (1,1,0.15,10)
Oliphan's avatar
Oliphan committed
		_NormalTex("Normals Texture", 2D) = "bump" {}
		_ReflectionStrength("Reflection Strength", Range(0,1)) = 0.5
Oliphan's avatar
Oliphan committed
		_ReflectionDeform("Reflection Deform", Range(-0.2,0.2)) = -0.02
		_RefractionDeform("Refraction Deform", Range(-0.2,0.2)) = 0.02
		_Caustics("Caustics", Range(0,5)) = 1
		[HideInInspector]_ReflectionTex("Reflection", 2D) = "white" {}
		//Render during the transparrent pass so that opaque objects under the water will render first
Oliphan's avatar
Oliphan committed
		Tags { "Queue" = "Transparent" "RenderType" = "Transparent" "LightMode"="ForwardBase"}
		//Don't write to the depth buffer so that we can get correct depth information for objects under the water
		//Grab colour from opaque screen pass behind water
		GrabPass { "_UnderWater" }
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
			#include "GerstnerWave.cginc"
Oliphan's avatar
Oliphan committed
			#include "UnityLightingCommon.cginc"
Oliphan's avatar
Oliphan committed
				float3 distortionNormal : NORMAL0;
				float3 viewDir : NORMAL2;
Oliphan's avatar
Oliphan committed
				float4 screenPos : TEXCOORD1;
				half3 tspace0 : TEXCOORD2; // tangent.x, binormal.x, normal.x
                half3 tspace1 : TEXCOORD3; // tangent.y, binormal.y, normal.y
                half3 tspace2 : TEXCOORD4; // tangent.z, binormal.z, normal.z
            };
			
			fixed4 _DeepColor, _ShallowColor, _FoamColor, _FoamSpeed, _CausticsSpeed, _WaveA, _WaveB, _WaveC;
Oliphan's avatar
Oliphan committed
			sampler2D _CausticsTex, _FoamTex, _NormalTex, _ReflectionTex, _CameraDepthTexture, _UnderWater, _CameraNormalsTexture;
			float _WaveSpeed, _WaveHeight, _ShallowThreshold, _ShallowTint, _FoamThreshold, _ReflectionStrength, _ReflectionDeform, _RefractionDeform, _FoamHardness, _Caustics;
			float4 _CausticsTex_ST, _FoamTex_ST, _NormalTex_ST, _CameraDepthTexture_TexelSize;
            v2f vert (appdata_full v)
            {
                v2f o;
				//Generate Waves
				float3 gridPoint = v.vertex.xyz;
				float3 tangent = 0;
				float3 binormal = 0;
				float3 p = gridPoint;
Oliphan's avatar
Oliphan committed
				p += GerstnerWave(_WaveA, gridPoint, tangent, binormal, _WaveSpeed, _WaveHeight);
				p += GerstnerWave(_WaveB, gridPoint, tangent, binormal, _WaveSpeed, _WaveHeight);
				p += GerstnerWave(_WaveC, gridPoint, tangent, binormal, _WaveSpeed, _WaveHeight);
				//Calculate without binormal and tangent plane setup for water distortion to prevent shifting
				o.distortionNormal = normalize(cross(binormal, tangent));
				binormal.z +=1;
				tangent.x+=1;
				//Calculate with binormal and tangent plane setup for proper lighting calculation
				float3 normal = normalize(cross(binormal, tangent));
				o.tspace0 = half3(tangent.x, binormal.x, normal.x);
                o.tspace1 = half3(tangent.y, binormal.y, normal.y);
                o.tspace2 = half3(tangent.z, binormal.z, normal.z);
Oliphan's avatar
Oliphan committed
				o.viewDir = normalize(ObjSpaceViewDir ( v.vertex ));
				o.vertex = UnityObjectToClipPos(v.vertex);
				//Calculate screen position and pass to frag shader
				o.screenPos = ComputeScreenPos(o.vertex);
				o.uv = v.texcoord;
                return o;
            }
			/// <summary>
			/// Gets the distance that water must travel through the water at a given point to reach the eye
			/// </summary>
			float GetWaterDepth(float4 screenPos, float2 screenUV) {
				float backgroundDepth = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, screenUV));
				float surfaceDepth = UNITY_Z_0_FAR_FROM_CLIPSPACE(screenPos.z);
				return backgroundDepth - surfaceDepth;
			}
			fixed3 frag(v2f i) : SV_Target
Oliphan's avatar
Oliphan committed
				//Prep caustics UVs with proper scaling from material
				float2 caustUV1 = TRANSFORM_TEX(i.uv, _CausticsTex);
				float2 caustUV2 = caustUV1;
Oliphan's avatar
Oliphan committed
				//Distort caustics based on depth texture texel size to make caustics look "underwater"
				caustUV1.y += _CameraDepthTexture_TexelSize.z * abs(_CameraDepthTexture_TexelSize.y);
				caustUV2.y += _CameraDepthTexture_TexelSize.z * abs(_CameraDepthTexture_TexelSize.y);
				//Pan caustics sampling UVs across one another to produce realistic looking caustics patterns
				caustUV1.xy = (caustUV1.xy + _CausticsSpeed.xy * _Time.y);
				caustUV2.xy = (caustUV2.xy + _CausticsSpeed.zw * _Time.y);
Oliphan's avatar
Oliphan committed
				//Rotate Caustic UV 2 so it can't line up perfectly and break immersion
				float tmp = caustUV2.x;
				caustUV2.x = caustUV2.y;
				caustUV2.y = tmp;
				//Combine geometry and normal map normals
				fixed3 waveNorms1 = UnpackNormal(tex2D(_NormalTex, caustUV1));
				fixed3 waveNorms2 = UnpackNormal(tex2D(_NormalTex, caustUV2));
				fixed3 waveNormsCombined = normalize(waveNorms1 + waveNorms2);
				//Calculate world space normals for lighting
				fixed3 worldNormal;
				worldNormal.x = dot(i.tspace0, waveNormsCombined);
                worldNormal.y = dot(i.tspace1, waveNormsCombined);
                worldNormal.z = dot(i.tspace2, waveNormsCombined);
				//Calculate screen space normals for distortions
				fixed3 screenNormal = mul((float3x3)UNITY_MATRIX_V, waveNormsCombined);
Oliphan's avatar
Oliphan committed
				//Sample caustics textures with normal distortion
				fixed3 caustic1 = tex2D(_CausticsTex, caustUV1 - normalize(screenNormal + i.distortionNormal)*_RefractionDeform).rgb;
				fixed3 caustic2 = tex2D(_CausticsTex, caustUV2 - normalize(screenNormal + i.distortionNormal)*_RefractionDeform).rgb;
Oliphan's avatar
Oliphan committed
				//Combine samples
Oliphan's avatar
Oliphan committed
				// dot product between normal and light direction for
                // standard diffuse (Lambert) lighting
                half nl = max(0, dot(worldNormal, _WorldSpaceLightPos0.xyz));
				// factor in the light color
                half lightingcol = nl * _LightColor0;

				//Calculate refraction and reflection UV offsets
				float3 refractionOffset = normalize(screenNormal + i.distortionNormal) * _RefractionDeform * abs(_WaveHeight);
				float3 reflectionOffset = normalize(screenNormal + i.distortionNormal) * _ReflectionDeform * abs(_WaveHeight);
Oliphan's avatar
Oliphan committed

				//Calculate screen space UV position
				float2 screenUV = i.screenPos.xy / i.screenPos.w;

				//Get opaque pass depth at current fragment with water distortion for refraction and without for foam
				float depth = GetWaterDepth(i.screenPos, screenUV);
				float distortedDepth = GetWaterDepth(i.screenPos, screenUV + refractionOffset);

				// Initialize the output color with colour grabbed from behind water screen pass with water distortion
				fixed3 col = tex2D(_UnderWater, screenUV + refractionOffset).rgb;

				//Calculate water depth fog and color tint
Oliphan's avatar
Oliphan committed
				float fogRatio = saturate(distortedDepth * _ShallowThreshold);
				col.rgb *= (1 - _ShallowTint);
				col.rgb += _ShallowColor * _ShallowTint;
				col.rgb *= (1 - fogRatio);
				col.rgb += _DeepColor * fogRatio;
				//Apply caustics with a bias to the opposite of light direction
				col.rgb += causticrgb * (1-lightingcol) * _Caustics;
Oliphan's avatar
Oliphan committed
				//Calculate fresnel effect
				float fresnel = 1 - saturate ( dot (worldNormal, i.viewDir ) );
				//Add reflections using distorted sampling based on normals and blending based on fresnel
				col.rgb *= 1 - (_ReflectionStrength * fresnel);
				col.rgb += tex2D(_ReflectionTex, screenUV + reflectionOffset).rgb * _ReflectionStrength * fresnel;
				//Add foam after reflections
				float2 foamUV1 = TRANSFORM_TEX(i.uv, _FoamTex);
				float2 foamUV2 = foamUV1 + _FoamSpeed.zw * _Time.y;
				foamUV1.xy += _FoamSpeed.xy * _Time.y;
				float foamAmt = 1 - saturate((depth - _FoamThreshold) * _FoamHardness);
				foamAmt *= saturate(tex2D(_FoamTex, foamUV1).r * tex2D(_FoamTex, foamUV2).r * _FoamHardness);
Oliphan's avatar
Oliphan committed
				col.rgb += _FoamColor * foamAmt;