Skip to content

Water Raymarch — Seascape Playground

App: apps/water_raymarch/

This example evolved. It began as a self-contained scene ray-marched inside a single full-screen post (the owner’s Slang Playground port of the gameidea.org stylized-water tutorial). It is now a full interactive seascape playground: that same look is rendered on a real mesh surface, with a free-fly camera, runtime variant + weather toggles, buoyant physics floaters, and the underwater pass — the equivalent of Water Playground, built on the seascape water.

The whole point is fidelity to the reference look on real engine geometry. The mesh surface shader shaders/water_seascape.slang reproduces the tutorial’s Seascape algorithm faithfully — the Alekseev |sin|/|cos| octave wave heightfield (3 octaves displacing the vertex, 5 for the fragment normal), colored Beer-Lambert absorption from a bright shallow tint to a deep teal, world-projected caustics, layered foam (fbm-Voronoi + wave-crest), and a procedural-sky reflection with fresnel and a sun glint — and the underwater view reproduces the tutorial’s underwaterFragmentMain as a full-screen post.

Terminal window
% ./plume3d water_raymarch

On launch it runs a short scripted tour (spawns floaters, shows both variants and a storm, dives under) so a headless run self-demonstrates; press any control key and it hands straight to free-fly.

Everything here rides already-shipped engine capabilities — the mesh set-5 custom material, the offscreen post + scene-depth path, opaque capture, Water buoyancy, and the Sky/Weather driver — plus one small additive Water method, setSeascape, so buoyancy tracks the seascape octaves.

  • Seascape mesh water. A subdivided MeshGen.plane is drawn with water_seascape.slang. The vertex displaces Y by the 3-octave seaOctave sum (centred on the plane so the waterline sits where the still plane is); the fragment does the depth colour, caustics, foam, reflection and sun glint. A second shader, water_toon.slang, gives the flat banded Toon look for the 2 toggle.
  • Free-fly, above and below. An explicit camera (yaw/pitch + WASDQE) flies anywhere, including under the surface — where the underwater post + the Snell-window underside take over.
  • Underwater that matches the surface. shaders/underwater.slang is a faithful port of the tutorial’s underwater pass. It decides submersion from a stable camera-vs-still-level mode gate (with a smooth band, so breaching the surface fades instead of popping) and then tints by the same octave wave field the surface uses, so the crossing is wave-accurate and the seabed fogs to the seascape palette rather than showing raw geometry.
  • Buoyant floaters. SPACE/B spawn a crate (4 corner probes → it rocks) or a barrel (single probe → it bobs); Physics.applyBuoyancy runs each against a Water whose setSeascape(true) mirrors the same octave field on the CPU, so they float on the waves you see. Crossing the surface kicks a splash.
  • Splashes, ripples & precipitation — via the particle system. The water FX now ride the engine’s built-in particle + instancing systems through Scene.draw(), not hand-rolled immediate-mode draws. A floater crossing the surface bursts a surface ripple ring (a SpriteGround particle — a soft foam ring laid flat on the water that expands and fades) plus a camera-facing splash flash (a SpriteBillboard particle), both pinned kinematic decals reusing the sprite pipeline (see Particle FX Demo). Rain and snow are real GPU-instanced 3D geometry — two InstancedMesh streak volumes (rain / snow chosen per-instance) that the Weather driver toggles per state — drawn by the same Scene.draw(), replacing the earlier screen-space precipitation overlay.
  • Weather + time of day, from the shared packs. The atmosphere is driven by the actual TimeOfDay and Weather content-pack scripts — not a bespoke copy. TimeOfDay owns the day cycle (its own sun, and the sky/ambient/fog when it’s clear); Weather takes over the overcast sky/fog/wind/precipitation when it isn’t. TAB cycles the Weather pack’s four states Clear → Rain → Snow → Storm (precipitation is the GPU-instanced rain/snow above, bundled per state; storm adds lightning), and holding T advances time of day. Overcast states paint the animated cloud sky (shaders/clouds); clear stays the TimeOfDay gradient. The water reads the packs for its own hooks — the wind gust for its swell, the current sky horizon for its reflection tint, and the time-of-day sun for the underwater god-rays.
  • W/A/S/D — move · Q/E — down/up · Arrow keys — look
  • SPACE — spawn a crate · B — spawn a barrel · R — clear floaters
  • 1 — Seascape variant · 2 — Toon variant · TAB — cycle weather (clear/rain/snow/storm) · hold T — time of day
  • Any control key — end the intro tour and take over · ESC — quit
  • A clean-room reimplementation. The Seascape algorithm and the underwater pass are reproduced as our own Slang from the public tutorial technique — no third-party source is vendored.
  • Buoyancy uses a centred octave mirror. The seaOctave sum is non-negative, so both the shader and the Water height query subtract a 0.9·gain bias to centre the waves on the plane; that is what keeps the floaters, the waterline and the registered still level in agreement.
  • The underwater pass is camera-gated, by design. The full-screen underwater tint engages from the eye’s depth below the still level (not the wavy height), so a passing crest can’t flicker it and it never overpaints the surface seen from above; the per-pixel wave field still shapes the crossing.
  • Surface ripple rings read subtly at this grazing camera. The SpriteGround ripple lies flat on the water, so from the near-water diver camera it foreshortens to a thin white foam accent rather than a crisp ring; viewed from above (as in Particle FX Demo) the same rings read as clean concentric circles. The camera-facing splash flash is unaffected by angle.
  • The precipitation volume follows the camera. The rain/snow instances share one XZ offset table that is re-snapped to a grid cell around the camera each frame (a cheap per-instance setInstancePosition, one SSBO upload), so the precipitation surrounds the diver wherever they fly — it never clumps at the origin. While it precipitates, the rain also stipples the surface: a foam ripple ring bursts at a random point near the camera every fraction of a second (reusing the splash ripple emitter), so the water reads as actively rained-on.