ORBIUM
Contents
Developers

The ocean

How the sea is made. FFT waves in four cascades, shore waves that bend around headlands, surf foam, caustics and the view under water.

View as Markdown

The sea around every island is the engine's most involved system. It is several layers, each one doing what it is good at, summed into one surface: engine/ocean/.

The open sea: FFT waves

Far from the coast, the sea is a sum of thousands of waves with a realistic spectrum (the JONSWAP spectrum, after Tessendorf's classic method). Adding that many waves directly would be far too slow, so the engine does it in the frequency domain: it evolves the spectrum in time, then turns it into a height field with an inverse Fast Fourier Transform (OceanFFT.js).

733 m157 m33.3 m7.1 mone surface
  • Four cascades: tiles of 733 m, 157 m, 33.3 m and 7.1 m, each covering one band of the spectrum. The ratios between them are not whole numbers, so their repetition never lines up.
  • Two dispatches a frame for all of them: a row pass evolves the spectrum and runs a 256-point FFT per row in workgroup memory, then a column pass finishes the transform.
  • Choppy waves: besides height, the FFT gives a sideways displacement, which sharpens the crests, and the surface's derivatives, for normals without extra samples.
  • Foam from the Jacobian: where the displacement folds the surface on itself, a wave is breaking. Its Jacobian marks those places, and foam accumulates there and decays over time.

The coast: shore waves

The FFT tiles know nothing about the island. Near the coast, waves slow down in shallow water, bend around headlands, grow, and break. ShoreWaves.js models that wave by wave:

  • Where waves go: a travel-time field, solved once per island with the Fast Marching Method (ShoreField.js), gives each point when a wave front reaches it. Fronts bend around headlands and line up with the depth, like real swell. It is built in a worker, with the rest of the land.
  • Shoaling and breaking: a wave grows as the water gets shallower, until it is too tall for its depth. Then it plunges: the face turns into a wall, the lip is thrown (Breakers.js), and the wave runs on as a turbulent bore.
  • The swash: the last of the wave runs up the beach as a thin sheet and drains back down.

Surf and foam

ShoreSim.js keeps a simulation of the surf zone on the GPU, every frame: foam carried by the water (advected with the flow, thinned where it spreads), sand wetness that dries over half a minute, and foam stranded on the sand when the water drains. SurfFoam.js shades it: dense white mats that tear into lace, then into thin bubble strands.

Light through the water

  • Refraction: everything under the water is drawn into its own image, at half resolution, just before the water (RefractionPass.js). The water follows its refracted view ray into it, so a wading unit's legs show through the surface.
  • Caustics: the bright nets on the seabed come from photon splatting (Caustics.js): a fine grid on the real wave surface is refracted down to the floor, and where the rays focus the grid's cells shrink and add up to bright light.
  • Under water: when the camera dives, sunlight fades along its path through the water, the view fills with murk and marine snow, and the lens splits at the waterline (UnderwaterLighting.js).

Asking the sea

The game needs to know where the water is: to float a unit, to splash, to put the camera under. WaterQuery.js evaluates the full surface on the GPU for a few points and reads the results back a frame or two later. The FFT moves points sideways, so the query solves for the point that lands where it asks, in a few iterations.

On the lower presets

The surf simulation and caustics are among the first systems the lower graphics presets switch off. The FFT always runs: the sea is part of every World.