Water
Make water with the water material - ripples, Fresnel reflection, refraction to the bed, depth fade, caustics and wind - and keep it animating.
Water in Voxolith is a material, not a separate system. Voxels whose slot has the water material
ripple over time, reflect the scene by Fresnel, refract down to the bed (found by a second walk
that treats water as empty), fade into the water's colour with depth and catch moving caustics.
Because it stays voxels, water streams, clips, edits and carves like everything else.
Making water
Give a role the water material hint and allocate it like any other role:
import { PaletteAllocator } from "@voxolith/engine";
const palette = new PaletteAllocator(1);
const { base } = palette.allocate([
{ id: "sand", name: "Sand", color: [0.76, 0.7, 0.5] },
{ id: "water", name: "Water", color: [0.12, 0.3, 0.38], material: { kind: "water", alpha: 0.35, att: 0.08 } },
], "pond");
const renderer = await createRenderer(gpu, { size, palette: palette.buildPalette(), materials: palette.buildMaterials() });coloris the colour the water fades into with depth.alphais its opacity, 0 clear to 1.attis how fast the bed fades with depth, per voxel.roughandspecshape the reflection.
Then fill the flooded cells with base + 1 (the water role) up to the water level. Generated
terrain does this for you: @voxolith/gen-terrain fills every flooded column with its WATER role,
which already carries { kind: "water", alpha: 0.35, att: 0.18, rough: 0.2, spec: 0.8 }.
Keeping it moving
Water animates from FrameParams.time, in seconds. Pass the clock every frame:
renderer.render({ ...frame(), ...lighting, time: now / 1000 });A constant time freezes the ripples. That has a consequence for render-on-demand apps: with water
on screen the picture changes every frame, so the app has to render continuously. The world
example does, and its ?still option passes time: 0 and renders on demand instead, which is also
what you want for screenshots and tests.
waterWind in FrameParams drifts the ripples and raises them with its length.
atmosphereFrame sets it from the atmosphere's wind, so a storm roughens the river on its own.
Costs
A water pixel does more work than a plain one: a reflection, a second walk down to the bed and the caustics. A lake filling the screen therefore costs more than the same area of grass. The quality presets and the adaptive render scale apply to it like everything else.
In a world at more voxels per metre, pass effectScale (or atmosphereFrame(..., { voxelsPerMetre }))
so wavelengths and the bed fade keep their size in metres.
Pitfalls
- Pass
materials(frombuildMaterials(), orupdateMaterialslater). Without a material block the renderer uses its flat palette path and water is an opaque colour. - Water under instances works (an instanced tree reflects and shades as any voxel does), but instances only fill cells the world leaves empty, so a model standing in a pond needs the water cells under it cleared.
- A continuously rendered still camera can make
makePerfresample the image as it probes; passretryAfterMs: Infinityand callperf.reprobe()when the view moves.
See also
- Palettes and roles for material hints
- Atmosphere and weather
- Generators: terrain
- API:
MaterialHint,FrameParams,ROLE
Atmosphere and weather
Time of day, fog, clouds, rain, snow and wet or snowy ground - what the renderer draws, what the engine translates, and what your game decides.
Effects
Explosions and muzzle flashes as voxel effects, flying debris through stampers, permanent damage as brick edits, and flashes as short-lived lights.