Of all the fun things we do at NVIDIA, pushing the limits of real-time physics simulation is always near the top of our list. Water simulation is a real challenge in a number of ways. First, you need a high resolution simulation (the count of particles or grid cells), especially if you want to flood a large area instead of filling a bathtub. Second, everyone knows what real water is supposed to look like, and it really stands out when you don’t get all the visual details exactly right. Third, the water should interact plausibly with other objects: things that float, that get smashed by waves, or caught in the current.
In that context, we’re very happy this week to share the results of an R&D demo project called Cataclysm. Although we spent considerable effort on the visual details, Cataclysm is primarily an effort to reach the scale of water simulation needed to flood a city scene with realistic-looking water, and we set aside for now the problem of rigid body interaction. We chose to develop Cataclysm in Unreal Engine 4 by Epic Games because it helps us to roll ideas out quickly and easily to a big community, and because the GPU particle system in UE4 offers a combination of features that helped achieve our goals without having to write everything from scratch.
The Cataclysm liquid solver can simulate up to two million liquid particles within the UE4 engine in real time. It uses a custom FLIP based GPU solver combined with Unreal Engine 4's GPU Particles with Distance Field Collisions. A FLIP solver is a hybrid grid and particle technique for simulating fluids. All Information for the fluid simulation is carried on particles, but the solution the physical simulation of the liquid is carried out on a grid. Once the grid solve is complete, the particles gather back up the information they need from the grid to move forward in time to the next frame. If you are interested in fluid solvers in general a quick web search will turn up a lot of information about FLIP solvers, and Robert Bridson's book is also a good source of information.
Results of the experiment can be seen here.
At the beginning of the frame, there is no grid to simulate the fluids on, only particles with position and velocity. The first thing the solver does is identify the the liquid boundary that is inside the bounding box of the FLIP solver domain. The liquid boundary is different from the liquid surface we render, and the distinction between the surface and the boundary is important because the liquid boundary is only seen by the solver, and anything within that boundary is solved on the grid. There are two ways to create the surface. The particle positions and a Smooth Kernel Radius parameter are used to splat the particles into a 3D smooth density texture . If the Surface at Density parameter is positive, then the liquid domain will be the level set of the smooth density field at that value. If the Surface at Density is 0 or a negative value, than the liquid domain will use spheres at the particle locations and the Particle Radius parameter to create the outline of the liquid domain. In that case it will also ignore any particles that have a smooth density value greater than the absolute value of Surface at Density. The most detailed liquid domains will come from a 0 Surface at Density value, but that can make single particle cause noise and float on the surface, so generally a small negative value is best for a detailed liquid boundary, or a positive value can be used for a liquid boundary that is faster to create but less detailed.
The Solid boundary is created for the solver from the same distance fields UE4 uses for GPU particle collision, which is the reason Liquid particles have to have the Distance Field collision type on their collision module. We want the solid boundary that the simulation sees to be the same one that the particles see. The solid boundary is used to set up the other set of boundary conditions for the fluid solver. The Voxel Width controls the voxel size of the grid, but the solid boundaries have a bit more detail because Cataclysm uses the variational boundary technique.
If you want to have your levels hold water, then you will need to have closed meshes for your level geometry, and they should be thicker than the Voxel Width. Meshes can be smaller, but the fluid simulation will leak through them or go around them, only noticing them as partially solid voxels.
Once the liquid and solid boundary are identified, the velocity from the particles is transferred to the grid with a sharp kernel. The divergence of the grid velocity is computed, and anywhere that divergence is not zero in a voxel we know that there is either too much liquid entering the voxel, or too much leaving it. A technique known as pressure projection is used, where we utilize a change in pressure to find the velocity that will make all the voxels have as close to a zero divergence as possible. The important thing for the FLIP technique is not how we get to a final velocity with a zero divergence, but that we keep the velocity from before and after the solve.
Once the grid solve is completed, the particles are moved through the grid with the final solved velocity. However, the velocity that the particles move with and the velocity that they must splat onto the grid for the next frame are not the same. Each particle also has a splat velocity stored on it. The PICSmoothing parameter is what controls the value of the splat velocity. If you want a very damped simulation, you can increase the PICSmoothing to 1, and the final velocity interpolated from the grid will be the same as the splat velocity. Because linear interpolation is used to get the velocity from the grid, and because the voxels are rather large, a PICSmoothing of 1 is not recommended. If the PICSmoothing is 0, then particles will keep their existing splat velocity, and instead of replacing it with the interpolated grid velocity, we just subtract the difference of the interpolated grid velocity before and after the grid solve. So, even though the particle is moved forward in time with the pure interpolated grid velocity, the information it carries in the splat velocity about the change in grid velocity gives the particles higher resolution detail, and makes the simulation much more lively and dynamic looking. A little PIC smoothing will help the particles though, so the value defaults to a small non zero value.
You’ll find more details and all of the code to our FLIP solver implementation here on a fork of Unreal (requires Unreal account to view):
Learn more about our Unreal forks containing NVIDIA technology here.