Skip to content

Water

Water has three parts. The Water shader (builtin:water) is the look: refraction, depth, reflection, foam. A WaterBody says what water is where: an ocean, a lake, a river. A WaterZone bakes every body over its region into one field and draws one surface from it, lifted by waves, and answers questions about that surface from the code, so what a script reads is what is drawn. This chapter is all three, and what is not there yet.

Water is an optional module: a package takes it only when a scene authors a WaterBody (or a script imports esengine/water), so a 2D game carries none of it.

Add a WaterBody to an entity. A body is not drawn on its own: it is an input to the zone (The zone), which bakes it with every other body into one surface. The entity places the body; it may be turned about its up, not tilted.

Field What it does
form Ocean: without an edge, at the entity’s height wherever the ground is lower and joined to the open sea, and on past the zone. Lake: a rectangle of sizeX by sizeZ centred on the entity, or the outline in boundary, at the entity’s height. River: follows the Spline on the same entity.
bodyId What the game calls the body, the same in every session (an entity’s number is not): what a query names the water by. Empty is named by its entity.
sizeX, sizeZ A lake’s size, when it has no outline of its own.
boundary A lake’s outline: three points or more round it on its own xz plane. Empty is the rectangle.
shoreWidth How far in from the shore, and how deep, the waves take to come to full height, for every form. 0 leaves them whole to the edge.
flowSpeed A river’s current, in world units a second where it is as wide as its widest.
waveHeight The height of the tallest wave, in world units. The waves’ length, speed and heading are the zone’s, which every body shares.
bedDepth How deep a river cuts its bed into the terrain under its surface, at its middle. 0 leaves the ground as it is.

Moving a body, editing its fields, its outline or its spline, or changing the terrain under it rebakes the zone, so the water follows at once.

A WaterZone says where the water is drawn, how finely, and what every body shares. Put one on an entity with a Transform: its region is sizeX by sizeZ, centred on the entity (only its x and z count).

Field What it does
sizeX, sizeZ The region baked. Water outside it is not drawn; an ocean goes on past it.
resolution Texels along the region’s longer side: how finely shores and rivers are traced.
material A material on the Water shader for the whole surface. Empty uses the stock water.
waveLength, waveSpeed, waveDirection The waves every body shares (Waves).
surf, surfLength The surf breaking on the shores: how high a breaker stands against its body’s waves (0 draws none), and the length of the surf’s waves (Surf).
cellSize, cells, rings The surface’s mesh: the cell size of its finest ring, cells along a ring’s side, and how many rings (Geometry).
ripples, rippleSize, rippleSpeed, rippleDamping, foamFade The ripples the zone keeps (Ripples).

The zone bakes every body over its region into a grid, each texel holding what the water is there:

  • the height of its surface: a lake’s level, the height of a river’s curve at its nearest place, the ocean’s level;
  • the signed distance to the nearest shore, positive on the water: where the surface is cut and where it foams;
  • how high the waves stand there: the body’s waveHeight, calmed toward its shore and as the water shallows (and the share of it kept, which calms the shading too);
  • the current (a river’s), and the depth of the water over the ground.

Where two bodies meet, each texel weighs what each says by how far inside it the texel is, over a texel and a half, so the values run from one to the other: a river is at its lake’s level where it runs into it, its current fades into the still water, and its banks run on into the lake’s shores with no seam. Lakes and rivers come first and the ocean has what they leave, so a lake by the sea is the lake’s. A texel no body holds keeps the height of the nearest water, so the surface’s edge, which the shore cuts, is not pulled down to nothing.

A scene without a WaterZone is given one over every lake and river (and a margin for their shores) with the component’s defaults, at half the finest cell to a texel (32 to 1024 texels along the longer side); its cells grow so the surface reaches over the whole region from wherever the camera is in it. A scene with only an ocean needs no region: the whole surface is the ocean. One zone is used: the first WaterZone found.

The bake is the CPU’s and needs no renderer, so a server or a test that draws nothing can still ask the surface where it is. It is redone whole when a body, the zone or the terrain changes, at a cost of the texels times the bodies.

Four peaked sines (exp(sin(theta) - 1): a crest narrower than its trough) a few directions apart, each with a length, a height and a speed that are shares of the zone’s waveLength, waveSpeed and waveDirection. Every body shares them; how high they stand at a place is that place’s in the field: its body’s waveHeight, times the share the shore leaves, times the weather’s (a Weather that drives the wind scales the waves as far as the water is open to it: wind waves grow with the water they blow over, so the sea takes the weather whole, a lake the more the wider it is against eight waveLengths, and a river hardly at all). They move the geometry: the shader lifts each vertex by the wave height, and the fragment takes the slope per pixel from the same formula, so the lit surface is the one that is drawn and the silhouette against the sky rises and falls with the waves.

The waves are measured on the world’s xz plane and lift the surface straight up. Parameters are in world units: a scene in metres wants the defaults, one in centimetres wants the lengths scaled.

Where water meets the ground, waves run in toward the shore. The zone measures how far each place is from the nearest shore across the ground (the same for an ocean over a beach as for a lake), and over the last four surfLengths of water the surf stands up on that distance: waves with a steep face toward the shore and a long back, travelling in at the speed of a deep-water wave of their length times waveSpeed. They grow as the water shoals, whiten where they break in the inner part of that band and stay white behind the crest a while, and past the shore they run up the ground: the surface is drawn there too, a little above the water’s rest height, and the depth test cuts it where the ground rises above it, so the waterline climbs a beach and falls back. A breaker stands surf times the body’s waveHeight (with the weather’s waves in), times how open the water is: surf is swell come in from open water, so a lake’s shore laps where the sea’s breaks; a current carries none, so a river and its mouth stay calm. The crests are lines of their own distance, the open water’s edge (a river’s water taken for land) smoothed over a surfLength, so they come in straight from open water and turn to the shore only near it, and run straight across a river’s mouth; where they rise, break and run up is the real shore’s. How white they burst is the shore’s too: the zone bakes how steep each shore is, so waves burst and foam against rock and cliffs, wash up a gentle beach in a thin line, and run up only a short way. Waters.sample adds the same surf, so what floats near a beach rides it.

The whole surface is one mesh: rings of the same number of cells (cells a side), the finest of cellSize cells, each twice as coarse as the one inside it with a hole where that one sits, so it reaches cells * cellSize * 2^(rings - 1) / 2 from its centre and spends its triangles near. It follows the camera on a lattice all its rings line up on, and the material’s origin moves by as much, so the water stays put in the world while the mesh moves under it. Its vertex hook reads the zone’s field at each vertex (the heights texel by texel, decoded and then blended) and lifts the vertex to the water’s height there plus its waves; the pixels where the field says there is no water are cut.

A coarse ring cannot draw a short wave: below three cells to a wavelength it would alias, so its vertices fade out the waves shorter than six of its cells (the shading still shows them, per pixel). Where two rings meet, the coarse one has half the vertices along the edge, and the surfaces can part by what the waves do between them; keep waveLength at least six of the coarsest visible ring’s cells and there is no seam to see.

A lake is its outline at its entity’s height. The zone takes the signed distance to the outline, and with a Terrain in the scene the depth of the water over it, and from the two the share of the waves a point keeps (none at the edge or where the water is not deep, all from shoreWidth in). The surface is cut where the outline is (traced at the zone’s resolution, not at the mesh’s cells), the geometry and the shading calm toward the shore, and the shore foams along it.

The water fills the low ground and meets the hills where they cross it, and the waves die out as it shallows. A body is water only where its surface is over its ground: a hill or a bank inside a lake’s outline or a river’s width that stands above the water is land, for what is drawn and for what is asked. Over no terrain a lake is as deep as it likes. The outline should not cross itself.

An ocean takes its shore from the terrain the same way: inside the zone it is cut where the ground stands above its level, and its waves calm over shoreWidth of depth. It is the open sea, which lies past the zone’s edge: low ground is sea only where low ground joins it to that edge, so a hollow below the sea’s level walled off by higher ground stays dry (or holds a lake of its own, at its own level). Past the zone it is everywhere at its level with its waves whole.

Where bodies overlap, a lake holds the water over a river (a river that runs into a lake is the lake there, at the lake’s level, its current fading into the still water) and both over the ocean; which body a place belongs to does not depend on the order the bodies were added. The surface casts no shadow: the sun lights what is under it, through the caustics.

Deciding where a world’s lakes and rivers go is a game’s (or its generator’s) work; findBasins(grid, ground, { seaLevel }) gives it the ground’s side of it, from one priority flood over a grid:

  • basins: each hollow, with the level water would fill it to (the lowest point of its rim), its deepest point and its outlet, where it runs over;
  • filled: the height water would stand at over each texel; basin: which basin a texel is in;
  • downstream and accumulation: where each texel drains to, down to the region’s edge, and how much drains through it, which is where rivers run; drainPath(map, x, z) follows it from a point;
  • sea: with a sea level, the low ground the sea reaches from the edge.

A lake placed at a basin’s level over its outline fills it to the brim; a river from its outlet down drainPath runs where the water would.

A river follows the Spline on its own entity (Splines): it is as wide as the curve says there, at the height of the curve at its nearest place, so a river runs downhill. Put a Spline and a WaterBody with form River on one entity and the zone bakes it.

  • The current. flowSpeed is how fast the water runs where the river is as wide as its widest; a narrower reach runs faster and a wider one slower, within a quarter and double of it, as the same water through a different width does. The direction is the curve’s. It fades in over a width from the river’s start and out over a width into whatever it runs into, so a river’s mouth is still water.
  • The surface rides it. The shader carries the waves and ripples downstream: two copies of the surface are moved along the current a cycle at a time, each fading in as the other fades out, so the moment one is put back at its start is hidden by the other, and the surface does not stretch where the river bends.
  • Waves. A river has its own waveHeight, calmed toward its banks over shoreWidth, like any body. Waters.sample gives the current in flow (world units a second, along the river; zero elsewhere).
  • Its bed. With bedDepth above zero the river cuts its bed into the terrain: a trough bedDepth under the surface at its middle and at the surface at its edge, its banks rising on at the same slope until they meet the ground, two widths out at most. It is laid as terrain modifiers a stretch of the river each, so the drawn ground, its queries and collision all follow, editing the spline remakes only the ground along it, and ground already lower than the bed is left alone. Ground a game carves itself (a heightmap made with its rivers in it) leaves bedDepth at 0.
  • Banks and mouths. A bank is where the field’s shore distance crosses zero, traced at the zone’s resolution. Where a river runs into a lake, the sea or another river, the two are blended over a texel and a half, so there is no seam, no shore across the mouth and no step in height.

Put a Floating component on an entity with a RigidBody3D and a collider and the water holds it up and carries it along. It works through forces on the rigid body, from asking the water’s surface (Asking the surface) where it is under a few points of the body, so it needs no renderer: a server, a test or a build that draws nothing floats things the same.

Field What it does
buoyancy The water’s density against the body’s. 1 holds it up exactly when it is wholly under; 2 floats it half under.
drag How hard the water resists the body moving through it, per second. It brings a body to rest in still water and carries it with a river’s current.
radius How deep, in world units, a point of the body has to be under the surface to be wholly wet.
points Points of the body, in its own space and world units, the water is asked about. Spread them (the corners of a hull) and the waves tip it. Empty is the one at its origin.
wake How much of a ripple the body makes in water that keeps ripples (Ripples). 0 makes none.

At each point the share of it under water (0 at the surface, 1 from radius down) gives a buoyant force against gravity of mass * g * buoyancy * share / points, applied at the point, and a drag against the point’s velocity relative to the water (the body’s own and what its turning adds there, less the current). The forces are applied every fixed step before the world steps. A body with buoyancy 2 and one point settles with its centre radius / 2 under the surface.

It needs the 3D physics module, and a build of it with the force functions (_physics3d_addForce, _physics3d_getBodyDynamics); one without them warns once and floats nothing. Floating is one rigid body in one fluid: there is no added mass, no lift from the shape of a hull, and a body that is wholly under is not pushed toward anything but by the current.

The zone keeps what disturbs the water when ripples is above 0. It holds a square window of the surface (rippleSize world units a side, ripples cells along it) as a grid of heights and steps it with the wave equation: every cell is pulled toward its neighbours and carries the velocity it had, so a bump put in spreads as a ring at rippleSpeed, loses rippleDamping of its height a second, and is gone. Beside the heights the grid keeps foam: a disturbance feeds it where it happened and it fades at foamFade a second, so a splash leaves white water that lingers and thins and does not blink out with the ring.

  • Who disturbs. A script calls Waters.disturb(x, y, z, radius, strength) (strength is a height in world units, negative is a dip; it does nothing where there is no water), and a Floating body does it itself: what goes down into the water, comes up out of it or moves along its surface pushes the water by its speed (wake multiplies it).
  • What it changes. The shader reads the grid as a texture: the slope of the ripples bends the normal, which is what the light, the reflection and the refraction see, and the foam whitens the surface. The geometry is not lifted by it. The surface the code reads does carry them: Waters.sample adds the ripple’s height to the surface, and Waters.rippleAt(x, y, z) gives it alone, so a floating ball is lifted by the ring that passes under it.
  • Where. The window follows the active camera, in whole cells, so water of any size has ripples where they are seen for the price of one grid, shared by every body. What the window leaves behind is forgotten, and what it comes to is still water. A wave reaching its edge goes out and is not reflected. The grid is stepped on the CPU, in as many substeps as keep a wave from crossing more than half a cell, and uploaded as an RGBA8 texture (red the height, green the foam): the same on every backend.

The grid can hold as much height as max(2 * tallest waves, rippleSize / 50), the tallest waves being the highest any place in the field stands to, times the weather’s: a disturbance that goes on (a body moving) saturates there and does not pile up. A cell is rippleSize / ripples world units, so a ripple narrower than a cell or two is not drawn. Cost is ripples squared per substep and one texture upload a frame: 128 is sixteen thousand cells.

When the active camera is under a body of water the picture is the water’s. The water module reports where the camera is each frame (from Waters.sample) and the post-process volume system adds an Underwater effect for as long as it is, the way a weather adds its rain:

  • Fog by water crossed. The light of each pixel has crossed the distance from the eye to what it came from (the sky, which has no surface, the longest the water is seen through, u_range), and the water takes it out colour by colour, red first, by the same u_absorption and u_density the surface’s material draws with, and gives back its own colour. A white wall under water is blue-green, and a far one more than a near one.
  • Dimmer with depth. The colour the water gives back dims with how far down the camera is (u_lightFalloff), as the light that reaches it comes from above.
  • A waterline across the lens. Each pixel is asked whether its place on the camera’s near plane is below the surface at the camera, so a camera with its lens half in the water has a dry upper half and a wet lower half with a soft line between them (u_band, a twelfth of the near plane), and crossing the surface is the line sliding up the lens and not a cut. The effect is on from a couple of near planes above the surface down.
  • The sun through the waves. What the camera sees is lit by the web of bright threads the waves focus the sun into: the same pattern the surface draws from above (u_causticStrength, u_causticScale, u_causticSpeed on the Water material, which the effect is given), on what faces the sun and is not in its shadow, less the further the sun’s light went through the water to reach it. It follows the first directional light, and its shadow map where there is one.
  • Light shafts. The water along each ray scatters the sun toward the camera: the pattern is marched along the ray in 24 steps, each lit where the shadow map says the sun reaches it and as much as the water above it lets through, which is what makes shafts (u_shaftStrength, u_shaftRange). It costs 24 taps a pixel in the water.
  • Yours if you wrote it. A scene that authors an underwater effect in a volume keeps its numbers; only the camera’s depth and how wholly it is under are filled in.

The surface itself, seen from below, is turned over and light goes the other way through it: from out of the water rays past the critical angle (about 49 degrees) are reflected whole, so the water above is a window of sky overhead in a mirror, and the Fresnel term follows. The window’s edge is not drawn as a circle on its own: it is where the refraction runs out. What the mirror shows is the world below it: the reflected ray is followed through the picture of the scene the frame copied (20 steps, growing), and the colour of what it meets is what the water reflects, so a box on the bottom appears in the surface above it. Where the ray meets nothing on screen the mirror is the water’s own dark colour.

Seen from above, the same caustics light what is under the water through the surface: the Water shader finds where the refracted ray ends, where the sun’s ray to that place crossed the surface, and lights it by the pattern there (and by the shadow map), so the web bends with the ripples that refract it.

Spline is data and nothing draws it: points (each an x, y, z in the entity’s own space and a width) and closed. A river reads the one on its entity; anything else that follows a path can read it with SplineCurve:

const curve = new SplineCurve(spline.points, spline.closed);
curve.position(s); // the point a distance s along it
curve.tangent(s); // the direction of travel there
curve.width(s); // the width there, eased from one point's to the next's
curve.nearest(x, z); // the nearest place on it in the xz plane: s, how far, which side

It is a centripetal Catmull-Rom curve (it passes through every point, and does not loop or cusp where they are close at one end and far at the other) measured by distance along it, so a place on it is s units from its start and not a parameter that runs fast where the points are close. Past either end of an open curve it is the end; a closed curve goes round. Splines are an optional module (esengine/spline).

Details shows a water body only what its form uses: a lake has its size and boundary, a river its flow speed, and every form its shore width and wave height. A lake’s Boundary row says how many points its outline has (or that it is the rectangle of its size) and resets it to the rectangle. A WaterZone shows its fields in groups: Region, Look, Waves, Detail and Ripples.

Select a lake or a river and its points are handles in the viewport, the way a polygon collider’s vertices are:

  • Lake. A square on each point of the outline and a circle in the middle of each edge. Drag a point to move it on the lake’s own plane; drag an edge’s circle to insert a point there; press a point and release it in place to delete it. A lake keeps at least three points, and with none the four corners of its rectangle are the points: pulling one turns the outline into a boundary of its own.
  • River. The same on the curve of its Spline, with a second handle at each point, offset across the river by half its width: drag it to set the width there. A river keeps at least two points. The point last pressed shows its width in Details under the Spline, where it can be typed.
  • Each gesture is one undo step, and the points are written back as they are dragged, so the water follows. The handles are the Water entry of the Gizmo menu.

A zone’s region has no handles: set its size in Details.

Water spends what the project’s quality level says it may. The five fields are in each level’s profile (project.esproject, features.rendering.quality.profiles), and a project can change what a level means. Project Settings → Rendering → Level · Water edits them for the level chosen under Editing level; the group is hidden while quality is off or the project excludes the water module.

Field Low Medium High What it does
waterWaves 2 3 4 How many of the four waves are summed: in the geometry, the shading, and what Waters.sample answers.
waterRippleCells 32 64 128 The most cells along a side of the ripple grid; the zone’s own ripples is held to it, and 0 keeps none.
waterDisplacement off on on Whether the waves lift the geometry. Off is the lite water: a surface at the water’s rest height whose waves are in the shading only.
waterMirror off on on Whether the surface seen from below mirrors the world under it (the screen-space march).
waterCaustics off on on Whether the sun’s web of light is drawn on what lies under the water, and, seen from under it, the shafts.

What code reads follows what is drawn: at a level without displacement the surface is at rest for Waters.sample, floating things sit on it, and fewer waves is fewer waves for both. With a quality policy running, the active level decides; with none, a minigame gets the Low profile’s water (the least it has to spend), and anything else gets all of it. The lite water costs no vertex work for the waves, and the shading still moves, so it reads as water at a distance.

Waters.sample(x, y, z) (the WatersRes resource) answers from the zone’s field, the same numbers the surface is drawn from:

  • entity: the body that owns most of the water there (where two meet, the one the point is further inside), and bodyId, what the game calls it;
  • depth: how far the point is above the surface (negative under it), in world units;
  • normal: the surface’s normal there, in world space;
  • surface: the world position on the surface straight above or below the point;
  • inside: whether the point is over water: always, for an answer that is not null;
  • flow: how the water moves there, in world units a second (a river’s current; still water is zero).

It reads the same formula the shader draws, at the time of the frame, with the height the waves stand to there and the ripples. Waters.heightAt(x, z) is the surface’s height alone. A point over no water (outside every shore, or past the zone with no ocean) answers null. Waters.bodyAt(x, z) says which body a place belongs to: its entity, its bodyId, its form and its level at rest. Waters.shoreAt(x, z) is how far a place is from the water’s edge across the ground: above zero on dry land, below it on the water.

The surface the code reads is the full wave sum; a coarse far ring draws less of it (see above), so near the horizon the two can differ by the short waves, which are small.

builtin:water also works on its own, on any mesh, which is how a flat plane of water was made before the component. With u_waveSpace at its default (0) the waves are measured on the world’s xz plane, nothing is lifted and the surface is taken to lie flat. The zone turns it to 1: the mesh’s uv is its x and z, u_origin is where the mesh was moved to as it follows the camera, and the vertices are lifted to the zone’s water and its waves.

  • Caustics. u_causticStrength (0 turns them off), u_causticScale (the size of one cell, in world units) and u_causticSpeed shape the web of light on what is under the water, seen from above and, through the effect, from below.
  • Waves. u_waveLength, u_waveHeight, u_waveSpeed, u_waveDirection; the ripples the wind raises (u_rippleStrength, u_rippleLength) ride on them: six short waves around the wind’s heading, from u_rippleLength down to a fifth of it, each fading out with distance before it would shimmer.
  • Rain. When a Weather drives the rain, drops ring out on the surface (u_rain, written by the zone): rings that spread and die, a few to every u_rippleLength, more of them the harder it rains, and the surface a little less sharp. They are drawn, not simulated: they put nothing in the zone’s ripples and make no foam, and they fade out with distance. On a zone’s surface the zone writes the four every frame (its own length, speed and heading, and the weather’s share as the height, which each place’s height in the field multiplies), so the material’s values for them are not used there.
  • Sparkle. The sun’s glints on small slopes come from two tiled normal maps that scroll over the surface, in the one texture a material has room for: u_detailMap, whose red and green are the first map’s slope (x and z, 0.5 flat) and whose blue and alpha are the second’s, read as data. Each has its u_detailTile (world units to a repeat), u_detailStrength (how many times as steep as drawn; 0 leaves it out, which is how a material that sets none stays as it was) and u_detailFlow (world units a second it drifts). The glints are the lit specular response of those slopes, not a white texture drawn on, so they appear where the sun, the eye and the slope line up and fade as the light does. They are fragment-only: the geometry and Waters.sample do not carry them.
  • Refraction and depth. What is under the water is the picture of the opaque scene, shifted by the waves in proportion to how much water there is. The depth is measured: before the water is drawn the frame takes a copy of how far each pixel of the scene is from the eye, so the water at a pixel is the difference between that and the water’s own distance. The light is absorbed along that column, red first (u_absorption, u_density), and the water gives back its own colour, from u_shallowColor to u_deepColor as it deepens.
  • Reflection. The sky, the lights and the environment reflect off it with a Fresnel term (water is 1.333), so it is clear looking down and mirror-like at a grazing angle. Add Screen Space Reflections to the volume for reflections of the scene itself.
  • Foam. Where the column is shallower than u_foamWidth (the shore, a rock, a pier’s legs) and on the crests of the waves (u_foamCrest), broken up by moving noise. On a zone’s surface the baked shore foams too, within u_foamWidth of the shore or of shallow water, so the shore foams where the depth cannot be measured.
  • Current. u_flowCycle is how long, in seconds, each of the two copies of the surface carried down a current lives before the other takes over.
  • The zone’s numbers. The zone writes these, and they are not for authoring: u_zoneOn, u_zoneHeights and u_zoneField (the baked field as two textures), u_zoneOrigin, u_zoneSize, u_zoneHeight and u_zoneRange (where the region is and what the textures encode over), u_oceanOn, u_oceanLevel and u_oceanWaves (the ocean past the region), u_cellSize (the finest ring’s cell, for the waves a coarse ring fades), u_geoWaves (0 for the lite water), and the ripple and quality ones (u_rippleOn, u_rippleMap, u_rippleOrigin, u_rippleSize, u_rippleTexel, u_rippleRange, u_waveCount, u_mirror, u_causticOn).

What is not there yet, so nobody is surprised:

  • It needs the same engine-copied scene as glass (a post-process chain is running) and a device whose render targets can hold floats for the depth. Without the chain the water is drawn over the scene as a translucent surface; without float targets the depth is u_fallbackDepth everywhere, so the colour does not change with depth and only the zone’s baked shore foams.
  • Blended surfaces (a pane, a particle, a leaf card) are ordered against water and glass by the pixel: when a frame has both, it is drawn in layers (the opaque scene, the blended draws over it for the picture the water refracts, the opaque scene put back and the water drawn, then the blended draws again depth-tested against the water). A blended thing under the surface is seen through it, one in front lies on it, and where there is no water it is blended once. It costs two more full-screen copies of the scene and a second pass over the blended draws, only in frames that have both. A blended draw with no depth test, or one that crosses the surface, is drawn whole on both sides of it, and one in front of the water is seen through it a second time, faintly, as the picture the water refracts holds it.
  • Water is horizontal: a body may be turned about its up, not tilted, and the surface is one height at each place, so there is no water over water.
  • One zone is used, the first WaterZone found, with one material for all of its water, and one ocean, the first found. Water outside the zone’s region is not drawn, except the ocean. A shore or a river is traced at the zone’s resolution: a river a texel or two wide, or an outline’s detail finer than that, is lost, and a large region wants a high resolution.
  • The surface reaches cells * cellSize * 2^(rings - 1) / 2 from the camera; with a WaterZone of your own, make that cover what should be seen (a zone you did not author grows its cells to do it).
  • A river cuts its bed only when bedDepth is above zero (see Its bed); rapids and falls are not foamed by how fast or steep the water runs. A lake’s outline is a polygon on a plane: it does not follow a curved shore.
  • Things float (see Floating) and make a wake (see Ripples); nothing swims yet.
  • Caustics and shafts follow the first directional light only, from a pattern and not a simulation: the web does not respond to the actual shape of the waves. From above the caustics take what is under the water to face up (the picture holds no normals there), so a wall under the surface is lit as a floor would be.
  • The mirror from below is screen-space: it shows only what the frame has on screen, and a ray that leaves the picture or meets nothing finds the water’s own colour.
  • Ripples only bend the shading and carry foam: they do not lift the geometry, and they are one window following the camera, so water far from the camera keeps none and two cameras in a split screen share the first’s. Foam that a ripple leaves is not carried by a river’s current, and the waves the geometry is lifted by are not carried by it either (only the shading’s).
  • The waves are the four peaked sines, not a spectrum, and they lift the surface without pushing it sideways.