The effect classes do not own the fluid. The solver runs once, then an effect decides how to display the latest textures.
In WebGL, effects are EffectComposer passes:
const overlay = new OilOverlayPass(fluid)
composer.addPass(new RenderPass(scene, camera))
composer.addPass(overlay)
composer.addPass(new OutputPass())
In WebGPU/TSL, effects are node factories:
pipeline.outputNode = oilOverlay(
sceneNode,
fluid.densityNode,
fluid.dyeNode,
fluid.velocityNode,
{ intensity, time, vibrance },
)
Outputs used by effects
| Output | Channels | Use it for |
|---|---|---|
fluid.velocityTexture | .xy | Post-advection velocity. Use it for particle systems and downstream motion. |
fluid.velocityProjectedTexture | .xy | Projection-clean velocity before self-advection. Use it for cleaner velocity visualizations. |
fluid.densityTexture | .rg + .b | Display-space smeared flow in RG and density mask in B. Distortion and most overlays use this. |
fluid.dyeTexture | .rgb | Per-stroke color. Requires fluid.enableDye = true and dyeColor splats. |
Overlay and distortion effects usually read densityTexture. That name is a
little misleading: RG stores a display-space flow vector and B stores density.
Dye-aware overlays also read dyeTexture.
Overlay families
Overlay effects add or mix color over the scene. Use them when the fluid should be visible as a layer rather than bending the pixels underneath.
Reads: dyeTexture, densityTexture
Tune: intensity, cursorColor, vibrance, dyeDissipation
Good for product sites and hero backgrounds where the user expects a clean cursor trail.
Reads: densityTexture
Tune: intensity, cursorColor, low curl, reflectWalls
Use for sharp leading edges and long tails. Keep vorticity off when the streak should stay clean.
Reads: densityTexture or velocityTexture
Tune: time, vibrance, curlStrength, velocityDissipation
Best when visible swirls matter. Raise curl and keep motion alive near 0.985 to 0.99.
Reads: dyeTexture, densityTexture
Tune: enableDye, coloredStrokes, dyeDissipation, intensity
Use when stroke color matters. The dye field is separate, so color can live longer than the mask.
Reads: dyeTexture, densityTexture
Tune: low pressure, high dyeDissipation, open walls
Let the field relax outward. This wants soft diffusion rather than tight vortex motion.
Reads: dyeTexture, densityTexture
Tune: colorize, bfecc false, intensity below bright overlays
Use when the stroke should feel like tinted glass or a liquid cursor lens.
Distortion families
Distortion effects keep the scene color but shift where the scene is sampled. Use them for glass, heat haze, refraction, melting UI, and water surfaces.
Reads: densityTexture.rg
Tune: intensity, splatForce, velocityDissipation
The cheapest option. It simply offsets scene UVs by the fluid flow.
Reads: densityTexture.rg and densityTexture.b
Tune: intensity, splatForce, enableVorticity
Use when motion should split red and blue channels along the flow direction.
Reads: blurred densityTexture
Tune: low visual intensity, high densityDissipation, vorticity
Good for oil-slick refraction. The effect is strong, so start with low intensity.
Reads: densityTexture.b
Tune: larger splats, slow density decay, gentle curl
Uses the density gradient as a fake water normal. Flat regions stay sharp.
Reads: densityTexture.rg and densityTexture.b
Tune: time, intensity, active density mask
Adds a procedural caustic web over the water refraction. Pass time every frame.
Style recipes
These are starting points, not sacred values. Copy a recipe, then tune by eye.
Use TrailOverlayPass, small splatRadius, medium splatForce, enableVorticity = false, and reflectWalls = true.
Use SmokeOverlayPass, larger splats, slow density decay, vorticity on, and reflectWalls = false so plumes leave the screen.
Use ArtInkOverlayPass or RainbowInkOverlayPass, set fluid.enableDye = true, and attach pointer splats with colored strokes.
Use ColorWaterOverlayPass, low pressure iterations, high dyeDissipation, open walls, and gentle curl.
Use SimpleDistortionPass, low to medium intensity, and tune splatForce until motion is visible but not tearing the scene.
Use WaterDistortionPass or WaterCausticsDistortionPass, larger soft splats, slow density decay, and time for caustics.
Common controls
| Parameter | What it controls | How to tune it |
|---|---|---|
intensity | Visual gain inside the pass or node. | Overlay intensity can often be above 1. Distortion intensity should be lower because UV offsets can tear. |
vibrance | Saturation boost away from luminance. | Use small values for production UI. Push higher for demo and reel visuals. |
cursorColor | Base color for cursor-tinted overlays. | Used by default, volume cursor, and trail styles. |
time | Animation clock for procedural palettes and caustics. | Update once per frame. Required for oil, colorful, rainbow fish, burn, and caustics. |
opacity | TSL overlay mix amount. | Useful when stacking overlay and distortion in the same RenderPipeline output. |
velocityScale | TSL-only gain for effects that read velocityTexture. | Lower it for velocity visualization and rainbow fish when the field is too hot. |
Runtime style switching
Create all WebGL passes once, add them to the composer, and toggle enabled.
Disabled passes are skipped.
const overlays = {
trail: new TrailOverlayPass(fluid),
oil: new OilOverlayPass(fluid),
smoke: new SmokeOverlayPass(fluid),
}
for (const pass of Object.values(overlays)) {
pass.enabled = false
composer.addPass(pass)
}
function syncOverlay(style: keyof typeof overlays) {
const active = overlays[style]
for (const pass of Object.values(overlays)) pass.enabled = pass === active
active.intensity = params.intensity
if ('time' in active) active.time = elapsed
}
For TSL, rebuild the output node when the style changes.
function buildOutput(style: FluidOverlayStyle) {
return fluidOverlay(style, sceneNode, fluid.densityNode, fluid.dyeNode, fluid.velocityNode, {
intensity,
time,
cursorColor,
vibrance,
})
}
setPipelineOutput(pipeline, buildOutput(params.overlayStyle))