Star 历史趋势
数据来源: GitHub API · 生成自 Stargazers.cn
README.md

three-fluid-fx

A drop-in 2D Stable-Fluids solver for three.js, tuned for real-time visual effects. Ships WebGL/GLSL and WebGPU/TSL pipelines.

Built to remove the pain of wiring a fluid sim into a three.js project. If you've ever needed one of these and ended up reading a SIGGRAPH paper to get there — this library is for you.

🌊 Live Demo & Documentation  ·  📖 Interactive Tutorials

Fluid cursor overlay
Fluid cursor overlay
Coloured ink / trail that follows the pointer.
live demo · source
Fluid screen distortion
Fluid screen distortion (UV refraction)
Smear / heat-haze / liquid-lens by sampling your scene with tFluid.rg.
live demo · source
Particle displacement
Particle displacement (vertex shader)
Push procedural particle positions with the velocity field — no GPGPU required.
live demo · source
GPGPU particle displacement
GPGPU particle displacement
Full ping-pong particle system advected by the velocity texture (2D and 3D).
live demo (3D) · 2D source · 3D source
Fluid text effect
Fluid text (DOM typography → CanvasTexture)
Mirror ordinary HTML text into a Three.js plane, then run it through fluid distortion and Art Ink overlay.
live demo · tutorial · minimal source · full source
Fluid reveal mask
Fluid reveal mask (WebGL + WebGPU)
Use the fluid density as an organic mask to reveal a hidden layer under the pointer; the velocity field ripples the edge.
live demo · tutorial · minimal source · full source

You bring a three.js scene; the library hands you solver outputs and a five-line API. In the WebGL/GLSL pipeline those outputs are textures (velocityTexture, densityTexture). In the WebGPU/TSL pipeline they are both raw textures and TextureNodes. Everything else — how to composite them, what to distort, which particles to push — stays in your shaders, where it belongs.

ℹ️ Not a new algorithm. This is a three.js-focused packaging of Jos Stam's Stable Fluids (SIGGRAPH 1999), with vorticity confinement (Fedkiw 2001) and optional BFECC advection. See Acknowledgements & scope for prior art and credits.

Don't use this if you need

  • ❌ CFD-grade physical accuracy (this is not Navier-Stokes engineering)
  • ❌ free-surface water with splashes (use FLIP / SPH)
  • ❌ 3D volumetric fluid (smoke volumes, fire as a volume) — this is 2D only
  • ❌ rigid-body collision coupling — the solver doesn't know about your scene

Why it's "easy"

  • Plain-property API. No configure() calls; write fluid.curlStrength = 0.7 any time.
  • Profile presets. One option (profile: 'balanced') sets resolution + iterations.
  • Drop-in helper for pointer splats; everything else is opt-in and tree-shakable.
  • No DOM dependency in the solver itself — runs in OffscreenCanvas / Worker.
  • Plain-JS friendly.d.ts is opt-in metadata, never required at runtime.

Tech facts

  • Tree-shakable ESM + CJS bundles (~13 KB gzipped for the full GLSL pipeline with all 20 passes, ~11 KB gzipped for the TSL pipeline). three stays a peer dependency.
  • WebGL2 / HalfFloat FBOs in the default GLSL pipeline; WebGPU/WGSL compute in the TSL pipeline.
  • GLSL pipeline: 20 Pass subclasses, compatible with three.js EffectComposer and the standard post-processing pipeline (see below).
  • TSL pipeline: RenderPipeline-ready node functions and WGSL-backed fluid simulation via three-fluid-fx/tsl.
  • Drop-in across React-Three-Fiber, plain three.js, or <script>-based pages.

WebGL post-processing passes

The default three-fluid-fx entry is compatible with three.js EffectComposer and the standard post-processing pipeline. It ships 20 Pass subclasses that chain alongside RenderPass, OutputPass, BloomPass, etc. without configuration:

  • 5 distortion passesSimpleDistortionPass, RGBShiftDistortionPass, ChromaticDistortionPass, WaterDistortionPass, WaterCausticsDistortionPass
  • 15 overlay passesDefaultOverlayPass, VolumeCursorOverlayPass, TrailOverlayPass, OilOverlayPass, VelocityOverlayPass, ColorfulOverlayPass, RainbowFishOverlayPass, GlazeOverlayPass, BurnOverlayPass, SmokeOverlayPass, ArtInkOverlayPass, RainbowInkOverlayPass, ColorWaterOverlayPass, LiquidLensOverlayPass, DensityTintOverlayPass

Each pass exposes plain properties (intensity, vibrance, cursorColor, …) and reads from tDiffuse — the convention ShaderPass.textureID uses by default — so chaining "just works":

import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js' import { RenderPass } from 'three/addons/postprocessing/RenderPass.js' import { OutputPass } from 'three/addons/postprocessing/OutputPass.js' import { ChromaticDistortionPass } from 'three-fluid-fx' const distortion = new ChromaticDistortionPass(fluid) distortion.intensity = 0.5 const composer = new EffectComposer(renderer) composer.addPass(new RenderPass(scene, camera)) composer.addPass(distortion) composer.addPass(new OutputPass()) // canonical final pass: tone mapping + sRGB // Loop: fluid.step(dt) composer.render(dt)

WebGPU / TSL nodes

The three-fluid-fx/tsl entry uses a WGSL compute solver and exposes TSL node functions for composition. These are meant for WebGPURenderer and RenderPipeline, not EffectComposer:

import { RenderPipeline, WebGPURenderer } from 'three/webgpu' import { pass } from 'three/tsl' import { attachPointerSplats, FluidSimulation, simpleDistortion } from 'three-fluid-fx/tsl' const renderer = new WebGPURenderer() await renderer.init() const fluid = new FluidSimulation(renderer) attachPointerSplats(renderer.domElement, fluid) const scenePass = pass(scene, camera) const pipeline = new RenderPipeline(renderer) pipeline.outputNode = simpleDistortion(scenePass, fluid.densityNode, 1) renderer.setAnimationLoop(() => { fluid.step(1 / 60) pipeline.render() })

Install

npm install three-fluid-fx three # or pnpm add three-fluid-fx three

Requires three >= 0.183.0. The default subpath three-fluid-fx is the WebGL/GLSL pipeline. Use three-fluid-fx/tsl for the WebGPU/TSL pipeline; it requires a WebGPU-capable browser/runtime.

Quick start

import { WebGLRenderer, Timer } from 'three' import { FluidSimulation, attachPointerSplats } from 'three-fluid-fx' const renderer = new WebGLRenderer({ antialias: true }) const fluid = new FluidSimulation(renderer, { splatRadius: 0.001, splatForce: 6, }) // live-tunable, just plain properties fluid.curlStrength = 0.7 fluid.splatForce = 8 // change at any time, picked up next frame // optional helper for mouse/touch — reads splatRadius/splatForce from fluid attachPointerSplats(renderer.domElement, fluid) const clock = new Timer() renderer.setAnimationLoop(() => { clock.update() fluid.step(clock.getDelta()) // sample fluid.velocityTexture / fluid.densityTexture in your own material })

Plain JavaScript (no bundler, no TypeScript)

<div id="stage" style="width: 100vw; height: 100vh"></div> <script type="importmap"> { "imports": { "three": "https://esm.sh/three@0.183.0", "three-fluid-fx": "https://esm.sh/three-fluid-fx@0.1.0" } } </script> <script type="module"> import { WebGLRenderer } from 'three' import { attachPointerSplats, FluidSimulation } from 'three-fluid-fx' const stage = document.getElementById('stage') const renderer = new WebGLRenderer({ antialias: true }) stage.appendChild(renderer.domElement) const fluid = new FluidSimulation(renderer, { splatRadius: 0.001, splatForce: 6, }) attachPointerSplats(renderer.domElement, fluid) function frame() { const width = stage.clientWidth const height = stage.clientHeight renderer.setSize(width, height, false) fluid.resize(width, height) fluid.step(1 / 60) requestAnimationFrame(frame) } requestAnimationFrame(frame) </script>

API

class FluidSimulation

new FluidSimulation(renderer: WebGLRenderer, options?: FluidSimulationOptions)

All tunables are plain properties — write to them at any time, the solver picks the new value on the next step(). This makes Tweakpane / dat.gui / any UI integration a one-liner: pane.addBinding(fluid, 'curlStrength', { min: 0, max: 2 }).

Quality profiles

Baseline resolution and Jacobi-iteration counts are picked at construction time. resize(width, height) reshapes the internal targets to the viewport aspect, but it does not change the selected profile's base resolution. Use a profile preset:

import { FluidSimulation, FLUID_PROFILES } from 'three-fluid-fx' new FluidSimulation(renderer, { profile: 'performance' }) // mobile / weak GPU new FluidSimulation(renderer, { profile: 'balanced' }) // default — desktop new FluidSimulation(renderer, { profile: 'quality' }) // presentation / high-end
sim FBOdye FBOpressure itersrelative cost
performance128²256²6
balanced256²512²12~6×
quality384²1024²20~25×

Individual options always override profile values:

new FluidSimulation(renderer, { profile: 'balanced', pressureIterations: 8, // overrides balanced default of 12 })
PropertyDefaultWhat it does
pressureIterations12Jacobi iterations for the balanced profile.
curlStrength0.55Vorticity confinement strength.
enableVorticityfalseToggle the curl + vorticity passes (Fedkiw 2001).
bfecctrueBFECC advection (sharper, ~5× cost in advect).
velocityDissipation0.985Per-second decay of velocity field.
densityDissipation0.91Per-second decay of density field.
dyeDissipation0.91Per-second decay of the optional dye field.
pressureDissipation0.8Decay of residual pressure between frames.
splatRadius0.00042Default radius for addSplat() (UV² units).
splatForce6Default force for attachPointerSplats.
reflectWallstrueReflect flow from the viewport edges.
enableDyefalseUpdate the optional per-stroke dye texture.

Read-only outputs:

fluid.velocityTexture // THREE.Texture, .xy is the post-advection flow field fluid.velocityProjectedTexture // THREE.Texture, projected pre-advection flow snapshot fluid.densityTexture // THREE.Texture, .rg is flow-like display data, .b is density fluid.dyeTexture // THREE.Texture, .rgb is the optional colored dye field

TSL/WebGPU exposes the same simulation state as TextureNodes, which are the inputs expected by the TSL effect factories:

fluid.velocityNode // TextureNode, .xy is the post-advection flow field fluid.densityNode // TextureNode, .rg is flow-like display data, .b is density fluid.dyeNode // TextureNode, .rgb is the optional colored dye field fluid.pressureNode // TextureNode, advanced/debug pressure field fluid.divergenceNode // TextureNode, advanced/debug divergence field fluid.curlNode // TextureNode, advanced/debug curl field

GLSL passes and TSL factories are paired by effect family:

GLSL pass classTSL factory
SimpleDistortionPasssimpleDistortion()
RGBShiftDistortionPassrgbShiftDistortion()
ChromaticDistortionPasschromaticDistortion()
WaterDistortionPasswaterDistortion()
WaterCausticsDistortionPasswaterCausticsDistortion()
DefaultOverlayPassdefaultOverlay()
VolumeCursorOverlayPassvolumeCursorOverlay()
TrailOverlayPasstrailOverlay()
OilOverlayPassoilOverlay()
VelocityOverlayPassvelocityOverlay()
ColorfulOverlayPasscolorfulOverlay()
RainbowFishOverlayPassrainbowFishOverlay()
GlazeOverlayPassglazeOverlay()
BurnOverlayPassburnOverlay()
SmokeOverlayPasssmokeOverlay()
ArtInkOverlayPassartInkOverlay()
RainbowInkOverlayPassrainbowInkOverlay()
ColorWaterOverlayPasscolorWaterOverlay()
LiquidLensOverlayPassliquidLensOverlay()
DensityTintOverlayPassdensityTintOverlay()

Methods:

fluid.resize(width, height) fluid.addSplat(x01, y01, dx, dy, { radius?, color?, dyeColor? }) fluid.step(deltaSeconds) fluid.dispose()

attachPointerSplats(element, fluid)

Attaches pointer listeners and pushes splats into the solver. Splat radius and force are read from fluid.splatRadius / fluid.splatForce on every event — set them in the constructor or write them at runtime; live-tuning works without re-attaching. Returns a teardown function.

Options:

attachPointerSplats(renderer.domElement, fluid, { coloredStrokes: true, colorUpdateSpeed: 10, colorize: (dx, dy, timeMs) => [Math.abs(dx) * 0.003, 0.08, Math.abs(dy) * 0.003], })

FullscreenPass(material)

Tiny full-screen quad pass for compositing your shader on top of the solver's outputs. Use FULLSCREEN_VERTEX as your vertex shader.

createSceneTarget(width, height)

Convenience factory for a WebGLRenderTarget configured for sRGB display sampling.

Tutorials

The site, tutorials, and live example pages now use one Astro engine. Source for the hand-authored guides lives in src/content/tutorials/; reusable tutorial UI lives in src/components/tutorials/; example route metadata lives in src/data/examples.ts. The static site build writes to dist/.

pnpm dev # Astro site + live examples at http://127.0.0.1:4321/ pnpm build # typecheck + static site build -> dist/ pnpm docs:dev # alias for pnpm dev pnpm docs:build # Astro site build -> dist/

The public tutorial surface is Astro-only. General guides live at /tutorials/, and every runnable demo has a personal walkthrough at /tutorials/<pipeline>/<level>/<slug>/. The runnable demos live at /examples/<pipeline>/<level>/<slug>/ and are generated from the same Astro manifest while importing examples/<pipeline>/<level>/<slug>/main.ts.

Core guides

These are source links. When pnpm dev is running, the same guides are served under http://127.0.0.1:4321/tutorials/.

  • Getting Started — solver lifecycle, pointer splats, resize handling, outputs, profiles, and parameters.
  • Effects Guide — overlay and distortion families, what they read, and when to use each one.
  • Particles Guide — procedural particles, GPGPU particles, camera data, and tuning without ambiguity.
  • GLSL vs TSL — choosing the WebGL/GLSL or WebGPU/TSL pipeline.

Demo walkthroughs

Per-demo walkthrough content is generated from src/data/exampleTutorials.ts, with route metadata in src/data/examples.ts. When pnpm dev is running, walkthroughs live at /tutorials/<pipeline>/<level>/<slug>/.

Repo layout

src/
├── core/                                       ← published library (only three is required)
│   ├── shared/
│   │   └── pointerSplats.ts                    ← pipeline-agnostic
│   ├── glsl/                                   ← WebGL/GLSL entry: 'three-fluid-fx'
│   │   ├── simulation/
│   │   ├── effects/                            ← EffectComposer-ready Pass subclasses
│   │   │   ├── distortion/                     ←  5 distortion passes
│   │   │   └── overlay/                        ← 15 overlay passes
│   │   └── index.ts
│   └── tsl/                                    ← WebGPU/TSL entry: 'three-fluid-fx/tsl'
│       ├── simulation/                         ← WGSL compute solver
│       ├── effects/                            ← RenderPipeline/TSL node functions
│       └── index.ts
├── content/tutorials/                          ← Astro MDX tutorial source
├── data/examples.ts                            ← examples manifest / route metadata
├── components/
│   ├── examples/                               ← catalog cards and shared example UI
│   ├── site/                                   ← header/footer shared by site pages
│   └── tutorials/                              ← tutorial UI blocks
├── layouts/                                    ← site, tutorial, and fullscreen example shells
├── pages/
│   ├── examples/                               ← Astro-generated live example routes
│   └── tutorials/                              ← Astro-generated tutorial routes
└── scripts/example-pages.ts                    ← imports the selected example main.ts

examples/
├── extras/                                     ← demo helpers (not published)
│   ├── controls/                               ← Tweakpane wrapper, param ranges
│   ├── backgrounds/{glsl,tsl}/                 ← background implementations
│   ├── particles/{glsl,tsl}/                   ← example particle systems
│   ├── text/                                   ← DOM text to CanvasTexture helpers
│   └── resolveProfile.ts                       ← URL profile resolver (?profile=balanced)
├── glsl/{minimal,full}/<slug>/main.ts          ← WebGL runtime entrypoints
└── tsl/{minimal,full}/<slug>/main.ts           ← WebGPU runtime entrypoints

examples-js/                                   ← generated from examples/
├── glsl/{minimal,full}/<slug>/main.js
└── tsl/{minimal,full}/<slug>/main.js

Develop

pnpm install pnpm dev # Astro site + live examples at http://127.0.0.1:4321/ pnpm build # typecheck + Astro static build -> dist/ pnpm build:js # regenerate examples-js/ pnpm build:lib # library build (both pipelines + .d.ts) → dist-lib/{glsl,tsl}/ pnpm build:lib:glsl # GLSL bundle only → dist-lib/glsl/ pnpm build:lib:tsl # TSL bundle only → dist-lib/tsl/ pnpm docs:dev # alias for pnpm dev pnpm docs:build # Astro site build -> dist/

Notes on design

  • No configure(...) method. Properties are public; the solver re-reads them each frame. This keeps GUI integration trivial.
  • No GUI in the library. Tweakpane is a dependency of the examples, not of the library. Tree-shakers will drop nothing extra; users who don't need a GUI never pay for one.
  • GPGPU particles are not part of the library. They live in examples/extras/particles/ as an example of how to use fluid.velocityTexture to drive your own particle system.

Acknowledgements & scope

This library does not introduce new fluid-simulation algorithms. The mathematics is Jos Stam's Stable Fluids (SIGGRAPH 1999) with Fedkiw's vorticity confinement (2001) and optional BFECC advection — all 20+ years old and widely implemented. The WebGL adaptation patterns are well-trodden ground, popularised by PavelDoGreat's WebGL-Fluid-Simulation and walked through clearly by the mofu-dev tutorial.

What this package contributes is packaging and ergonomics for three.js projects: tree-shakable npm entries with a plain-property API, profile presets, three.js-native texture outputs, and small helpers (attachPointerSplats, FullscreenPass). The solver is opinionated towards real-time VFX — not CFD accuracy.

If you want to learn the algorithm, read Stam's paper and the mofu-dev tutorial. If you want to drop a velocity/density field into your three.js scene in five lines, use this.

Algorithms (not authored by this project)

  • Jos Stam, Stable Fluids (SIGGRAPH 1999) — pressure projection method.
  • Fedkiw, Stam, Jensen, Visual Simulation of Smoke (SIGGRAPH 2001) — vorticity confinement.
  • Kim, Liu, Llamas, Rossignac, Advections with Significantly Reduced Dissipation and Diffusion (2007) — BFECC.

WebGL adaptations and tutorials this work studied

What this project authored

The public API (FluidSimulation, attachPointerSplats, profile presets), the three.js integration layer, the example tutorials and the packaging. The shaders are derivatives of the prior-art shader code listed above.

License

MIT © Artem Korenevych. See LICENSE and THIRD_PARTY_NOTICES.md.

关于 About

No description, website, or topics provided.

语言 Languages

JavaScript42.0%
TypeScript37.5%
CSS18.3%
Astro0.8%
MDX0.7%
HTML0.7%

提交活跃度 Commit Activity

代码提交热力图
过去 52 周的开发活跃度
15
Total Commits
峰值: 10次/周
Less
More

核心贡献者 Contributors