--- title: "Effects Guide" description: "A practical catalog of overlays and distortions: what each family reads, when to use it, and which parameters make it look good." section: "Visual effects" order: 2 badge: "Overlay and distortion catalog" --- import Callout from '../../components/tutorials/Callout.astro' import EffectCard from '../../components/tutorials/EffectCard.astro' import ExampleEmbed from '../../components/tutorials/ExampleEmbed.astro' import ParameterTable from '../../components/tutorials/ParameterTable.astro' import RecipeCard from '../../components/tutorials/RecipeCard.astro' import TextureChannels from '../../components/tutorials/TextureChannels.astro' 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: ```ts 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: ```ts pipeline.outputNode = oilOverlay( sceneNode, fluid.densityNode, fluid.dyeNode, fluid.velocityNode, { intensity, time, vibrance }, ) ``` ## Outputs used by effects 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.

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

Use for sharp leading edges and long tails. Keep vorticity off when the streak should stay clean.

Best when visible swirls matter. Raise curl and keep motion alive near 0.985 to 0.99.

Use when stroke color matters. The dye field is separate, so color can live longer than the mask.

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

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.

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

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

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

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

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 ## Runtime style switching Create all WebGL passes once, add them to the composer, and toggle `enabled`. Disabled passes are skipped. ```ts 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. ```ts function buildOutput(style: FluidOverlayStyle) { return fluidOverlay(style, sceneNode, fluid.densityNode, fluid.dyeNode, fluid.velocityNode, { intensity, time, cursorColor, vibrance, }) } setPipelineOutput(pipeline, buildOutput(params.overlayStyle)) ``` A useful effect entry should say what visual task it solves, which fluid output it reads, which parameters matter, and which demo preset is a good starting point. It should not explain the whole fluid solver.