effects
The builtin effects library: procedural visual effects, and the shared WGSL they are built out of.
The maths modules
Procedural effects are computed rather than sampled — a plume is fBm over a
gradient noise, a shockwave is a ramp over a radial UV, an energy shield is a
signed distance field. Three .shaderModule assets hold that maths, once:
| Module | Include | What it puts in scope |
|---|---|---|
fx_noise | @builtin::systems.effects.fx_noise | integer hashes, value and gradient noise in 2D and 3D, fBm, curl noise, worley/voronoi |
fx_sdf | @builtin::systems.effects.fx_sdf | sphere, box, torus, plane, capsule and cylinder fields, and the boolean, smooth-boolean and domain operators over them |
fx_shaping | @builtin::systems.effects.fx_shaping | remap and easing curves, four-key gradient ramps, polar and radial UV transforms, flow-map distortion |
#include "@builtin::systems.effects.fx_noise"
#include "@builtin::systems.effects.fx_shaping"
Every function is pure — the modules declare no bindings, no textures and no
samplers. That is what lets one definition serve both halves of an effect: a
Surface-domain .shader and a .computeShader see different generated
frameworks and can share a function only when it touches neither. It also means
including them costs a shader nothing from its material budget of 9 textures and
1 storage buffer.
The three are independent of one another, so a shader includes any combination of them in any order.
Each module's own README lists its functions and states the properties they guarantee — where a noise is zero, what a ramp returns at a keypoint, which distance an SDF measures.
The runtime
runtime.module is what one call plays an effect over: a backend registry, a
pool every play leases from, a frame driver that advances every live play, and
the handle a caller stops, moves or re-tunes. backends.module ships the five
kinds an effect is made of — an emitter, generated geometry, a material on a
mesh the caller already has, a decal projector and a render feature — and
params.module is the one declaration of what a parameter of each declared type
accepts. Each has its own README.
The pool is why the runtime exists: building an emitter is expensive and its
VRAM is allocated per declared slot, so one emitter per shot churns the
renderer. A play leases and gives back; the pool keeps what it has leased until
effects.drain().
The effects
Each effect is an .effect asset: a declaration of its family, its parameters
and its measured cost, plus the definition that builds it. Gameplay code plays
one by identity:
effects.play("@builtin::systems.effects.combat.explosion", {
position = { 0, 2, 0 }, params = { scale = 3.5 },
})
asset.ref(identity, "effect"):play { … } runs the same effect unpooled, which
is the reference path a preview or a single measurement takes.
The component
Effect.component binds an effect asset to an entity: it starts with the
component, follows the entity as it moves, ends when the entity leaves the
world, and comes back with its parameters out of a saved world. It plays through
this same runtime — a binding, not a second implementation — and it owns its
play's clock, which is what makes an effect an author attaches in the editor
visible there. Its README carries the fields, the methods and what a shared
field does when the bound effect declares no parameter of that name.
drum.component.add("Effect", {
effect = "@builtin::systems.effects.combat.explosion",
scale = 2.0,
})
| Family | Effect | Reads as |
|---|---|---|
combat | @builtin::systems.effects.combat.explosion | a fireball, smoke column, thrown debris and a flash of light, sized in metres |
Effects are grouped into a folder per family, and the folder is part of the
identity — combat.explosion, not a bare explosion — so the catalogue can
grow without two families ever contending for one name.
guides { path: "topics/visual-effects" } covers finding and playing them;
man /zero/source/libs/@builtin/assetTypes/effect.assetType covers authoring
one.
The demo
@builtin::systems.effects.demo.explosion_showcase builds its own ground,
light and camera and fires the explosion on a loop at a grenade, a car and a
fuel-tanker radius, side by side with a 1.8-metre cube beside each for scale.
Loading the scene is the whole setup.
@builtin::systems.effects.demo.effect_component_showcase is the same asset
carried rather than fired: three carts each with an Effect component on them,
two of them driving across the floor so the blast can be watched following the
thing that owns it.
Scoped to this part · feeds back into the world's score.