← Back to the beach

An awakewithai.com production

The Math
Behind It

The physical specification behind Just a Tropical Beach — a beach that is not drawn but solved. One system, followed all the way down: the ocean that arrives, the instability that breaks it, the shore that absorbs it, the light that makes any of it visible, and what the water leaves behind.

IThe premise

A beach is not a picture of a beach. It is a coupled dynamic system that happens to be visible.

Almost every synthetic ocean is built the wrong way round. Someone decides what the water should look like — blue here, white there, a wave-shaped object that plays a crashing animation — and then spends enormous effort making the decision look convincing. It never quite does, because the eye is extraordinarily good at detecting an effect that has no cause.

The alternative is to specify the rules and let the appearance be a consequence. Water moves because of pressure and gravity. Waves change because the depth changes. Waves break because they become unstable. Swash runs up because it has momentum and runs back because of gravity. Water vanishes into the sand because sand is porous. Foam is white because a dense cloud of air–water interfaces scatters light many times before any wavelength is preferentially absorbed. Sand darkens because water displaces the air between its grains.

None of those are visual decisions. All of them have visual consequences. The whole specification below is an attempt to state the rules precisely enough that the picture has no choice but to emerge.

Ocean→spectrum→shoaling→refraction→breaking→turbulence→air→foam→bore→swash→infiltration→backwash→sediment→wetness→light→camera

Read left to right, nothing in that chain is a rendering trick. Each stage is the input to the next, and the last one is the only one you actually see.

IIThe ocean that arrives

One sine wave is a swimming pool. A sea is a sum.

CoordinatesWhere everything lives

Use a right-handed frame: x across the shore, y along it, z up. Still water is z = 0, the ocean occupies x < 0, and the bed is a surface z_b(x,y). The instantaneous water depth is the difference between the free surface and the bed, and the shoreline is simply the contour where that difference vanishes.

h(x,y,t) = η(x,y,t) − z_b(x,y) shoreline: η(x,y,t) = z_b(x,y) This is the single most important equation in the whole system. The shoreline is not a texture, not a decal and not a line to be drawn — it is the moving solution to a wet/dry condition, extracted from the depth field every frame.

SpectrumMany waves, not one

The offshore surface is a superposition of components, each with its own amplitude, direction, frequency and — critically — a random phase:

η₀(x,y,t) = Σᵢ Aᵢ cos(kᵢx + lᵢy − ωᵢt + φᵢ) At minimum three bands: long swell (T = 10–18 s), medium swell (6–10 s) and locally generated wind sea (2–6 s). The interference between bands is what produces sets, lulls and the sense that no two waves are quite the same.

DispersionWhy shallow water changes everything

Gravity waves obey a dispersion relation that depends on depth:

ω² = g k tanh(k h) deep (kh ≫ 1): ω² ≈ g k shallow (kh ≪ 1): c ≈ √(g h) In shallow water the phase speed depends only on depth. As h falls the wave slows — but the water inside it keeps its momentum. That mismatch is the seed of everything that follows.

ShoalingEnergy squeezed into less depth

As the wave slows, its energy is compressed into a shorter wavelength and a taller crest. Height and steepness both rise:

H = H₀ · K_s · K_r K_s = √(C_g,₀ / C_g) steepness: ε_w = H / λ energy: E = ⅛ ρ g H² K_s is the shoaling coefficient and K_r the refraction coefficient. As H rises and λ falls, H/λ rises twice as fast — which is why breaking arrives suddenly rather than gradually.

RefractionWhy waves turn toward the beach

Because speed depends on depth, a wave arriving at an angle travels faster in its deeper part and pivots. It is Snell's law with the same structure as optics:

sin θ₁ / c₁ = sin θ₂ / c₂ Wave crests therefore bend to face the shore, but never completely — the residual angle is what drives the alongshore current and the slow drift of foam down the beach.

GroupingSets and lulls

Real waves do not arrive as identical soldiers. They arrive in groups, and a wave inside a strong group breaks harder than an isolated wave of the same nominal height:

A(t) = A₀ · exp[ G(t) ] G = correlated noise, f_g ≪ f_wave The exponential form matters: it keeps amplitudes positive and makes the big sets rarer than the small ones, which is the observed statistics. Successive waves should be correlated — Xₙ = ρXₙ₋₁ + √(1−ρ²) εₙ — so the sequence reads small, small, medium, large, small rather than a coin flip each time.

GroupinessSets, not an exponential tail

Group structure is easy to add and easy to overdo, and overdoing it does not look like bigger sets — it looks like a flat sea that occasionally produces a monster. A linear random sea is Gaussian to first order: kurtosis 3, Rayleigh-distributed heights, and a largest wave in N of about √(½ ln N) times the significant height.

envelope depth H_s kurtosis median H H_max/H_s 0.00 0.737 2.82 0.428 1.52 0.32 0.739 3.40 0.406 2.23 0.62 0.742 5.25 0.355 2.99 Two things to read here. The significant height is invariant to under a percent across the whole range, so this parameter moves only the shape of the distribution and never the energy — which is why raising the swell height does not fix a sea that feels wrong. And at the deepest setting the number of runs above the significant height actually falls: it was adding an exponential tail, not groupiness.

The ceilingWhy a bigger swell is not a bigger shorebreak

Here is the most useful thing to know about making waves, and it is not obvious until you measure it. Raising the swell offshore does almost nothing to the wave that reaches the beach. Not a little — almost nothing. Every wave arrives at the shore already broken down to what the water depth there permits:

H_max = gamma · h gamma ≈ 0.78 flat, 0.6 – 1.2 observed, rising with slope so the height at the beach is set by the DEPTH, not by the sea that sent it measured: swell 1.35 m → 4.0 m offshore changed the water depth in the swash by 0.88 m → 0.92 m, i.e. by nothing that can be seen A tripled swell breaks further out, in deeper water, and arrives at the same beach as the same bore. The bar and the reef enforce the ceiling long before the shoreline gets a say.

Which means that if you want an occasional bigger wave at the beach, the amplitude of the sea is the wrong dial, and so is the grouping. Widening the group envelope makes the big sets relatively bigger but — because the envelope is normalised on its mean — pulls the typical wave down, and raising the swell to compensate runs straight back into the ceiling. Measured over matched runs, that combination made every wave smaller.

The dial that works is the ceiling itself. Two ways to lift it: gamma, which is a property of the bed slope and not a constant to be borrowed from a textbook; and the water level, because h is what gamma multiplies. The second is the interesting one — a real beach already does it.

Surf beatThe sea breathes, and the ceiling breathes with it

Groups do not only make waves arrive together. A group of large waves pushes extra water shoreward and holds the level up beneath itself; the lulls between let it fall. The result is a long oscillation of the water level at a period far below any individual wave — two or three minutes down to twenty seconds — and it is the reason a beach visibly surges and withdraws over a timescale nothing in the wave field explains.

eta_IG(t) long-period rise and fall of the mean level, T ≈ 20 – 200 s H_max(t) = gamma · [ h + eta_IG(t) ] → the DEPTH LIMIT ITSELF RISES AND FALLS A set arriving on a raised level breaks in deeper water and is allowed to be bigger; the same set on a fallen level is not. This is what produces an occasional big one rather than a uniformly bigger sea — and unlike a gain applied to the wave field, it is the mechanism the beach actually uses.

It is a strong lever and worth handling carefully: the response is nonlinear, because the biggest waves are exactly the ones held back by the ceiling, so they gain most when it lifts. Tripling the infragravity amplitude here doubled the whole sea rather than adding the occasional set.

IIIThe break

The crash is not an object. It is an instability propagating through a fluid.

A breaking wave is usually animated as a shape that turns white and falls over. Physically it is a continuous loss of stability with a definite cause: the water near the crest begins to move faster than the shape that is carrying it, and at that point the crest can no longer stay attached.

CriterionWhen a wave must break

The classical index compares wave height to local depth. The more fundamental criterion compares the crest's particle velocity to the wave's phase velocity — a Froude number:

depth index: γ_b = H_b / h_b ≈ 0.78 (use 0.70 – 0.83, varying slowly) Froude: Fr_c = U_c / c → 1 probability: P_break = 1 / [ 1 + exp(−a(B − 1)) ] B = H / (γ_b h) A sigmoid rather than a hard threshold, so that two nearly identical waves do not break at exactly the same place. The breaking point is then wherever H(x_b) ≈ γ_b h(x_b) — an emergent location, never a fixed distance from shore.

Breaker typeWhat the slope decides

The same incoming wave produces a completely different break on a different beach. The surf similarity parameter predicts which:

ξ₀ = tan β / √(H₀ / L₀)
ξ₀TypeCharacter
lowSpillingcrest aerates and tumbles; no hollow; long turbulent face
moderatePlunginglip throws forward, encloses air, impacts the face
highCollapsingface collapses without a clean overturn
very highSurgingwave surges up the face with little breaking at all

The jetBallistics, briefly

Once the crest detaches it is a projectile. It leaves with the crest's own velocity plus a throw that scales with √(gH), and from then until impact it is under gravity alone:

U_j ≈ U_c + C_j √(g H) W_j ≈ C_w √(g H) x_j(t) = x₀ + U_j t z_j(t) = z₀ + W_j t − ½ g t² impact when: z_j(t_i) = η_face(x_j, t_i) The forward-and-downward pairing is the whole visual signature of a plunging lip. The critical event is not the throw but the impact — and note the lip does not land at the height it left from. It falls into the trough in front, most of a wave height lower, and that drop is what buys the flight time that makes a tube deep.

The overhang criterionWhether it can curl at all

There is a single number that decides whether a wave can overturn, and it is worth stating on its own because everything else is downstream of it. A crest does not stand still. It travels at the characteristic speed u + √(gh) — that is precisely why crests steepen and overtake their own troughs. So what matters is not how fast the lip is thrown, but how fast it is thrown relative to the crest it is leaving:

Λ = U_j / √(g h) = C_j √( 2 η_ex / h ) reach relative to the crest: √(gh) · (Λ − 1) OVERHANG REQUIRES Λ > 1 Below one, the thrown water never gets ahead of the crest carrying it: the face merely steepens and leans, and no amount of throwing harder will produce a curl, because the crest outruns the throw. A surface that overhangs is multivalued, and a depth-averaged field cannot be — so the sheet has to be carried separately from the body it left.

The same criterion appears again, independently, from the rendering side. If the drawn surface is displaced along the trajectory, x′ = x + U_j τ, and age and position are locked together along the band, then dx′/dx = 1 − Λ — and a fold, meaning a genuinely multivalued drawn surface, needs that to go negative. Same inequality, arrived at two different ways.

The ratio of the two launch components then decides something separate: whether the throw pitches over or merely shoots flat. A plunging lip rises about a quarter to a third of a wave height above the crest, reaches a little over one wave height forward, and enters the water steeply — 60° or more. That steep entry is what completes the rotation, the going over and down and back under. A flat, fast throw with the same Λ clears the crest and never comes back over itself.

Two surfacesDrawing what a single-valued field cannot hold

The criterion above ends in an impasse. Overhang requires a multivalued surface — two water heights above one ground point, the lip out in front and the body beneath it — and a depth-averaged field h(x,y) cannot represent that. It has one value per column by construction. Refining the grid does not help; the limitation is in what the variable is.

The resolution is to stop asking one surface to be in two places, and to draw the water twice from the same state:

BODY z = b + h the volume that keeps moving forward LIP x′ = x + U_j τ the sheet that left it z′ = b + h + W_j τ − ½gτ² drawn only where a jet is live: cov = f(age, amplitude), discard where cov ≈ 0 Two draws of one field, not two simulations. The lip carries no mass of its own — it is the same water, redrawn where the ballistic solution says it has got to by age τ. Nothing is added to or removed from the solver, so conservation is untouched.

The order matters. The lip is drawn after the body and does not write depth, so the body stays visible through the sheet — which is the whole point, because a thrown sheet of water is thin enough to see the wave through. Draw it depth-writing and the barrel fills with an opaque tongue and the effect collapses.

The undersideA surface seen from the wrong side

Once the sheet is out in front of the body, the camera can see under it, and a fragment on the inside of a curl is a back-facing one. Every shading term that uses a normal is then pointing the wrong way, and two corrections are needed — one geometric, one about how far the light has actually travelled.

back-facing: N → −N path length: t_sheet = t₀ + t₁·τ_lip (≈ 0.10 – 0.32 m) NOT the column depth h transmitted: T = exp(−σ_e · t_sheet) t_sheet ≪ h ⇒ T stays large Using h here is the mistake that makes barrels read as solid. The column under a breaking crest is deep, so exp(−σ_e h) is essentially zero and the underside goes black — or, once foam is added on top, flat white. The sheet is centimetres thick, and the light comes through it.

That is why a real barrel glows. You are looking at a backlit pane of water a few centimetres thick with the sun on the far side, so it transmits its own colour at high intensity — the green-gold that no amount of reflected sky will ever produce. Aeration is raised slightly, because a thrown sheet is full of bubbles; foam coverage is lowered, because a thin sheet lit from behind reads as translucent rather than white.

ImpactWhere the organised wave dies

At impact the jet's kinetic energy is redistributed, and none of it goes back into the wave:

p_imp ≈ ½ ρ U_j² E_total → E_turbulence + E_air + E_foam + E_sediment + E_remaining air entrained: Q_air = C_a A_i U_i A harder crash traps more air, and the trapped air is what makes the water white. The cavity should fragment immediately — a clean geometric air pocket that persists is one of the most obvious tells of a fake.

Splash-upThe crash happens more than once

A plunging jet does not merge with the water it lands on. It penetrates, drives a wedge of water ahead of it, and that wedge is thrown up and forward as a second jet — which can itself overturn, land, and do it again. Photographic sequences of real breakers show two to three of these cycles, and they are most of what makes a crash look violent rather than merely white: the whitewater keeps relaunching itself ahead of the break.

rebound: U_j , W_j → r·U_j , r·W_j r ≈ 0.57 (≈ ⅓ of the energy) An inelastic collision keeping about a third of its energy keeps 0.57 of its speed. Six cycles follow from that, of which the first three carry 97% of the throw — which is exactly why a real one reads as "two or three" rather than as a decaying ring. For the impulse no extra state is needed: relaunching is the model. For the geometry it is not enough, and that turns out to matter more.

GenerationsWhy one state per place is not enough

Store the throw as a single record per point — age, speed, rise — and the cascade above costs nothing, because each relaunch simply overwrites the last. That works perfectly for the impact it delivers. It fails completely for what you see, and the failure is easy to miss because nothing about it looks broken:

one record per point ⇒ ONE SHEET PER PLACE the curtain lands at t_land → the splash-up is born at t_land → and the curtain's record is gone so the two can never be drawn together, at any grid resolution Generation 0 is destroyed at the instant generation 1 is created. The falling curtain and the tongue leaping up through it are never on screen at the same moment, because the thing that would have to remember both only remembers one.

And that overlap is precisely what a completed crash looks like. Watch any plunging breaker: the curtain is still pouring down while the splash-up is already climbing in front of it, and often a third sheet behind that. A crash drawn as a clean handover from one stage to the next reads as almost right — everything is in the correct place at the correct time, and it still looks like a sequence of events rather than an event.

The fix is not a finer grid; no resolution helps, because the limit is in what is stored rather than where. Three records per point, each with its own drawn surface. An arrival hands r of its speed to the generation behind it and keeps its own place in the air.

measured, once the field settles: in flight peak age curtain 0.13 % 0.41 s splash-up 0.04 % 0.26 s third 0.01 % 0.18 s two or more sheets over the same ground: 0.14 % all three: 0.05 % Occupancy and flight time both fall by about the rebound ratio per generation, which is the cascade doing what the physics says it should. The last line is the one that matters: it was identically zero before, and no parameter could have changed that.

Not a projectileThe lip is still attached

Treat the thrown lip as a body in free flight and its whole character is fixed by two constants. Reach, rise and entry angle stop being independent:

x = U_j t z = W_j t - g t^2/2 flight time is about 2 W_j/g, so reach = U_j t = 2 C_j C_w H <- needs BOTH coefficients Lambda = U_j/sqrt(gh) <- needs C_j alone entry = f(C_j/C_w, fall) <- a constant. No wave enters differently. Measured consequence, and the thing that gave it away: the entry angle came out at 46 degrees at every station in the surf zone, with no spread at all. Heavy breakers and weak ones alike. Nothing in the model could vary it.

So raising C_j until the lip could overhang produced a tongue six wave heights long, and pulling C_w back to shorten the tongue flattened the entry. Every setting trades one against another because the projectile has only two knobs for three quantities.

But a real lip is not in free flight. It is still attached: the sheet behind it is in tension, and the air cavity it is closing over is collapsing and pulling it in. That is precisely why a real plunging lip comes down at sixty degrees or steeper while reaching only about one wave height forward — a combination the ballistic model says is impossible.

u(t) = U_j exp(-t/T) horizontal only; vertical stays ballistic reach is bounded at U_j T, instead of growing with flight time the tip STEEPENS on its own: horizontal speed dying while vertical grows One drag time on one axis. The difference between a parabola and a plunge is that a parabola leaves as steeply as it arrives, and a plunge does not.
measured, T = 0.30 s: before after real reach 3.1 H 1.4 H 1 - 1.5 H entry angle 46 deg flat 38 - 73 varies with intensity Lambda (mean) 1.09 1.60 > 1 to overhang Three constraints satisfied together that could not be satisfied together at all before. The entry angle varies now because it is no longer a function of constants — it depends on how much horizontal speed a given throw has left when it arrives.

The crownThe water that gets landed on

The tip arriving is only half the collision. The water it lands on is driven out of the way and thrown upward around the line of impact — and on a plunging breaker that line is the crest, so what goes up is a curtain along it rather than the circular crown a single droplet makes. Same mechanism, different topology.

on arrival: w = C_c sqrt(I) z = 0 thereafter: w -= g dt z += w dt Carried as height and vertical speed rather than as an age. An age would have to be differentiated to get a height, and neighbouring points reset their age on their own impact frame — a discontinuity. Anything discontinuous that displaces geometry tears the mesh, which is a lesson this build learned the expensive way.

It must also be white. Drawn as a displacement alone it is a bump of ordinary sea, because the raised surface is shaded exactly like the water it was raised from. Water thrown up by an impact is the whitest thing in the scene — shattered and full of entrained air before it is properly airborne — and keying the foam to the crown height gives the gradient for nothing: solid at the root, spray at the top.

The rollerWhat advances after the arc

For the second or two after the arc finishes, what the eye reads as the wave breaking is not the lip at all. It is the roller: a recirculating mass of aerated water riding the front face, carried at the wave speed, with a cross-section of order 0.9 H² (Svendsen 1984). Drawn as a flat lid of foam with isotropic noise on it, it is a boiling pot. Two properties noise cannot have:

IT STANDS PROUD a body of water on the face, lifting the surface coherently IT IS ANISOTROPIC a horizontal vortex tube lying PARALLEL to the crest, so its structure is long along the crest and short across, and it translates shoreward with the wave carrying it Square, stationary noise reads as texture applied to a surface. The same noise stretched along the crest and advected shoreward reads as water turning over.

The touchdownAn arc has to finish

There is a second, smaller thing that keeps a crash from reading as complete, and it hides in a single line of bookkeeping. When the tip arrives, the throw is over — so the record is cleared, and the sheet stops existing between one frame and the next.

tau ← 0 at t_land the sheet vanishes in one frame instead: hold the record open past t_land let the ballistic curve carry it on DOWN into the water fade it out over the same window The arc's last act was a disappearance. Water does not leave; it goes in. Everything before the final instant was already right, which is why this reads as a vague sense of incompleteness rather than as a visible glitch.

One trap, for anyone implementing this. It is tempting to flag "landed, now sinking" by making the age negative — one field, and the sign is free. It does not survive contact with a filtered texture: the state is read back through the flow with linear interpolation, so a flying age and a sinking age get averaged, and +0.30 mixed with −0.10 is not a state any parcel was ever in. Keep the age monotonic and let it run past t_land.

ReboundWhat a splash-up does not inherit

Each rung of the cascade is launched by rotating the previous velocity upward and scaling its magnitude — the rotation is what makes a splash-up read as its own event rather than a smaller copy of the throw. But the flight time was being recovered by inverting the launch relation, which is exact only for the primary curtain, whose vertical speed really was set by the wave height. Feed a rotated velocity through that inversion and it manufactures a wave height out of the rotation angle:

gen U_j W_j recovered H flight 0 4.43 1.22 0.500 exact 0.427 s 1 2.02 1.67 0.933 0.584 s 2 0.66 1.34 0.602 0.469 s 3 0.21 0.82 0.228 0.289 s Generation 1 is thrown at 0.57 of generation 0's speed and was given a 37 % longer flight than the throw that created it; generation 2 outflew it too. A rebound cannot outfly its parent under gravity. The consequence was audible in the timing rather than visible in the arc: the sheet went below the surface it launched from and was occluded a quarter of a second before the impact it was supposed to deliver fired.

The fall is therefore a property of the throw, not something recovered from it. The primary curtain falls the height the crest gave it; a splash-up leaves the surface at the impact point and lands forward of it, where on a shorebreak the ground is rising, so its fall is at or below zero and zero is the correct neutral. Zeroing it shortens the cascade, which is restored where it belongs — in the window over which a landed tip sinks into the water, which is what a splash-up actually spends that time doing.

There are four rungs. The hand-off is geometric, so three ran out while there was still visible water in the chain: a real run-up ends as several sheets, each thinner and slower than the last, overlapping because the one in front is still draining when the next lands on it.

BubblesA cascade, not a texture

Large cavities are unstable and break down through a size cascade that conserves volume, then dissolve at a rate that depends on radius:

R_parent³ ≈ Σ R_child³ dr/dt = −K_d / r ⟹ r²(t) = r₀² − 2 K_d t Because the dissolution rate goes as 1/r, small bubbles vanish quickly and large ones persist. Foam therefore changes texture as it ages rather than simply fading out — the fine sparkle goes first.

FoamGenerated, advected, decayed

Foam is a field with its own transport equation, not paint applied to the surface:

∂F/∂t + u·∇F = D_F ∇²F + C_F G − λ_F F with breaking source: G = max(0, H/h − γ_b)ᵖ Because foam is advected by the flow it forms streaks that follow real currents. Because it decays exponentially it survives longest where it was thickest. Because it is generated only where H/h exceeds the threshold, it appears exactly where the water is actually breaking.

BoreWhat survives the crash

After the crest collapses the remaining momentum travels shoreward as a turbulent hydraulic transition, and its leading edge is where the whitewater concentrates:

c_b ≈ √(g h_b) foam generation ∝ S = |∂u/∂x|
A realistic wave should never simply turn white and fall. The bottom slows it; the crest keeps going; the upper water outruns the shape beneath it; a sheet becomes a jet; gravity takes the jet; the jet hits the face; the impact traps air; the air becomes bubbles; the bubbles become foam; the remaining momentum becomes a bore; the bore becomes swash. The crash is one event inside a much longer process

IVThe shore

The beach is the only part of the system with a memory.

Governing equationsNonlinear shallow water

Everything from the surf line to the top of the swash is one solver — mass and momentum over a moving wet/dry boundary:

∂h/∂t + ∇·(h u) = 0 ∂(hu)/∂t + ∂(hu² + ½gh²)/∂x + ∂(huv)/∂y = −gh ∂z_b/∂x − τ_x/ρ + F_x Solved numerically, this alone produces a convincing swash zone. The wet/dry front is not interpolated as a line; it is extracted as the contour where depth crosses a small threshold.

UprushMomentum against a slope

To first order the swash front is a projectile running up a ramp, which gives a surprisingly useful estimate of runup:

u₀ = C_u √(g H_b) x(t) = x₀ + u₀t − ½ g β t² t_max = u₀ / (gβ) R_u ≈ u₀² / (2gβ)

But real swash loses energy in several ways at once, and the deceleration is strongly nonlinear:

du/dt = −g β − C_f u|u|/h − C_p u − C_t u³ gravity friction percolation turbulence

InfiltrationWhere the water actually goes

Sand is porous, and a great deal of every wave simply disappears downward rather than flowing back:

q_p = K_p h ⁿ n ≈ 0.5 – 1.5 ∂h/∂t = … − q_p This is the reason a runup can end without a matching backwash, and the reason foam is left stranded in a line rather than dragged back down. It is one of the strongest and most commonly omitted cues on a real beach.

AsymmetryBackwash is not uprush reversed

Uprush and backwash have different durations, different distances and different velocities. A simulation that mirrors them looks mechanical:

A_s = R_u / R_bw allow 0.5 < A_s < 2.0 u_bw = −U_bw · F_r · F_p · F_s (roughness, permeability, saturation)

MemoryWhy the sand stays dark

The beach remembers the last wave, and the one before it. Wetness is a field with a decaying memory, and the whole visual character of the swash zone comes from its overlap with the next wave:

∂W_b/∂t = I(x,y,t) − λ_d W_b W_b(t) = W_b(0)e^(−λt) + ∫ I(τ) e^[−λ(t−τ)] dτ Drying is not uniform: λ_d depends on sun, wind and permeability, so the beach dries in patches rather than as a retreating line.

Two wetnessesDark and shining are not the same thing

It is tempting to drive brightness, roughness and specular response from that one field. It is also wrong, and the error is worth stating plainly because it is easy to make and hard to see. Two different mechanisms darken sand, and they run on timescales a hundred apart.

PORE SATURATION water filling the space BETWEEN grains index step at each boundary falls 1.00→1.55 to 1.33→1.55 light penetrates further, is absorbed before scattering back out → the sand goes DARK. Drains over MINUTES. SURFACE FILM water lying ON TOP, in excess of pore capacity, continuous, held by surface tension against the grain roughness a continuous water surface is a dielectric interface → the sand becomes a MIRROR. Soaks away in SECONDS.

Collapse them into one scalar and the mirror becomes tied to the drying rate. With a specular threshold at W_b ≈ 0.53 and λ_d = 0.011 s⁻¹, the beach keeps a sheen for fifty-eight seconds after the water has gone. The whole shallow zone then ages at one rate, and the wide matte-dark band — pores full, film gone, which is the state a real beach spends most of its time in — cannot exist at all.

saturation: darkens, and slightly smooths B = B_dry − ΔB·S_p film: mirrors, and only mirrors F = F(θ) gated on film film drain: d(film)/dt = −K_p (1 − c·S_p) − E_evap The film soaks in more slowly where the pores are already full, because there is nowhere for it to go — about 1.8 s at the water's edge against 0.4 s on dry sand up the beach. That gradient is why the shine lingers along the waterline and vanishes almost at once further up, and a single scalar cannot produce it.

DewettingThe last water does not retreat

Below roughly a grain diameter a film cannot stay continuous over a rough surface. It pins on the high grains, drains into the hollows, and breaks into patches and rivulets with dry islands between them. This is why the last water on a beach does not slide back with the wave — it stays where it is and disappears in place, which is a different motion entirely and one of the clearest signatures of real swash.

MonotonicityWhy the edge of the water is only allowed to go one way

The shoreline is where two surfaces meet that were computed by different means: a water mesh carrying the solver's depth, and a sand mesh carrying two to four centimetres of sub-grid relief the solver never sees. Every visible fault at that join — bare sand showing through a retreating sheet, an advancing tongue breaking into islands — is the same arithmetic mistake wearing different clothes, and it is worth stating as a rule because the rule is what fixes it, not the tuning.

The water is drawn where its depth clears a threshold. So any term that can reduce that depth can push it under, and a hole opens. Most of the tempting terms are signed, and a signed term does not have a good setting — it has two bad ones:

sub-grid relief subtracted from the depth, ungated h drops over the HIGH ground → gaps in a fast backwash the same term gated by the Froude number, so the sign flips h drops in the HOLLOWS instead → the arriving sheet breaks into lobes One term, one mechanism, two symptoms, and every threshold on it merely trades between them. The same fault reappears in any geometric form: make the surface ride the drawn bed and the sand comes through on the way out; perturb the depth by the specific-energy relation and the sheet vanishes in hollows on the way in.

The way out is not a better constant. It is to require every term touching the edge to be monotone — able to move the result in one direction only — so the failure stops being expressible rather than being tuned away.

the depth tested by the cut is the solver's own, and nothing subtracts from it the noise warp that fingers the edge max(warped, true) may reach OUT, never cut in the surface against the sand it lies on max(eta, sand + e) may COVER sand, never uncover water a raft of foam standing proud min(lift, 0.55 h) may only be REDUCED by the water under it Each is self-limiting. In water of any real depth the surface is already far above the sand and the max does nothing, so there is no fade to tune and no band where it half-applies; the same is true of the others. The relief still bends the bed normal for refraction and caustics, and the warp still makes the edge finger — they simply do it without a vote on whether water exists.

The foam term is worth its own line, because it looks physical and is not. A raft of foam does float proud of the water it sits on — but of the water it sits on. Lifting the surface by a fixed amount wherever foam is thick will hoist a millimetre-deep sheet three centimetres clear of the sand, and where the foam is patchy the lift is patchy, so the sheet breaks into floating lumps. Archimedes, roughly: how far a raft stands proud is set by the column supporting it, which makes it a proportion of the depth and not a constant.

One field, two surfacesHow foam ended up drawn twice

The longest-running fault at this join was not at the join at all. Foam is one advected tracer field, and both surfaces were drawing it: the sand painted stranded foam wherever the field was non-zero, and the water painted floating foam on top of the same field. Deliberately differently, too —

sand coverage(p × 1.9, velocity 0, t × 0.35) stationary water coverage(p, velocity u, t × 1.00) advected Nearly twice the wavelength, nailed to the ground, running at a third of the rate — sliding underneath a pattern moving at the local flow speed. Frozen, both read as foam and nothing looks wrong, which is why this survived every still taken of it. Only the relative motion is visible, and it is worst on a fast backwash, where the foam is thickest and the velocity difference largest.

The physics has no two layers in it. Foam is either floating or stranded. Floating foam goes with the water and the water draws it; stranded foam is what the water left behind, and it cannot be left behind somewhere the water has not left yet. So the sand's copy fades out on the complement of the water's own opacity, and neither is ever drawn over the other.

The general lesson outlives the fix: when a fault will not respond to tuning, the question is not which constant is wrong but how many times the same quantity is being drawn.

FetchingA value read from the wrong place

Wetness ran ahead of the waterline for a long time, and three separate mechanisms were each doing it. Two were thresholds. The third could not be fixed by any threshold, because the number itself was being read from somewhere else.

Every tracer field on the sand is sampled through a noise warp, which exists to hide the solver's 19 cm bilinear steps. That warp reaches up to 0.8 m in an arbitrary direction — so wherever it happened to point seaward, sand ahead of the water sampled saturation from behind the water and came back wet. The cure is the asymmetry used everywhere else here: take the minimum against the unwarped sample, and the warp can still find drier ground and cut the contour in, but is arithmetically incapable of finding wetter ground and pushing it out.

The other two were ordinary once found. Saturation began accumulating at 0.2 mm of depth while the renderer does not draw water until 0.6 mm — two contours from two constants, with no reason to agree — and a lateral wicking term diffused saturation into cells the water had never touched. That one was deleted rather than tuned: suction between grains moves water millimetres, not the 19 cm a cell of this grid spans, so at this resolution it was never modelling anything real. The threshold now evaluates the renderer's own cut field, so the two contours are the same contour by construction rather than by agreement.

RillsWhy the retreat looked right and the run-up did not

A backwash gets its shape for nothing. It drains down channels it cut itself, so its outline is channelised — tongues and interfluves with a direction and a hierarchy — and no amount of noise reproduces that. Noise gives a wiggly line, and the eye tells the difference at once.

The same micro-topography steers water going the other way. A run-up runs further up the channels and is held back on the ground between them, which is why swash tongues and backwash rills line up on a real beach: one field, two directions. The solver already keeps the flow accumulation that records where the draining water concentrated — the sand has been drawing its rills from it all along — and it had simply never been offered to the water.

flow accumulation, measured: 0 .. 4, median 0.46, saturating in the channels rill = smoothstep(0.30, 3.00, accumulation) cut ×= mix(1.42, 0.58, rill) further up a channel, back on a ridge Applied to the threshold and not to the depth, which is the whole point. An additive term can only push a contour out, so every earlier attempt at this could make the outline bulge and nothing else. A threshold moves it both ways, and still cannot open a hole inside the sheet, because the body is centimetres deep while this never leaves the millimetre band.

FingeringWhy more octaves did not help

Before the rills, the edge was fingered by noise, and it kept coming out evenly scalloped however many octaves went into it. The reason is geometric rather than spectral: isotropic noise displaces a contour by the same amount in every direction, and a contour pushed equally in all directions bulges into round lobes. That is a scallop. Adding octaves only puts smaller scallops on the larger ones.

Fingering at a moving contact line elongates along the flow. On a beach the flow is cross-shore, so the tongues are long and narrow and point up the beach — the same geometry the retreat gets free from its rills. The noise is therefore sampled in a frame squashed three to one across shore and displaced with a cross-shore bias. The total reach is bounded at about half a metre, which matters: pushed to nearly two metres it samples depth from well behind the front and draws it out as filaments of water lying on sand the swash never reached.

SoftnessAn edge measured in metres, not millimetres

The water fades in over a range of depth. But what the eye sees is that range divided by the local depth gradient, and the gradient is not a constant: a fast arriving bore climbs steeply behind its toe, so a fixed few millimetres of ramp collapses into a few pixels and the edge goes hard, while the same ramp on a slow sheet spreads into a wide soft band. Identical numbers, opposite results — and it is the fast front, the one that matters most, that comes out worst.

ramp top = max( floor, cut + |grad h| · L ) L = 0.62 m bore, 5 cm over a metre grad h 0.05 → top 31.6 mm gentle sheet grad h small → the floor Dividing the gradient out fixes the softness in space. The floor is what protects the backwash: every gentle case keeps the numbers it already had, so only genuinely steep fronts are feathered.

Both directionsThe film the advance never had

A depth-averaged solver carries one depth per cell, and a real sheet has structure below that at its edge. The retreat had a residual film for exactly this reason for a long time — and the argument never mentioned direction. An arriving sheet has the same problem at its leading edge, where the true profile runs to tenths of a millimetre over a distance the grid cannot resolve.

So the film is mirrored: same gate, same thinness window, same amount, with the sign of the flow flipped. It cannot reach dry ground, because where there is no water the flux and the depth are both zero and the term goes with them. Once it existed, every advance-only shading curve that had been added to compensate for its absence could be deleted — two ramps that differ are two edges that differ, and the retreat's was the one that was right.

The rimWhere a sheet of water ends

The edge of a sheet running over sand is not simply where the depth reaches zero. Surface tension holds a bead there, and its width is set by the one length that balances capillarity against gravity:

ℓ_c = √( σ / ρg ) = √( 0.072 / (1000 · 9.81) ) ≈ 2.7 mm within a few ℓ_c of the contact line: curvature is set by σ, not by the flow → the surface normal rolls over toward the horizontal → roughness collapses toward specular Blended in over the last few millimetres of depth, strongest as h → 0. It costs one smoothstep, and it is the difference between an edge that merely stops and an edge that is held.

This is the bright thread along the front of every advancing sheet, and the outline that survives on a retreating one. A millimetre-scale effect that is plainly visible from ten metres away, because a curved specular strip gathers light from a wide arc of sky and sends it to the eye — the same reason the wet rim of a glass catches light.

FootprintsWhat a foot does to the packing

Standing on sand is a mechanics problem with three separate outcomes, and a beach shows all three at once. Peak load is roughly 1.2 body weights walking and 2.6 running, and the sand answers differently depending on how much water is already in it.

depth held: hold = 0.30 + 0.70 · smoothstep(0.04, 0.50, W_b) DILATANCY shearing a dense packing must OPEN it (Reynolds, 1885) pore volume increases → surface water drains inward → the sand goes PALE it needs water to lose: ring ∝ smoothstep(0.30, 0.85, W_b), decay ≈ 0.85 s⁻¹ PONDING a hollow below the water table fills, and drains slower than flat sand Dry sand has no cohesion, so the walls of a print collapse inward and the mark is shallow and soft-edged. Damp sand has capillary bridges between grains, holds an edge, and takes a crisp deep print. Same foot, same load, different sand.

The pale halo is the counter-intuitive one, and worth stating plainly because it looks like a rendering error: the sand around a fresh print on saturated beach goes lighter, not darker, and stays that way for a second or two before capillarity refills it. It is the surface draining into pore space that the shearing has just opened. Ponding is the reason a line of prints survives a swash that runs over it — the flat sand drains and pales in seconds, while the hollows stay dark for minutes.

Stride is not a cadence multiplied by speed. A walk lengthens its step with speed and keeps a slow cadence; a run jumps to a much longer step at a cadence that then barely changes — which is why runners' prints are far apart and evenly spaced however hard they are running.

walk: L = 0.42 + 0.25 v run: L = 1.15 + 0.16 (v − v_run)

SedimentSand that moves

Grains mobilise when bed shear passes a threshold, and the bed evolves to conserve sediment:

τ_b = ρ C_f u² θ = τ_b / [(ρ_s − ρ) g d] mobile when θ > θ_c ≈ 0.03–0.06 (1 − p) ∂z_b/∂t + ∇·q_s = 0 Run slowly (τ_morph ≫ T_wave) this produces berms, bars, rip channels and scour — the beach shaping itself rather than being shaped by hand.

Long periodsThe slow shove

Two motions much slower than the waves keep the shoreline from looking periodic: bound long waves, and the tide.

η_IG = A_IG sin(2π f_IG t + φ) f_IG < 0.05 Hz, A_IG ≈ 0.01–0.30 m η_tide = A_tide sin(2π t / T_tide + φ) T_tide ≈ 12.42 h x_s(t) = x_mean + x_swash(t) + x_IG(t)

NumericsWhat the timestep is allowed to be

CFL = c Δt / Δx < 1, preferably ≤ 0.5 ⟹ Δt ≤ C Δx / √(g h) Resolution should be nested rather than uniform: metres offshore, tens of centimetres through the surf, and a few centimetres in the swash where the interesting geometry is.

VThe light

The sun does not colour the beach. It illuminates a continuously changing optical system.

Light leaves the sun, is filtered and scattered by the atmosphere, meets a surface whose orientation is changing everywhere at once, splits into a reflected and a transmitted part, travels through water that absorbs it unevenly by wavelength, bounces off pale sand, comes back out, and only then reaches a camera that has to compress an enormous dynamic range into an image. Every one of those steps is a place where a shortcut becomes visible.

AtmosphereWhat actually arrives

I_surface = I₀ e^(−τ m) I₀ ≈ 1361 W/m² air mass: m = 1 / [ sin α_s + 0.50572 (α_s + 6.07995°)^(−1.6364) ] Rayleigh: σ_R(λ) ∝ 1 / λ⁴ The 1/λ⁴ dependence does two jobs at once: it makes the sky blue, and it reddens the direct beam at low sun, because the short wavelengths have been scattered out of it. The sky is then a second light source in its own right — E_total = E_sun + E_sky + E_indirect — which is why shadows on a beach are blue rather than black.

SurfaceA million tiny mirrors

The water surface has a normal that varies continuously in space and time. Every ripple is an independently oriented mirror:

n = (−η_x, −η_y, 1) / √(η_x² + η_y² + 1)

FresnelThe 2 % that becomes 100 %

F(θ) = (F_s + F_p) / 2 at normal incidence: F₀ = [(1.00 − 1.33)/(1.00 + 1.33)]² ≈ 0.02 at grazing: F(θ) → 1 Water reflects only about two per cent of light arriving head-on, and nearly all of it at a glancing angle. This single curve is why you can see the bottom at your feet and only sky at the horizon — and it is why the ocean brightens toward the horizon without anyone painting it brighter.

GlitterWhy the sun path shimmers

A glint occurs where the local normal happens to bisect the sun and view directions. Because the surface carries an enormous population of normals, a sparse and constantly changing subset satisfies the condition at any instant:

H = normalize(L_s + V) glint where n·H → 1 G ∝ F(θ) · D(n,H) · I_sun D_GGX = α² / [ π ( (n·h)²(α² − 1) + 1 )² ] The roughness α is set by the wind, so a fresh breeze widens and fragments the glitter path while calm water narrows it into a coherent column. Scintillation is free: as the waves move, dn/dt ≠ 0, so dG/dt ≠ 0.

The sun has a widthWhy a highlight cannot be a point

The sun is not a point source. It subtends an angular radius of 4.65 mrad, so no specular reflection of it can be narrower than that, however smooth the water is. Convolving the disc with the microfacet lobe adds in quadrature:

a' = √( a² + (θ_sun/2)² ) floor a ≥ 2.3 × 10⁻³ mirror limit: Ω_sun = π(4.65e-3)² = 6.79e-5 sr → L = F/Ω = 14700 lobe peak at the floor: 1/(4πa²) = 15000 The two agree, which is the check that the floor is the right one. Floored an order of magnitude below it, the lobe is narrower than its own source and the highlight has to be rescued by a clamp further down — which produces a flat plateau roughly the angular size of the sun at a small fraction of its brightness, in place of a peak of the right width and the right height.

Water columnColour by subtraction

I(z,λ) = I(0,λ) e^[−K_d(λ) z] with a_red > a_green > a_blue bottom seen through depth h: T_total = e^(−2 K_d h) The factor of two is the light going down and coming back. Tropical turquoise is not a colour choice: it is what remains of white sunlight after red has been removed by a metre of water, reflected off pale carbonate sand, and filtered again on the way back up.

CausticsLiquid sunlight

The moving surface acts as a lens, focusing light into bright moving networks on the bed. Their brightness is the inverse of how much a bundle of rays has spread:

J = ∂(x′,y′) / ∂(x,y) I_caustic ∝ 1 / |J| Because η changes, J changes, so the pattern is never static. Calm water gives coherent, high-contrast caustics; chop breaks them into something broader and fainter.

RegistrationWhere a caustic is painted

A caustic net is not a property of the water. It is light on the bed, and the bed is not seen where it is: refraction displaces the sand along the view ray by up to about a depth laterally, which is a function of the camera. Compute the net at the surface point and paint it on a bed sample taken a metre away and the pattern slides across the sand as the viewer moves, in the opposite sense to the sand texture underneath it. That parallax — a pattern that swims relative to what it is lit on — is the signature of a caustic living on the water instead of on the bottom, and it is entirely a bookkeeping error.

Foam and bubblesWhy white is white

Foam is not white because it is painted white. It is a dense population of air–water interfaces; light entering it refracts, reflects internally, meets another interface and scatters again, and after enough of those events wavelength-dependent absorption has barely had a chance to act:

L_foam ≈ L_in [ 1 − e^(−μ_s L) ] μ_s,b = N_b σ_s The exponential does all the work. Where the bubble path is long, L_foam → L_in and the core saturates to opaque white. Where the patch thins at its edges, μ_s falls and the water, sand and shadow underneath show through. Translucent edges around an opaque core are the signature of real foam.

ConservationNothing reflects more than it receives

Whitewater is the brightest thing in a surf frame, and the temptation is to reach the look by raising its gain. Measured thick fresh whitecap reflectance is 0.5–0.6 broadband and 0.7–0.9 in the visible; a passive surface cannot exceed 1 by definition. Four separate sites here normalised whitewater to effective albedos between 1.38 and 1.85, which put sunlit foam at roughly five times sunlit dry sand where the physical ratio is nearer 2.6.

The correction is not a single scalar, and that is the interesting part. The direct and ambient halves of those expressions are in different units — one an irradiance, one a prefiltered radiance — so while the direct half was well over one, the ambient half was already close to a factor of two under. Cutting both by the same number fixes the overshoot and doubles the undershoot, which shows up as backlit foam and overcast foam losing half their light. The two channels have to be separated: direct brought down to a legal albedo, ambient rescaled so that every shadowed value is unchanged.

Two regimesWhat a small droplet actually does

Drag on a falling droplet is linear in its velocity only in the small limit. The inertial law that fits millimetre rain — terminal speed proportional to radius — is right for the ballistic arcs the eye follows, and wrong by more than an order of magnitude for the fine fraction, which is most of the population by number:

inertial v_t = g/k, k = C/r 1 mm → 6.13 m/s (measured 6.49) Stokes v_t = 2ρg r² / 9μ 20 µm → 0.048 m/s the two cross at r = 51 µm on their own Capping the drag coefficient to keep an explicit integrator stable makes every fine droplet fall at the cap — here 0.70 m/s against a true 0.048, some fourteen times too fast. Integrating the linear drag exactly removes the stability constraint, and then the coefficient can carry both regimes with no tuning constant and no clamp.

What this buys is not a detail of the arcs, which are unchanged: it is wind-blown mist, which otherwise does not exist. A forty-micron droplet settles ten centimetres over its whole life while the wind carries it ten metres, so a plume leaves a drifting veil behind it instead of raining straight back down.

SprayThousands of small lenses

τ_d = ∫ σ_d N_d ds I = I₀ e^(−τ_d) G_total = Σᵢ Fᵢ Dᵢ I_sun Each droplet is a curved lens that can reflect, refract and focus. Backlit, a spray cloud becomes far brighter than the wave that threw it — L_rim ∝ T_water + S_bubble + S_spray — which is the single strongest cue in a sunlit tropical breaker.

Wet sandA temporary mirror

A retreating swash leaves a film one to ten millimetres deep. For a few seconds the beach is a water surface with its own normals and its own glints, and then it is not:

R_wet = R_dry + ΔR · W_b roughness_wet = roughness_dry − Δr · W_b

CameraCompressing the range

Direct sun, bright foam, mid-water and shadow span something like nine orders of magnitude. The renderer must carry that range and only compress it at the very end:

L_camera(λ) ≈ T_atm [ F·L_sun + (1−F)·L_water + F·L_sky + L_bottom + L_scatter + L_foam + L_spray + L_indirect ] + L_atm R,G,B = ∫ L(λ) · r,g,b(λ) dλ C_pixel = ToneMap( Exposure × L_camera ) The tone mapper's job is to compress a physically computed range, not to create the lighting. Get that backwards and the glints clip, the foam goes flat and the water loses its gradients.

VIWhat lies on it

Everything above is water. The last of the realism is in what the water leaves behind — the sand it sorts, the film it cannot quite take back, and the stones it drops — and in one discipline that governs all of it: when you draw something explicitly, you must take it away from whatever was standing in for it.

RipplesWhy a ripple field cannot be a sine

Wind ripples on dry sand are the most-drawn and least-convincing feature of any beach, and the reason is topological rather than aesthetic. On a real bed, crests end: one crest forks into two, a neighbour dies out between them, and the field is full of these terminations. Describe the field by a phase φ whose level sets are the crests, and a fork is a point where the phase is undefined and must wind by a full turn around it.

crest set: φ(x) = 2πn height h = A cos φ a fork at a requires ∮C ∇φ · dl = ±2π for any C enclosing it but for φ = k(x)·x with any smooth k, A : ∇×∇φ = 0 ⇒ ∮ ≡ 0 So no amount of varying the wavelength or the amplitude can ever split a crest. This is why the usual fix — modulate the frequency and hope — produces a field that is differently uniform and still reads as a raked garden.

The repair is to put the winding in on purpose, as dislocation dipoles: a matched ±1 pair per cell, so the far field is untouched and the near field contains exactly one crest that begins and one that ends.

φ(x) = k·x + Σj sj wj [ arg(x − aj) − arg(x − bj) ], sj = ±1 ∮C ∇φ · dl = 2π Σ sj over the cores inside C Measured over 160 cores: winding ±1 at every one, error 0.0000. The ripple index λ/H and the stoss/lee asymmetry come from Bagnold's aeolian work — the asymmetry is an odd harmonic, because a symmetric profile has no upwind side.

SamplingA beat that looks like a decision

A detail locked to the world and sampled on a mesh locked to the camera does not simply disappear when it goes under Nyquist. It beats, and the beat is smooth, large and stationary in world space — which is precisely the signature of something deliberate.

a world-locked λ sampled at quad size q → Λ = 1 / | 1/λ − 1/q | a central-difference normal has transfer sin(kε)/(kε) kε > π ⇒ sin(kε)/(kε) < 0 ⇒ the crest is shaded as a trough The second line is the nastier one: past the first zero the finite difference does not merely blur the surface, it inverts it. Every octave therefore fades to its mean — never to zero, which would change the average, and never abruptly.

BudgetWhat you draw, you must stop assuming

The same mistake turned up three times in three unrelated places, and it is worth stating as a rule. A statistical model — a roughness, a slope variance, a sheen — is a stand-in for structure too small to draw. The moment you draw that structure, the stand-in has to go, or the surface carries it twice.

total slope variance of a wind-driven sea (Cox & Munk, 1954) σ²total(U) = 0.003 + 0.00512 U U in m s⁻¹ σ²residual = σ²total − Σi (aiki)²/2 over every capillary you actually drew Cox–Munk against explicit capillaries; Oren–Nayar's σ against explicit sand grains; the foam sheen against explicit Plateau borders. Three different systems, one accounting error.

Its mirror image is the invariance the other way. Add an occlusion field and you have darkened the surface by its areal mean, so the albedo has to rise to meet it — otherwise the beach dims as you walk toward it and the detail switches on.

⟨AO⟩ = 0.7760 measured over the grain lattice, contrast 10.6 : 1 albedo ← albedo / ⟨AO⟩ so that ⟨albedo · AO⟩ is unchanged The same normalisation, done as an integral over the grain palette rather than a lattice, came out 1.0003 / 1.0005 / 1.0006 in R, G and B.

Five wetnessesThe last water to leave

A depth-averaged solver has one depth per cell and a cell nineteen centimetres across, so the last millimetre of water on a beach is invisible to it. But that millimetre is most of what the eye reads on the swash face, and it does not go all at once. It goes in tiers, each with its own optics and its own time constant.

ponded h > 15 mm a free surface with its own ripples, draining into the bed film 1–15 mm a sheet: specular, still flowing trace 0.1–1 mm disconnected water in the hollows only, held by the bed's own relief sheen < 0.1 mm no free surface left — a wetted grain skin, F₀ 0.030 → 0.0203 damp pore water below the surface: dark sand, no specular at all The last two are what make a retreating sheet look like water leaving rather than a texture fading out. The trace tier is gated on the hollows of the bed relief, which is why it breaks into a braid instead of a shrinking outline.

ClastsWhy a beach stone is a disc

Pick up a stone on a beach and it is flat. Pick up twenty and they are all flat. That is not a coincidence and it is not a choice a renderer gets to make — it is the shape that particular flow produces, and getting it wrong is visible from thirty metres.

A pebble in a swash zone is worked by a flow that reverses twice a wave. Under that regime it is rolled about its short axis far more often than about its long one, so its two broad faces spend disproportionately more time being abraded than its edges do, and the shape converges. In Zingg's classification of clast form — the two ratios b/a and c/b of the three principal axes — beach clasts pile into the oblate quadrant: discs and blades, hardly ever a sphere and hardly ever a rod.

a ≥ b ≥ c the three principal axes b/a ≈ 0.70 .. 0.95 broad in plan c/b ≈ 0.25 .. 0.50 and flat outline: a slightly lumpy ellipse — two low harmonics and no more, because abrasion removes the high frequencies FIRST The last line matters as much as the ratios. A worn stone is smooth: fractal noise on the surface reads as freshly broken rock, not as something a sea has been turning over for a century.

ImbricationWhy they all lie the same way

A disc in a reversing flow does not settle flat. It comes to rest with its broad face tilted, dipping into the last strong flow that touched it — and on a beach face the last strong flow is always the backwash, running seaward. So they dip seaward, and crucially they all dip the same way. This is imbrication, and it is one of the oldest indicators in sedimentology precisely because it is so reliable.

dip ≈ 10° seaward, shared by the whole population scatter about it: a few degrees, no more roll turns about the LONG axis only ⇒ a flat stone cannot stand on edge The second line is the one that has to be enforced in code. If the rotation that tumbles a clast while it moves is allowed to tip its short axis out of the vertical, then when it stops it stops propped up — balanced on an edge at an angle nothing on a beach holds. A field of stones at random attitudes reads as scattered props; a field with a shared dip reads as a beach.

RestingHow to sit an arbitrary body on the ground

Once an object can come to rest in any attitude, placing it by a fixed height is wrong in both directions: one that rolled onto its narrow side floats, and one that rolled onto its broad side sinks. What is needed is the lowest point of the rotated body, which is its support function in the downward direction — and for a box that is exact and costs three multiplies.

lowest point of a body with half-extents h under rotation R: ymin = − ( |R10| hx + |R11| hy + |R12| hz ) place the centre at zsand + |ymin| (1 − B) B = burial fraction And zsand has to be the surface the sand mesh actually draws — base plus sub-grid detail plus the solver's slow bed evolution. Grounding on the smooth bed alone omits a centimetre or two of relief against an object whose whole proud height is a few millimetres, and half the field then hovers above its own contact shadow. An object with daylight under it and a shadow beneath is the definition of placed.

ScourThe moat and the tail

Anything standing proud of a sheet flow accelerates the flow around itself and digs. A clast therefore sits in a shallow moat, with a tail of scour trailing away downstream — on a beach face, seaward again. It is the same hollow you find around a stone in a stream, and the eye recognises it without ever having been taught the name.

moat: a ring depression at 1.0 .. 2.4 clast radii, about −6 mm tail: a trough extending ~8 clast radii downstream, about −4 mm and the clast itself sits 25 to 55 % of its own height BELOW the sand line That last number is doing more work than the moat is. The one part of a silhouette that can look like a cut-out is its rim, and burying the rim removes the question.

Wet stoneThe strongest cue on the beach

Nothing else in the scene changes as much between dry and wet as a stone does, and the reason is not that water is dark. A dry stone's surface is a field of micrometre pits left by the same sand that rounded it; light entering a pit has many chances to scatter back out without ever penetrating the rock. Fill the pits with water and the refractive index step at the surface drops from 1.60 : 1 to about 1.20 : 1, so far more light goes in, takes a long absorbing path through the rock, and comes back out attenuated.

albedo → × 0.55 roughness 0.72 → 0.13 F₀ 0.054 dry (calcite, n = 1.60) → 0.0125 through a film plus a smooth 2 % coat on top of it, which is the water's own surface Halving the albedo and collapsing the roughness together is why a retreating sheet leaves a dark, glinting trail of stones behind it, and why that trail fades from the top of the swash downward as the film drains and evaporates.

SortingWhy they come in lines

Scatter anything uniformly over a beach and the eye reads it as scattered. A beach does not scatter — it sorts, by weight, and it does it along a line, because the process doing the sorting is a sheet of water running up and back along one direction.

threshold of motion (Shields, for a very large grain): ucrit ≈ 0.78 + 34 D D = clast half-extent, metres placement: shore-parallel lags, 11 : 1 elongated alongshore ~25 % left as strays, because the backwash drags them out The threshold for a stone sits far above the one for a shell. Rock is several times the mass of a thin shell wall over a hollow, and a flat clast lying face down presents the flow almost no frontal area to lift against — so it is welded to the sand until a big run-up, and then it skids rather than rolling freely.

Two meshesMaking the far agree with the near

A scene like this is solved out to about eighty metres and approximated beyond that, and the two surfaces overlap. Where they overlap they have to agree to within the quantisation of the frame buffer, or the handover is a visible line — and the culprit is almost never the blend. It is that the far surface was given a plausible-looking stand-in for something the solver already knows exactly.

the caustic must be the SAME operator on both meshes: C = clamp( 1 − 26 · min(d, 1.2) · ∇²η ) ∇²η from the same 5-point stencil at the same spacing what it replaced: C = clamp( 1 + 8 d (0.3 − steepness) ) — a function of the wave GROUP, not of the surface: pinned at its ceiling below steepness 0.217 and slamming to its floor whenever an envelope passed. A five-fold swing on the sunlit bed. Measured disagreement across the handover: 61 counts of green before, 2 after. The other half of the gap was a literal sand colour on the far mesh that was 2.04× brighter than the albedo function the near mesh calls, and answered to none of the scene's parameters.

MeasurementTwo instruments that lie

Two of the day's longest detours were caused by trusting a measurement, and both are worth writing down because both are silent.

gl.finish() is NOT a barrier in WebGL → wall-clock timing around a draw call reports ~0.02 ms for real work → use EXT_disjoint_timer_query_webgl2 MRT blending is PER DRAW BUFFER, against THAT buffer's own alpha → a colour faded out by α while attachment 1 writes alpha = 1.0 hands over its payload fully while contributing no colour The second one produced a pale offshore haze that was invisible at a grazing angle and obvious from above — colour discarded across the whole overlap while the packed subsurface glow was replaced outright, then tinted and added back in post. Frame time once it could be measured honestly: 15.325 ms → 14.138 ms.

VIIThe whole system

One vector defines the physical personality of a beach.

Every appearance in the scene descends from a single parameter set. Change one entry and the beach changes character without anything being redrawn:

P = [ β, H_s, T_s, H_w, T_w, θ, K_p, d₅₀, ρ_s, K_R, A_IG, T_IG, A_t, L_t, A_y, C_f, C_b, λ_f, λ_d, U_w, A_tide, reef_depth, reef_distance, sediment_albedo, water_clarity, sun_elevation ] Calm lagoon, open tropical beach, reef-protected, stormy, reflective, dissipative — these are regions of this space, not separate art assets.

The example beachThe one you are looking at

ParameterValueWhat it sets
β0.075 facethe swash-built slope of the beach face
H_swell / T1.35 m / 11.5 sdeep-water swell, before the reef takes any of it
H_wind / T0.74 m / 4.5 slocal chop riding on the swell
d₅₀0.42 mmmedian grain — sets permeability and colour
K_p1.4 × 10⁻⁴ m/show fast the sand drinks the swash
γ_b0.88 ± 0.055the depth ceiling — what actually sets height at the beach
λ_f0.82 s⁻¹foam decay — how far rafts travel before bursting
A_IG / T_IG0.18 m / 55 ssurf beat — the ceiling rises and falls with it
reef258 m offshorefilters the swell to ~0.55 m at the beach
clarity0.72divides K_d — how far you see into the water
C_j / C_w2.00 / 1.05the throw: mean Λ 1.2–1.8 inshore, entry 39°
a_g0.62set envelope — how unequal successive waves are
exposure5.5the camera, not the physics
α_s65.5°sun elevation — near-noon tropical

ScalesWhy small structure matters

A convincing scene carries several scales at once, in space and in time. Missing the small ones is what makes an otherwise correct simulation read as computer graphics.

ScaleSpaceTime
Largewave envelope, crest, trough, boretide, morphology — minutes to hours
Mediumfoam patches, jets, vortices, splash crownswaves, swash, backwash — seconds
Smallbubbles, droplets, ripples, foam filamentsfoam breakup, spray — sub-second
Microair–water interfaces, capillary wavesmilliseconds

RulesFifteen ways to get it wrong

  1. Never make two waves identical.
  2. Never make shoreline motion perfectly periodic.
  3. Never distribute foam uniformly.
  4. Never make the beach perfectly flat.
  5. Never let wet sand dry instantly.
  6. Never make every bubble the same size.
  7. Never make backwash the mirror of uprush.
  8. Never break along a perfectly straight line.
  9. Never hold water colour constant with depth.
  10. Never make foam disappear instantly.
  11. Never give every wave the same runup.
  12. Never use independent per-pixel noise for the shoreline.
  13. Always use correlated randomness.
  14. Always run multiple time scales at once.
  15. Always let the beach remember previous waves.

If you build oneThe minimum that still convinces

With limited resources, the components that carry most of the realism are: a stochastic wave spectrum; shallow-water propagation; a breaking threshold; a dynamic shoreline; uprush and backwash; infiltration; wet-sand memory; foam generation and decay; depth-dependent water colour; Fresnel reflection; dynamic surface normals; sun glints; bottom reflection; shadows that stay blue rather than black; and an HDR exposure at the end. Fifteen systems, and the rest is refinement.

Do not tell the environment what a tropical beach should look like. Tell it what rules govern water, sand, waves, gravity, air, sediment, foam, bubbles, terrain, sunlight and time — and then let the shoreline emerge. The final generative principle

The most convincing virtual beach is not a photograph recreated by mathematics. It is a mathematical ocean and a mathematical beach allowed to produce the photograph as an emergent result.

REALISM ≈ PHYSICS + MULTISCALE VARIATION + MEMORY + STOCHASTICITY