Visual effects

Effects Guide

A practical catalog of overlays and distortions: what each family reads, when to use it, and which parameters make it look good.
Overlay and distortion catalog

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.

Cursor color Default / Volume Cursor

Reads: dyeTexture, densityTexture

Tune: intensity, cursorColor, vibrance, dyeDissipation

Good for product sites and hero backgrounds where the user expects a clean cursor trail.

Directional wake 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.

Velocity stylization Oil / Colorful / Rainbow Fish

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.

Dye-aware strokes Smoke / Art Ink / Rainbow Ink

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.

Soft watercolor Color Water

Reads: dyeTexture, densityTexture

Tune: low pressure, high dyeDissipation, open walls

Let the field relax outward. This wants soft diffusion rather than tight vortex motion.

Tinted lens overlay Liquid Lens

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.

Baseline UV warp SimpleDistortionPass / simpleDistortion

Reads: densityTexture.rg

Tune: intensity, splatForce, velocityDissipation

The cheapest option. It simply offsets scene UVs by the fluid flow.

Chromatic split RGBShiftDistortionPass / rgbShiftDistortion

Reads: densityTexture.rg and densityTexture.b

Tune: intensity, splatForce, enableVorticity

Use when motion should split red and blue channels along the flow direction.

Iridescent smear ChromaticDistortionPass / chromaticDistortion

Reads: blurred densityTexture

Tune: low visual intensity, high densityDissipation, vorticity

Good for oil-slick refraction. The effect is strong, so start with low intensity.

Density-as-height WaterDistortionPass / waterDistortion

Reads: densityTexture.b

Tune: larger splats, slow density decay, gentle curl

Uses the density gradient as a fake water normal. Flat regions stay sharp.

Water plus light web WaterCausticsDistortionPass / waterCausticsDistortion

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.

Overlay Clean cursor trail

Use TrailOverlayPass, small splatRadius, medium splatForce, enableVorticity = false, and reflectWalls = true.

Overlay Soft smoke

Use SmokeOverlayPass, larger splats, slow density decay, vorticity on, and reflectWalls = false so plumes leave the screen.

Overlay Color ink

Use ArtInkOverlayPass or RainbowInkOverlayPass, set fluid.enableDye = true, and attach pointer splats with colored strokes.

Overlay Watercolor bloom

Use ColorWaterOverlayPass, low pressure iterations, high dyeDissipation, open walls, and gentle curl.

Distortion Heat haze

Use SimpleDistortionPass, low to medium intensity, and tune splatForce until motion is visible but not tearing the scene.

Distortion Pool surface

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))
The full overlay demo is the visual catalog for overlay styles. Open example
The full distortion demo shows all distortion styles with live tuning. Open example