Course contentsShow
← Unity Game Development
Lesson 620 of 8289. Rendering and VisualsFree lesson

Simulating Water Surfaces with Gerstner Waves

Combine vertex displacement and normal map animation to create convincing, flowing water surfaces with realistic wave movement.

In this lesson, you will learn how to combine dynamic vertex displacement with animated normal maps to create the illusion of flowing, deep-water movement using Gerstner Waves.

The Physics of Gerstner Waves

While simple sine waves move vertices purely up and down, Gerstner Waves provide a more realistic "choppy" look by moving vertices in circular orbits. This pulls the crests of your waves together, creating sharp peaks and wide troughs—exactly how real water behaves.

In Shader Graph, we achieve this by calculating the displacement for each vertex based on its World Position and the Time node. The formula involves offsetting the horizontal position (X and Z) using a cosine function, while the vertical position (Y) uses a sine function.

Combining Displacement and Detail

Vertex displacement alone creates the "silhouette" of the waves, but it lacks the fine-grained surface ripple we expect to see. To finish the effect, we overlay this with Normal Map animation.

  1. Vertex Displacement: Connect your Gerstner calculation to the Vertex Position port on your Master Stack. This physically moves your mesh geometry.
  2. Animating Normals: Create two normal maps that scroll in slightly different directions using Time nodes connected to Tiling and Offset. By blending these two maps together, you create a complex, non-repeating interference pattern that simulates surface chop.
  3. The Result: Because your normal maps are applied to the already-displaced vertices, the highlights and shadows will "catch" the light across the moving peaks and troughs.

Your custom function for the displacement logic might look like this:

// Conceptual offset for the Vertex Position
float2 wave = float2(sin(dot(pos, direction) + time), cos(dot(pos, direction) + time));
float3 displacement = float3(wave.x * steepness, wave.y * amplitude, 0);

By layering these two techniques, you ensure that the water surface looks physically reactive from a distance while remaining visually intricate up close. This dual-layer approach provides the volume needed for believable waves without requiring an incredibly high-density mesh, keeping your performance efficient.

Key Takeaway: Gerstner Waves create the shape of the ocean through circular vertex deformation, while animated normal maps provide the high-frequency detail required to catch light across those moving surfaces.