Fluid Simulation
5/19/2022There are two primary ways of simulating fluid: the surface can be approximated via the wave equation across a 2d plane, or the water particles themselves along with the forces between them can be simulated. My first attempt at simulating the surface of water starts with a grid of scalars (integers) and diffuses (spreads out) the values of each cell until an equilibrium is reached. Each cell during a simulation step has an amount subtracted from it proportional to the diffusion rate, and its neighboring cells receive one-fourth of that amount each.
Although simple and somewhat fluid-like, there isn't much going on here.
- index.html
- water.mjs
The next draft of this program uses a concept I was already familiar with, which utilizes a wave function approximated over two dimensions. The solution is derived in Mathematics for 3D Game Programming and Computer Graphics, Third Edition, in section 15.1. This approximation has a variety of uses, most commonly (such as in the example below) it is mapped to a grid where the resulting values for each point are used as surface depth.
There are several constants, which are the viscosity of the fluid, the speed by which waves are propagated through the fluid's surface, the distance between each grid point, and a time interval. For the sake of this simulation the demo automatically uses a minimum time step based on the viscosity and speed constants, and the distance stays constant. There are sliders to change viscosity and speed (of the waves) in the demo below.
The main limitation is, of course, that only the surface is simulated, so there can be no arching of the waves, and the technique is most useful for cheap and performance-friendly (while not the most realistic) renders.
- index.html
- water.mjs
For more advanced simulations that can run in real-time, I made my first attempt at a particle simulation. Each frame updates the particle positions and speeds, and any collisions between particles result in the particles reflecting off of each other. The particles in the demo below can be interacted with with the mouse, which gives a good idea as to how unrealistic it is. The issue with this approach is that the particles are actually rigid bodies, and there are no inter particle forces outside of collision.
Further, fluid particles do not collide in such a rigid manner, the collisions are softened, so being in proximity with a particle will cause a nearby particle to be affected but not deflected. There is a more clever way of simulating particles that takes into account this along with the various forces that liquid particles have on each other.
- index.html
- water.mjs
The solution is called smoothed-particle hydrodynamics, which simulates the viscous, pressure and gravity forces acting on a particle by adding up the surrounding particle's forces weighted by a dampening function. This means that for each particle, the aforementioned forces are summed up, and then those same forces in the nearby particles are added to that sum, but the added amount is made smaller (through the weight function) the further away the particles are.
These forces are derived from the incompressible Naiver-Stokes equations, approximated using taylor series for performance. The Naiver-Stokes equations describe the relationship between a fluid's pressure, density and viscosity, and the incompressible form is used because it is accurate for the vast majority of cases, and allow for some simplifications in the math making the simulation faster. The research paper by Matthias Müller, David Charypar and Markus Gross (https://matthias-research.github.io/pages/publications/sca03.pdf) describes this and the constants used in more detail, with the referenced paper by J. J. Monaghan Smoothed particle hydrodynamics (Annual Review of Astronomy and Astrophysics, 30:543–574, 1992.) giving an excellent derivation of the formulas used.
When implementing the solution, the particles are sorted into voxels, so that when comparing neighboring particles only the ones within the same or nearby voxel are compared, so that not every other particle has to be checked for each particle. This can safely be done as due to the weight function the inter-particle forces are 0 when the particles are further apart than a certain distance, so the voxels are set to be that distance in length and height. The voxels are set when the particles are created, and continually updated as the particles move.
There is another important change, in the way that it is rendered, now using marching squares rather than circles. Marching squares is a method of rendering a grid of scalars by forming a surface at the points where the values transition from being above a certain threshold to below. This is done by looping through each corner of every cell in the grid and first calculating a value based on the nearby particle positions. Then, for each value it is determined to be above or below the threshold. Based on this information a table of all possible shapes that cell may form is queried, and the resulting shape is drawn. For the case that a vertex of the shape is found on an edge of the cell rather than a corner, the position of that vertex is interpolated to be closer to the corner with the larger value, so that when a particle moves between cells it appears as a smoother motion.
- index.html
- water.mjs
- marching_squares.mjs
- gl.mjs
- vert.vs
- frag.vs
In retrospect marching cubes is limited in how smooth it can be without being too performance-heavy to run. Furthermore, there is a method to draw the particles in a more fluid manner using sprites and a post-processing effect, which may be better performance-wise as well.
The program begins by rendering each particle as a radial gradient, that is a white circle where the colors fade towards the edge. After all of the particles are rendered the resulting frame is re-drawn with the help of a post-processing effect, in which the pixels where the color is below a certain value are not rendered, and the rest are rendered in a solid color. This makes the particles appear as droplets that smoothly mesh into one another, similar to water droplets. Although this initially was not enough for a smooth look, adding the colors of the pixels surrounding each pixel instead of only considering the pixel alone blurred the effect enough to appear smooth.
I had also found that there was a mistake in the way the particles were being assigned to voxels and fixed it, resulting in a far more accurate simulation.
- index.html
- water.mjs
- gl.mjs
- vert2.vs
- frag2.vs
- vert3.vs
- frag3.vs
All code used in this post can be found on my github at https://github.com/realJavabot/webgl_liquid_sim.