Bubble Trouble
I've worked on a number of jobs requiring small-scale fluid simulations over the past 2 years.
FLIP sims can be a bit of a nightmare to be honest - tricky to control, difficult to mesh and require a lot if iteration to dial in the settings. If you also include viscosity, object instancing and texture UVs you are signing up for additional fun/pain.
Download Hip File
The Goal
Our aim is a to produce a constantly-sourced stream of viscous fluid which includes bubbles (or other internal objects) tracking the fluid motion in a stable manner.
A Classic Viscous Pour Sim
We'll start with a standard pour with fluid continually sourcing from a sphere. It collides with a deforming object to produce some stretches and folds before collecting on a ground plane.
Viscous pours like this generally require reseeding to produce smooth results with minimal flickering when meshed. In fact we often want to significantly over-sample the surface of the fluid for best results as this excellent demo shows.
Other notable settings:
Swirly Kernel (Always use for viscous sims)
OpenCL on & Solve Viscosity with Adaptivity on (Recommended for final sims but not necessary for testing)
Min/Max Substeps set to 2/3 instead of 1/2 (Recommended for final sims but not necessary for testing)
We export the particle field and cache to disk.
Bubbles
Since FLIP outputs a particle field directly it makes sense to use it for our bubbles. By ticking the 'id' paramater in the FLIP solver 'Particle Motion’ tab we can randomly remove most of the exported particles from the particle field using a VEX snippet seeded by 'id'.
We set pscale on the remaining points with an attribute randomize also seeded by 'id' and copy spheres onto these points.
Render tip: Redshift supports nested dielectrics so the correct way to render bubbles in Redshift is to assign a 'Glass' material with a refractive index of 1.0 (air).
The Overlap Problem
For large numbers of tiny bubbles this does the job perfectly. However if you want to create larger bubbles (or copy some larger non-spherical bubble geo onto the points) you can run into issues where:
Adjacent bubbles overlap each other:
Bubbles which lie close to fluid surface start to penetrate the surface
Both of these can cause artefacts in your final render.
There are a few ways to solve #1 - here is my method. We use the pcfindradius VEX function to lookup adjacent bubble points and determine if they overlap. With a single wrangle we can resolve all the overlaps by reducing the pscale of each point by the overlap ratio.
This can make the bubbles smaller than necessary, so we can also gradually reduce the bubble radius and run a few iterations in a feedback loop instead (see the hip).
This method will cause bubbles to expand and contract over the duration of the sim as their proximity allows. It is possible to create a fixed pscale for each bubble for the entire duration - this requires an additional SOP Solver. It’s also possible to force certain points to have priority and keep to their original size while others scale down more aggressively. I’ve left examples of both of these options in the hip file.
#2 is pretty straightforward to solve. We use the xyzdist VEX function to find the distance to final surface mesh and reduce the bubble pscale if it's larger than this distance.
The Reseeding Problem
With reseeding enabled in our FLIP sim we are constantly creating particles in the most sparse areas of our sim and destroying particles in the most dense (based on the Birth and Death threshold values). A newly reseeded particle is assigned attribute values based on the average of it's neighbours, allowing it to properly blend critical ones such as velocity, rest or UV. However it's 'id' attribute is always unique. With our randomised-by-id method of keeping points for our bubbles post-sim you will notice that newly reseeded points pop into existence suddenly and un-reseeded points disappear. There is no attribute or group assigned to say 'this point is the result of reseeding' and points are created all over our fluid so how do we resolve this?
The “disappearing points” issue can be solved quickly and unelegantly by raising the 'Death Threshold' value.
The “appearing points” issue needs more work. One solution might to scale in bubbles during the first few frames of their existence - this would certainly work if newly reseeded points began with @age=0 but unfortunately this is inherited from neighbours on birth.
We start by creating a 'float_id' float attribute on the FLIP source points. This can be any whole number - here we have used 1.0 * @ptum. Add this attribute to the list of attributes interpolated during reseeding in the FLIP solver. During the sim newly reseeded points will be assigned new 'float_id' values. You will notice that due to neighbour averaging these are generally not whole numbers. On our sim output we can now group/delete any points where 'float_id' is not whole with this snippet:
if(f@float_id % 1.0 > 0.000001){
i@group_reseeded = 1;
}
It’s technically possible for a reseeded point to have an integer 'float_id' value. Imagine the new point is averaged from 3 with float_id values of 1.0, 4.0 and 10.0. In this case our new point is given a float_id of exactly 5.0. We can create a second identifier like 'float_id2' = (23334*@ptnum + @Frame) and cull any point where either float_id or float_id2 are non-integer to resolve this.
We now have a consistent flow of non-overlapping points to use for our bubbles.
Oriented Instances
Embedding larger objects onto the surface or interior of a FLIP sim in a believable manner is an extra challenge. The tips above will take you part way but you now face the additional problem of orienting pieces to the fluid flow or fluid surface and rolling them smoothly during advection. There are several different approaches such as attrib-transferring the fluid surface normal to create an orientation each frame, running a secondary particle sim with POP Spin By Volumes, or creating custom spin in a SOP solver.
I may cover these in a future post, along with some meshing techniques.