What it does
A scrolling billboard is a picture of smoke. A wisp is a plane that really turns in space as it rises, the angle of the turn read per vertex from a drifting column of tileable noise, so the ribbon curls, uncurls and never repeats. A sheet that turns hides parts of itself and shows a different amount of surface at every height, and the eye reads it as a volume. Wind sways the top while the base stays pinned to whatever the wisp rises from, and a remap of the noise erodes the sheet into separate curling wisps instead of a solid strip.
The awkward cases of a turning plane are handled. The whole ribbon turns to face the camera about its vertical axis, so it never shows you its edge, and where a twisted slice is edge-on to the eye the shader dissolves it rather than drawing a hard bright line.
It is cheap. The 12 by 40 grid of each ribbon is generated from the vertex index, so there is no vertex buffer, no mesh
and no simulation step, and the animation is four texture reads a vertex. It shares render-3d's camera uniform, so
a wisp takes exactly the fog and tone map the solids do, and it draws depth-tested so walls hide it, but never writes
depth, so ribbons that overlap blend instead of clipping each other.
Features
- A real twist — geometry turning in the vertex stage, from
0(a flat billboard) through10(the coffee cup) to a shredded corkscrew. - Wind with a pinned base —
windsways the top, weighted by height squared, while the base stays put. - Steam or smoke —
remaperodes a solid sheet into separate curls. - One draw for every wisp — no vertex buffer, no mesh and no particles.
- Fits the scene — the solids' fog and tone map, depth-tested, both faces drawn.
- Live fields — every field of a returned wisp changes on the next frame, and
strengthdims them all at once. - Its own noise — tileable noise baked at boot, shared as
noiseFrame, or a greyscale frame of your own per wisp.
Good for
- Chimneys in village and farm scenes.
- Cups and hot drinks — the thin curl of steam above a mug.
- Cooling vents and hot springs in industrial and outdoor scenes.
- Censers — incense rising in a still room.
- Smouldering wrecks after a battle, where the smoke stands rather than travels.
- Busy dioramas, where many columns of smoke still cost one draw.
Make it yours
A wisp is a spec: its base x, y and z, its width w and height h, color, alpha, twist, wind, speed,
remap, an extra yaw on top of the camera-facing, and a noise frame to drive it. Every field stays live on the
returned wisp, so w.wind = 4 makes it sway next frame. The manifest sets the baked noise's noiseSize and seed,
the overall strength and whether the pass is enabled.
Two different columns from the same call: a thin, pale plume of steam over a cup, half a unit wide and two and a half tall with a full twist and a little wind; and a factory chimney fourteen units tall, three wide, in a dirty grey, with less twist, strong wind and a hard remap that breaks it into separate curls.
A quick look
const steam = game.wisps.add({ x: 0, y: 1.1, z: 0, w: 0.5, h: 2.5, twist: 10, wind: 0.4, alpha: 0.55 });
// a chimney: taller, wider, dirtier, more eroded
game.wisps.add({ x: 12, y: 6, z: -3, w: 3, h: 14, color: '#8d8a86', twist: 6, wind: 3, remap: [0.55, 1] });
steam.wind = 1.2; // every field is live
Works with
render-3d— required; it shares its camera, fog and tone map.world-3d— the 3D world the wisps stand in.core-3d— the usual 3D setup to compose this beside.vfx3d-smokeandparticles-3d— for smoke that travels: puffs that expand and drift, billows and dust.
Good to know
- The base is pinned to a fixed point; to follow a moving object, set
x,yandzeach frame. - Wisps in front of one another blend rather than sort. At the opacities smoke wants this is invisible; at
alpha: 1with hardremapedges it can show. - A wisp is a standing column, not smoke with a life; for a puff that drifts away, use
vfx3d-smoke.






