Ocean Update #1

I’ve started some initial shader setup for the ocean shader.

Jasper Flick (of catlikecoding.com) has a fantastic tutorial explaining the mathematics and implementation of Gerstner waves in Unity, borrowing from the original GPU Gems article on nvidia.com.

To give you some background, the wave functions are based on how large bodies of water act on Earth, themselves grounded in gravitational principles. This strictly means that a speed modifier isn’t necessary for physically accurate waves, as their speed is derived from these principles. 

So far, I’ve taken this as a base and simply expanded on it a tad for now. Its nothing special right now.

I’ve added in a fixed tessellation function, which I can adjust later (as I have every intention of using a distance-based function here, as the water will be a large bodies  stretching to the horizon).

I’ve also optimised the Gerstner waves calculations somewhat. There was significant duplication of sin(f) and cos(f) in the code, along with a common multiplier on those. I dumped these into a float4 to save on memory footprint.

Finally, I added in some commented-out code for foam texture and assigning wave height offset in the vertex colours, just as I suspect these may come in handy later down the line.

Other than that, it appears much the same as Jasper’s implementation, but from here on in, it’ll be my own take.

Shader "Custom/Waves" {
	Properties {
		_Color ("Color", Color) = (1,1,1,1)
		_MainTex ("Albedo (RGB)", 2D) = "white" {}
		_FoamColor("Foam Color", Color) = (1,1,1,1)
		_FoamTex("Foam Texture (RGB)", 2D) = "white" {}
		_Glossiness ("Smoothness", Range(0,1)) = 0.5
		_Metallic ("Metallic", Range(0,1)) = 0.0
		[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)]
		_Tess("Tessellation", Range(1,32)) = 1
	}
	SubShader {
		Tags { "RenderType"="Opaque" }
		LOD 200

		CGPROGRAM
		#pragma surface surf Standard fullforwardshadows vertex:vert tessellate:tess addshadow
		#pragma target 4.6
		#include "Tessellation.cginc"

		sampler2D _MainTex, _FoamTex;
		half _Glossiness, _Metallic, _Tess;
		fixed4 _Color, _FoamColor, _WaveA, _WaveB, _WaveC;

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

		float4 tess() {
			return _Tess;
		}

		float3 GerstnerWave (float4 wave, float3 p, inout float3 tangent, inout float3 binormal) {
		    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);
			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);
			b = GerstnerWave(_WaveB, gridPoint, tangent, binormal);
			c = GerstnerWave(_WaveC, gridPoint, tangent, binormal);
			p += a + b + c;
			float3 normal = normalize(cross(binormal, tangent));
			vertexData.vertex.xyz = p;
			vertexData.normal = normal;

			// For later: Get current wave height offset and assign per vertex color RGB
			// vertexData.color.rgb = saturate(float3(a.y, b.y, c.y));
		}

		void surf(Input IN, inout SurfaceOutputStandard o) {
			fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
			// For later: Foam map
			// fixed4 f = tex2D(_FoamTex, IN.uv2_FoamTex) * _FoamColor;
			o.Albedo = c.rgb;
			o.Metallic = _Metallic;
			o.Smoothness = _Glossiness;
			o.Alpha = c.a;
		}
		ENDCG
	}
	FallBack "Unlit"
}

Leave a Reply

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