Builtin Library
README.md
oceans
A sea and the ground under it, as one package: the wave simulation, the surface that renders it, the seabed it is seen through, and the answer to "how high is the water here" that gameplay needs to float things on it.
local Ocean = require("@builtin::systems.oceans.modules.ocean")
Ocean.create({ preset = "gale" })
That is a whole ocean — waves out to the horizon, foam on the breaking crests, a seabed below, and a surface that follows whoever is looking at it. To float something on it:
local ocean = Ocean.forEntity(oceanEntityId)
boat.position = { x, ocean:sampleHeight(x, z), z }
It is a building block. The sun the water glitters under, the ambient its foam sits in and the sky it reflects all come from the scene, through the same directional light and reflection probes every other material in the engine reads — so an ocean drops into a world that already has lighting instead of bringing a second set, and changing the scene's light changes the water with it.
What it does
The simulation is spectral: instead of animating individual waves, it builds the sea's energy across every wavelength and direction from an oceanographic spectrum, then inverse-transforms that into a height field. A real sea is what comes out — wind speed alone decides wave height, and it rises with wind the way a fully developed sea does, following the Pierson–Moskowitz law.
It runs in cascades — independent wave fields over different patch sizes, each band-limited to its own range of wavelengths. A tile of roughly two kilometres carries the long waves and one of a few hundred metres the short ones; they sum into a surface with detail at every scale, and because the bands are disjoint no wavelength is synthesised twice. Above them a Gerstner bank adds the swell analytically, which no cascade can hold: a wave longer than its own patch has nowhere to live, and swell arrives from a distant storm on its own heading anyway.
Foam is not painted on. It comes from the Jacobian of the wave displacement: where the surface folds over itself, the wave is breaking. Whitecaps appear where and when the geometry actually breaks, accumulate into a persistent world-space field, and streak behind the crest as they decay.
Colour and transparency are the same mechanism. Per-channel Beer–Lambert extinction over the distance light travelled through water gives both — red dies within a metre, blue survives tens. That is why shallow water over sand reads turquoise and the open sea reads near-black blue without switching materials.
The seabed is one noise field read twice: scaled it is the terrain's relief, unscaled it picks between sand and rock, so outcrops stand on the high ground and sand fills the hollows. The sun's caustic web moves over both, bent by the horizontal displacement of the water directly above it. Bodies land on it: the pass that raises the ground writes the same field out as a height patch that travels with the viewer, so what a body stands on is what the frame drew.
Nothing here ships an image. Every map the surface and the seabed sample — the foam patterns, the sand, the rock, the caustics — is grown on the GPU at startup.
The pieces
| Asset | What it is |
|---|---|
components/Ocean | Puts a sea on an entity. Its fields are the sea state; the simulation is rebuilt from them, so an ocean survives a scene reload. |
modules/ocean | The substrate: attach, create, forEntity, sampleHeight, sampleDisplacement. |
components/Buoyancy | Floats a rigid body on any water, inside the physics step. Its fields are the hull: how dense it is, how quickly it settles, and what displaces water (its colliders, a stated box or what it draws). |
modules/buoyancy | The float: attach, forEntity, list, clear, submergedFraction, restingY. |
modules/oceanPresets | Nine named sea states, from calmClear through gale. |
modules/oceanQuality | The four tiers — how finely a sea is resolved, as distinct from what sea it is. |
modules/oceanClipmap | The camera-following ring geometry both the surface and the seabed are built on. |
modules/oceanFloor | The seabed: terrain, ground materials, caustics and the ground physics collides with. |
modules/oceanScene | The seam between the scene capture and the materials that sample it. |
shaders/oceanSurface | The water: cascade normals, absorption, exact dielectric Fresnel, subsurface scattering, sun glitter, foam, and the view from below. |
shaders/oceanFloor | The seabed: sand and rock blended by terrain noise, under a caustic web. |
materials/oceanWater, materials/oceanSeabed | The default looks. |
renderFeatures/oceanScene | Copies the frame's colour and depth so the surface can refract what is behind it and measure its own water column. |
computeShaders/ocean_* | The ten-stage GPU pipeline. |
Wind is the control
Significant wave height goes as the square of wind speed, so that one field does most of the work:
| Wind | Sea | Wave height |
|---|---|---|
| 3 m/s | glassy, a mirror | ~0.2 m |
| 11 m/s | working sea, occasional whitecap | ~2.5 m |
| 24 m/s | gale, breaking and streaked | ~12 m |
Everything else shapes character rather than size: choppiness pinches crests
into peaks, standingWaveRatio runs waves both ways instead of only downwind,
jonswapGamma sharpens a fetch-limited sea, and the foam layers decide how
readily a breaking crest whitens and how long it streaks.
Read documentation/oceans.guide for the full walkthrough, including the
seabed, buoyancy, reflections and what each tier costs.