This version keeps the integration small so the moving parts are visible. It uses the WebGL / GLSL path from `three-fluid-fx`, where the effect is built with EffectComposer passes, ShaderMaterial uniforms, and WebGL render targets.
Live demo
What this demo teaches
Hello World is not an effect stack. It is the baseline proof that pointer input is writing energy into a GPU fluid field and that the render loop is stepping that field correctly. In this variant, the relevant fluid output is Texture objects: velocityTexture, densityTexture, and dyeTexture when dye is enabled.
- Understand the smallest useful FluidSimulation lifecycle.
- Know why the solver output is invisible until you render or sample it.
- Keep resize, pixel ratio, and dt handling explicit.
Implementation path
- Create the renderer and solver together
The renderer owns the GPU context. The simulation allocates its render targets against that context, so construction belongs near renderer setup.
- Attach pointer splats
Pointer movement writes velocity and density impulses. The helper is intentionally thin, so splatRadius and splatForce remain normal runtime parameters.
- Render the density output
The demo draws the fluid field itself. Later examples replace this final draw with overlays, distortion passes, or particles.
- Minimal scope
It avoids GUI state and preset switching. Read it first when you need the smallest reliable version of the technique.
import { attachPointerSplats, FluidSimulation } from 'three-fluid-fx'
const fluid = new FluidSimulation(renderer, {
profile: 'balanced',
splatRadius: 0.001,
splatForce: 6,
})
attachPointerSplats(renderer.domElement, fluid)
renderer.setAnimationLoop(() => {
fluid.step(clock.getDelta())
renderFluidDensity(fluid)
}) Parameters to tune
| Parameter | What it controls | How to tune it |
|---|---|---|
profile | Chooses the default render-target sizes and pressure baseline. | Use performance for embeds, balanced for normal pages, and quality for hero captures. |
splatRadius | Brush size in normalized UV units. | Start small for crisp trails. Raise it when the pointer should feel like smoke or water. |
splatForce | Velocity gain from pointer movement. | Raise it until the field clearly reacts. Lower it if fast mouse movement tears the shape. |
densityDissipation | How long the visible mask remains alive. | High values make long trails. Lower values make quick puffs. |
Source landmarks
Start from examples/glsl/minimal/helloworld/main.ts. These are the parts worth reading first:
- Renderer setup and animation loop.
- FluidSimulation construction options.
- resize() and fluid.resize(width, height).
- The draw path that turns density into pixels.
Production notes
- Keep the simulation resize tied to the visible canvas size, not just window.innerWidth.
- Clamp devicePixelRatio for small demos and documentation embeds.
- Use this demo as a smoke test before debugging any higher-level effect.