You can simulation fluids in lots of different ways - using particles or springs! But if you want to do it so it's all swirly and juicy - and looks out of this world - then grid-based models are the way to go (along the lines of computational fluid dyanmics).
The reason it is often avoided - and makes people run for the hills is the Navier Stoke equations. They arne't pretty!
We're going to go through implementing it on WebGPU! We want it fast, realistic, interactive and runs in real-time!
Fluid simulation output - see the die/color mixing - in addition to showing the velocity field.
Fluid Simulations - What? Go through what it all means...
Lets talk about whats happening and why...
We have a grid of values - and within each grid cell is some information.
Two pieces of information in each cell:
1. velocity and
2. die color.
As you'll notice from the figure below - the key pieces of information are the die (color) and the velocity which is reponsible for moving the die/color around. You could do a simple simultion with just these two values - but you'll notice it doesn't look realistic (just looks like it fades and scrolls - it lacks that swirling and mixing that you'd see when you look at a liquid in a glass).
Example of the program running - showing the interactive nature - start with a die in the pattern of some text.
Velocity Field
For any given point in the grid, we can access the velocity at that point using the x, y coordinate, this velocity value tells us the velocity of the fluid at that point.
Easiest way to think of the belocity field is the direction that things are moving, so if you have:
For instance, if every cell has a velocity
u(x,y)=(1,0)
- then it will appear to scroll to the right.
Advection - is when we use this velocity to move things about! If you’ve got some black dye in some water, and the water is moving to the right, then surprise surprise, the black dye moves right.
Of course, you can have very complex swirly velocities representing more complex currents - so the dye swirls and mixes in a more complex manner.
So you can imagine, if lots of cells each having a vectors; each pointing in different direction with different magnitudes - we'd have to try and keep track of what is going where! This can be complex and messy!
A better way is to take a cell - and look at the velocity vector for that cell - and look at where it came from - not where it is going.
So, instead of figuring out where our imaginary particles at the grid points go to, we'll figure out where they came from in order to calculate the next time step.
With this scheme, we only need to write to a single grid point, and we don't need to consider the contributions of imaginary particles coming from multiple different places.
The last teensy hurdle is the value from where it came from might not be at an exact grid point. For instance, it comes from (x=10.2,y=33.5). We use a bilinear interpolation on the surrounding 4 grid points instead of just picking the value rounded to the nearest digit. If we just use the rounded value - the liquid becomes unstable and noisy and doesn't move smoothly as we want.
Advecting the die is easy and straightforward - and we can test the advect function is working - by watching the die color scroll in the direction of the velocity.
In addition to advecting the die color, we also advect the vector itself! Just as the ink would move through the fluid, so too will the velocity field itself!
Divergent Fields (Keep Density)
This is where it gets complex :( Water is roughly incompressible. That means that at every spot, you have to have the same amount of fluid entering that spot as leaving it.
If we just use the advection - and the vectors push against each other - the die would disapear - it would be squashed into nothing! It emulates the pressure/density characterists of liquid - this is what gives us those swirly characteristics.
Mathematically, we can represent this fact by saying a field is divergence-free. The divergence of a velocity field (u) is a measure of how much net stuff is entering or leaving a given spot in the field.
An incompressible fluid will have a divergence of zero everywhere. So, if we want our simulated fluid to look kind of like a real fluid, we better make sure it's divergence-free.
This is the point that brings in the Navier-Stokes equations which describe the motion of fluids! These equations can be scary - especially if you're not comfortable is differential equations and solving for unknowns ;)
Calculating Divergence
The divergence of a velocity field \( \vec{u} \), denoted as \( \text{div}(\vec{u}) \) or \( \nabla \cdot \vec{u} \), measures the net rate at which "stuff" is entering or leaving a specific location in the field. For a 2D velocity field, it is defined as:
At this point, it’s helpful to remember that, for the purposes of the simulation, we're not interested in knowing the value of pressure - we just need to calculate it to solve our problem.
We're dealing with a discrete system - with approximate steps for the cells - so it won't be exact - but it'll be close enough to help stear our solution the the right direction.
We want to know about its approximate value to correct the velocities at the grid points.
The goal is to calculate the pressure \( p \) on a grid, where \( \epsilon \) is the distance between adjacent cells. For each grid point \( (x, y) \), we form one equation with 5 unknown pressure values, based on the velocities at surrounding points. If the grid has \( n \times m \) locations, this leads to \( n \times m \) equations.
Using cleaner notation, we define \( p_{i,j} \) as the pressure at grid point \( (i, j) \), and group known values into a divergence term \( d_{i,j} \). The system of equations is simplified as:
We can't get the best solution in a single calculation - too many unknowns - instead, we can use a iterative approach to find a best guess solution.
The iterative method of solving this system of equations, where each iteration provides values closer and closer to a real solution (use the Jacobi Method).
In the Jacobi method, we first rearrange the equation to isolate one term: