Ocean Update #4

I added in some simple blended normal map support, just to see how things were looking and got somewhat carried away…

This looked alright with some temporary normal maps lying in my project, so I went about adding in some displacement with a simple height map. 

I also experimented a bit with the depth buffer and used some of those results to start adjusting colour on wave peaks, not to anything logical right now, mind you, just seeing what results turn out to be interesting.

The next plan is to get some depth fog implemented, then reflections and refractions.

Thereafter I’ll probably dive into getting foam maps added – I want three controls for these:

  1. Foam on intersection – so we get a nice outline of objects that the water touches
  2. Foam on wave peaks – a subtle addition I can control when things get stormy
  3. General sea foam – this can be controlled to be more prevalent when really storm and the ocean is getting churned up.

After all that? Plan is to get a more final set of assets and a scene together, and start finding a way of doing dynamic splashes – no idea if I’ll do this with particles, or what yet.

As always, code below, though this is a more experimental dump:

Shader "Custom/Waves" {
	Properties{
		[Header(Standard Properties)]
		_Color("Color", Color) = (1,1,1,1)
		_MainTex("Albedo (RGB)", 2D) = "white" {}
		_Glossiness("Smoothness", Range(0,1)) = 0.5
		_SpecColor("Specular Color", Color) = (1,1,1,1)
		_BumpMap("Normal Map", 2D) = "bump" {}
		_BumpMap2("Normal Map 2", 2D) = "bump" {}
		[Header(Water Surface Properties)]
		_FoamColor("Foam Color", Color) = (1,1,1,1)
		_FoamTex("Foam Texture (RGB)", 2D) = "white" {}
		_FoamFade("Foam Intersection Falloff", Range(0.01,10)) = 1
		[Header(Water Surface Properties)]
		_FogDepth("Fog Depth", Float) = 50
		_FogColor("Fog Color", Color) = (1,1,1,1)
		[Header(Wave Properties)]
		[Tooltip(dir xy, steepness, wavelength)]
		_WaveA("Wave A", Vector) = (1,0,0.5,10)
		_WaveB("Wave B", Vector) = (0,1,0.25,20)
		_WaveC("Wave C", Vector) = (1,1,0.15,10)
		[Tooltip(wave A speed, wave B speed, wave C speed, final multiplier)]
		_WaveSpeed("Wave Speed", Vector) = (1,1,1,1)
		_WavePeakDepth("Wave Peak Depth", Float) = -1
		_WavePeakColor("Wave Peak Color", Color) = (1,1,1,1)
		[Header(Tessellation Properties)]
		_ParallaxMap("Surface Detail Height Map", 2D) = "grey" {}
		_ParallaxOffset("Height Adjust", Float) = 0.1
		_Tess("Tessellation", Range(1,32)) = 1
	}

	SubShader{
		Tags{ "RenderType" = "Transparent" "Queue" = "Transparent" }
		Blend SrcAlpha OneMinusSrcAlpha
		ZWrite Off
		Cull Back
		LOD 200
		CGPROGRAM
		#pragma surface surf StandardSpecular fullforwardshadows vertex:vert tessellate:tess addshadow alpha:fade
		#pragma target 4.6
		#include "Tessellation.cginc"

		sampler2D _MainTex, _FoamTex, _CameraDepthTexture, _ParallaxMap, _BumpMap, _BumpMap2;
		half _Glossiness, _Tess, _FoamFade, _ParallaxOffset, _FogDepth, _WavePeakDepth;
		fixed4 _Color, _FoamColor, _WaveA, _WaveB, _WaveC, _WaveSpeed, _WavePeakColor, _FogColor;

		struct Input {
			float2 uv_MainTex;
			float2 uv2_FoamTex;
			float4 color : COLOR;
			float4 screenPos;
			float3 worldPos;
		};

		float4 tess() {
			return _Tess;
		}

		float3 GerstnerWave(float4 wave, float3 p, inout float3 tangent, inout float3 binormal, float speedModifier) {
			float k = 2 * UNITY_PI / wave.w;
			float c = sqrt(9.81 / k);
			float2 d = normalize(wave.xy);
			float f = k * (dot(d, p.xz) - c * _Time.y * speedModifier *_WaveSpeed.w);
			float a = wave.z / k;

			/*
			> sin(f), cos(f) are used a few times in the final calulations, so pre-clculate them once
			> We also multiply these a few times by steepness, so pre-calculate these as well
			> Store it as a float4 to keep memory footprint small
			> It's a bit tricky to read, but the gains are worthwhile
			*/
			float4 sinFCosF = float4(sin(f), cos(f), 0, 0);
			sinFCosF.z = sinFCosF.x * wave.z;
			sinFCosF.w = sinFCosF.y * wave.z;

			tangent += float3(
				-d.x * d.x * sinFCosF.z,
				d.x * sinFCosF.w,
				-d.x * d.y * sinFCosF.z
				);
			binormal += float3(
				-d.x * d.y * sinFCosF.z,
				d.y * sinFCosF.w,
				-d.y * d.y * sinFCosF.z
				);
			return float3(
				d.x * (a * sinFCosF.y),
				a * sinFCosF.x,
				d.y * (a * sinFCosF.y)
				);
		}

		void vert(inout appdata_full vertexData) {
			
			float3 gridPoint = vertexData.vertex.xyz;
			float3 tangent = float3(1, 0, 0);
			float3 binormal = float3(0, 0, 1);
			float3 p = gridPoint;
			float3 a, b, c;
			a = GerstnerWave(_WaveA, gridPoint, tangent, binormal, _WaveSpeed.x);
			b = GerstnerWave(_WaveB, gridPoint, tangent, binormal, _WaveSpeed.y);
			c = GerstnerWave(_WaveC, gridPoint, tangent, binormal, _WaveSpeed.z);
			p += a + b + c;
			float3 normal = normalize(cross(binormal, tangent));
			vertexData.vertex.xyz = p;
			vertexData.normal = normal;
			vertexData.color.r = -UnityObjectToViewPos(vertexData.vertex).z;

			// displace it
			float displace = tex2Dlod(_ParallaxMap, float4(vertexData.texcoord.xy, 0, 0)).r;
			vertexData.vertex.y += _ParallaxOffset * ( displace * 2 - 0.5);
			// For later: Get current wave height offset and assign per vertex color RGB
			vertexData.color.rgb = saturate(float3(a.y, b.y, c.y));
		}

		float magnitude(float3 value)
		{
			return saturate(value.x + value.y + value.z);
		}

		void surf(Input IN, inout SurfaceOutputStandardSpecular o) {

			// Base Water Color
			fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
			// Base Foam Color
			fixed4 f = tex2D(_FoamTex, IN.uv2_FoamTex) * _FoamColor;

			// Get mesh intersection through depth testing
			float sceneZ = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE_PROJ(_CameraDepthTexture, UNITY_PROJ_COORD(IN.screenPos)));
			float surfZ = -mul(UNITY_MATRIX_V, float4(IN.worldPos.xyz, 1)).z;
			float diff = sceneZ - surfZ;
			float intersect = 1 - saturate(diff / _FoamFade);
			
			// Wave height computation (useful for translucency and foam mapping). Hacky right now.
			float wavePeaksFromDepthBuffer = saturate(IN.worldPos.y - _WavePeakDepth);
			float3 waveform = magnitude(IN.color.rgb) * 4;

			// TODO: Depth for sea fog
			//float d = saturate(IN.worldPos.y - _FogDepth);
			//float3 depth = float3(d,d,d);

			//Just get some height input so we can change colour on heightmap peaks/troughs
			float displace = tex2Dlod(_ParallaxMap, float4(IN.uv_MainTex, 0, 0)).r;

			// Compute final surface
			o.Albedo = c.rgb + f * intersect + _WavePeakColor * wavePeaksFromDepthBuffer + _WavePeakColor * waveform + (f * displace * 0.025 * wavePeaksFromDepthBuffer);
			o.Specular = _SpecColor;
			o.Smoothness = _Glossiness;

			// Need to add in scale controls into shader properties
			// Currently offset one map's UVs by main wave direction to add a sense of movement to the surface
			// I may or may not keep this
			o.Normal = BlendNormals(
				UnpackScaleNormal(tex2D(_BumpMap, IN.uv_MainTex), 1),
				UnpackScaleNormal(tex2D(_BumpMap2, IN.uv2_FoamTex + 0.5 *  float2(_WaveA.x * _WaveSpeed.x * _Time.x * _WaveSpeed.w, _WaveA.y * _WaveSpeed.x * _Time.x * _WaveSpeed.w) ),1)
				);
			o.Alpha = c.a;
		}
		ENDCG
	}
	FallBack "Unlit"
}

Leave a Reply

Your email address will not be published. Required fields are marked *