Marine-Snow-Simulation in Houdini

Join our workshop and discover how to simulate marine snow and oceanic particles for VFX – Master underwater simulations, ocean effects, and particle-based systems. Perfect for VFX artists and anybody who tripped over the phrase “Ocean dandruff”.

The force is deep with that one!

The basic framework already works quite well, but once you have started, you often come up with more ideas. At the beginning I mentioned that I wanted to create a simulation in shallow water and that particles should not come completely out of the area of influence of the waves. Nevertheless, a depth dependency can make perfect sense – even in water depths of a few metres.

The dependence on depth is ultimately a specific factor based on the Y-position of a particle. The further away a particle is from the surface, the smaller the influence of the total force should be. There are countless approaches to calculating such distance dependencies, such as the distance-squared law. This is a principle that occurs very frequently in physics and generally states that the “intensity” is inversely proportional to the square of the distance. In other words, if the distance to a source doubles, the “intensity” is only a quarter as great. Typical examples of this law are gravitation, but also the propagation of light and sound. The “problem” with this law is that it is physically correct, but often somehow doesn’t look realistic. I have therefore opted for a different approach.

3D scatter plot of data points in a cube

Relatively low

During my tests, the simulation domain was a cuboid with a fixed position. In many cases, however, it is necessary to position the domain freely and this must also be reflected in the calculation of the position-dependent forces. Fortunately, this only affects the Y-axis because the water surface is defined in this direction.

The top of the simulation domain, i.e. the surrounding cuboid, represents the water surface. To calculate the position of the surface, I still need the Y-position of the cuboid. The corresponding values can be read directly from the cube SOP in Houdini using ch(). However, this calculation should only be carried out once, as the size of the domain/box does not change during the simulation. I found it easiest to determine the size at frame 1 and save the relevant values as detail attributes.

float domain_pos = ch("../../box1/ty");
f@y_max = domain_pos + ch("../../box1/sizey") * 0.5;
f@y_min = domain_pos - ch("../../box1/sizey") * 0.5;

However, I could not simply insert this section of code into the existing script because the size data is only transmitted once the entire script has been executed. This means that for frame 1, the values for y_min and y_max are 0. In addition, the values would be constantly queried during the entire simulation. One way around this is to insert a geometry wrangle before the POP wrangle. The “Run Over” parameter of the new wrangle is set to “Detail (only once)”.

In the POP Wrangle, @y_min and @y_max are then read out. It can of course happen that the particles have a negative or positive Y-position. To avoid annoying case distinctions, I have normalised the values. This results in only positive values between 0 and 1.

// Normalised position in simulation domain
float normalised_depth = fit(@P.y, @y_min, @y_max, 0.0, 1.0);

This allowed me to calculate a damping that replaces the original damping. To get more control, I introduced two more variables. The depth_factor is a scaling of the damping and depth_scale indicates how fast the force decreases with depth. I have deleted the original variable definition (“float damping”)!

As mentioned at the beginning of the chapter, I did not want to rely on the distance-squared law, but used an exponential function instead.

float depth_factor = 1.0; // Scaling of the damping
float depth_scale = 0.5; // Speed of the force decay
float damping = depth_factor * exp(-depth_scale * normalised_depth);

The effect is not always easy to recognise and depends heavily on depth_scale. However, with 1.2. I have achieved a sufficiently rapid drop in the values, at least in my simulation.