Skip to content

About

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Fluid Simulation

An interactive, browser-based fluid simulation implementing two approaches to fluid dynamics, rendered in real time with WebGL via Three.js.

Simulations

Stable Fluids (Eulerian / grid-based)

2D smoke simulation based on Jos Stam's Stable Fluids (SIGGRAPH 1999). The velocity and density fields are stored as floating-point render targets and each simulation step runs as a WebGL fragment shader pass on the GPU.

Pipeline per frame:

  1. Add source — inject smoke density at the bottom-centre every frame; mouse drag injects additional density and velocity
  2. Diffuse velocity — viscosity via Backward Euler + Gauss-Seidel (Jacobi) iterations
  3. Advect velocity — semi-Lagrangian backtrace
  4. Project — enforce incompressibility: divergence → pressure Jacobi solve → subtract pressure gradient
  5. Advect density — carry smoke along the divergence-free velocity field

SPH (Lagrangian / particle-based)

2D water simulation using Smoothed Particle Hydrodynamics. Runs on the CPU and uploads particle positions and velocities to the GPU each frame via a Three.js Points object with a custom additive point-sprite shader.

Pipeline per frame:

  1. Spatial hash rebuild — O(n) neighbourhood queries with cell size = smoothing radius
  2. Density — Poly6 kernel
  3. Pressure — equation of state p = k(ρ − ρ_rest)
  4. Forces — pressure gradient (Spiky kernel) + viscosity (Viscosity Laplacian kernel)
  5. Gravity — −9.8 m/s² applied as a direct acceleration
  6. Integrate — Forward Euler
  7. Boundary — clamp + reflect with damping coefficient 0.3

Controls

Input Stable Fluids SPH
Drag Add velocity force + smoke density Push particles
Click Inject smoke density Spawn 10 particles
Space Pause / resume Pause / resume

Parameters (viscosity, diffusion, grid size, particle count, stiffness, etc.) are adjustable in the right panel at runtime.

Tech Stack

Item Technology
Language JavaScript (ES Modules)
Rendering Three.js r170 + WebGL
UI controls Tweakpane 4
Bundler Vite 6
GLSL loader vite-plugin-glsl
Environment Nix flake (optional)

File Structure

fluid/
├── index.html
├── vite.config.js
├── package.json
├── flake.nix                          # Nix dev shell (Node 22 + npm)
└── src/
    ├── main.js                        # Entry point: UI, mouse input, animation loop
    ├── renderer.js                    # Three.js WebGLRenderer + orthographic camera
    ├── stable-fluids/
    │   ├── FboPingPong.js             # Ping-pong render target pair (read/write/swap)
    │   ├── GPUComputationPass.js      # Full-screen quad pass wrapper
    │   ├── StableFluids.js            # Simulation manager
    │   └── shaders/
    │       ├── addForce.glsl          # Splat force/density at a point (Gaussian kernel)
    │       ├── advect.glsl            # Semi-Lagrangian advection
    │       ├── diffuse.glsl           # Jacobi diffusion iteration
    │       ├── divergence.glsl        # Central-difference divergence
    │       ├── pressure.glsl          # Jacobi pressure solve
    │       ├── project.glsl           # Subtract pressure gradient from velocity
    │       └── render.glsl            # Density → warm colour palette
    └── sph/
        ├── SPH.js                     # Simulation manager + Three.js Points upload
        ├── SpatialHash.js             # Hash-grid neighbourhood search
        ├── Kernels.js                 # Poly6, Spiky, Viscosity kernel functions
        └── shaders/
            ├── sph.vert.glsl          # Point-sprite vertex shader (passes velocity)
            └── sph.frag.glsl          # Speed → blue/cyan/white colour with soft circle

Getting Started

Without Nix

npm install
npm run dev

Open http://localhost:5173.

With Nix

nix develop
npm install
npm run dev

Production build

npm run build
npm run preview

Architecture Notes

Stable Fluids — GPU compute pattern

FboPingPong owns two WebGLRenderTarget instances. .read always holds the freshest result; .write is the next render target. Calling .swap() flips them.

GPUComputationPass wraps a RawShaderMaterial (with explicit attribute declarations required by WebGL) on a full-screen PlaneGeometry. Its .compute(renderer, fbo) method renders into fbo.write then calls fbo.swap(), so the result is immediately available on fbo.read for the next pass.

All uniform updates happen on the material before each call — no global state.

SPH — CPU simulation, GPU rendering

Particle state (pos, vel, den, pres) lives in Float32Array buffers updated on the CPU each frame. After integration the positions and velocities are written into Two BufferAttribute arrays (position, aVelocity) and uploaded to the GPU via needsUpdate = true. The fragment shader colours each particle by speed using an additive blend so overlapping particles accumulate brightness.

References

  • Jos Stam, Stable Fluids, SIGGRAPH 1999
  • fluid-three — Stable Fluids reference demo
  • david.li/fluid — SPH reference demo
  • M. Müller et al., Particle-Based Fluid Simulation for Interactive Applications, SCA 2003

About

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages