Back to homepage

Real Time Fluid Dynamics

I'm a Web Developer who enjoys trying out new tech and had recently been spending some time learning Rust.

The Why

Sometimes you can have lot of fun learning a new skill in development but at the end say "cool, but what can I use this for?"

I came across this article for Fluid Simulation for Dummies by Mike Ash and I decided to convert the article's C code into Rust and start building something fun and interactive for the web.

C -> Rust -> WASM

Converting the C code to Rust was the easy part. Next I had to compile the code to the Web. One of the intersting things about Rust is that it can be compiled into many different languages. I used WebAssembly (WASM), a binary instruction format that allows low-level code to run at near-native speeds in the browser. It runs with minimal overhead, bypassing for example JavaScript's slower interpretation layer. The compiled binary format is therefore optimized for performance-critical tasks like fluid simulation.

Once I had my Fluid Sim code making super fast calculations I needed to figure out how to render this data in a way that was fun for the user and performant in the Browser.

Rendering

I started out with trying to render each cell as a square on a HTML Canvas. The issue with this was that each draw call to the canvas ate up a small peice of the CPU, multiply that thousands of times and the result is something super laggy.

Shaders

The solution is to turn to the dark arts, something that until now as as a web developer I had only seen glimpses of... Shaders. Shaders have their own C like language called GLSL (OpenGL Shading Language) which essentially allows your browser to talk directly to the computer's Graphics Processing Unit (GPU). The advantage of this is while your CPU processes tasks one after another, the GPU processes thousands of pixels simultaneously.

Computational Load

On a typical 128 × 128 grid, the solver manages over 16,000 cells. With 20 iterations per project and diffuse step to ensure numerical stability, the Rust core performs over 1.3 million calculations per frame.

Rendering Pipeline

Instead of slow DOM updates, the density field is passed as a Raw Float32 Array directly to a WebGL Fragment Shader. This allows for per-pixel interpolation and hardware-accelerated color mapping at zero CPU cost.

Interactive: Move the mouse around the canvas to inject momentum.