Dev log time. I’ve been back in Houdini again, mesmerized by the OFFF By Night 2016 title sequence.

There are a ton of amazing shots in here, but today I’m just looking at the opening shot. Director William Arnold and artist eparizi have some behind the scenes experiments, but no indication of to how (other than “sop solver”)

After a few experiments, here’s what I’ve gotten to:

I’m decently happy with this recreation, but would love to hear if anyone (esp the artists on it!) can break down the effect better.

## The Breakdown

The setup is not that complicated: a bunch of thin grid geo repeated and passed into a SOP solver. A solver gives you the previous frame as input, and you modify how the next frame should look, the basis of simulations.

After the solver, the color and vertical position is applied. There is no shading here, separating the layers vertically is just to fix the z-fighting as they overlap.

SOP level node graph

Well the meat of what’s happening is in the solver itself.

• Every frame, we’re sampling some random activating points (from the `scatter` node above, passed in as an input).
• For each point in the stripes, we see if they are within some `RADIUS` of activating points. These points are now activated.
• Activated are given a velocity proportional to their distance to the activating point. There’s a lot of extra logic to make sure the points don’t move back, and only the top line of the stripe moves, the bottom is static. Additionally, only affect points in the hemisphere above the activating point, so points aren’t pushed backwards.

With some drag this gives a neat bursting growth, and as long as the radius of affected points is roughly greater than the max distance travelled by each point, the layers give the allusion of not overlapping.

Solver node graph

Debug viz of velocities

And roughly the `VEX` involved:

``````// find_active_and_dir
int pchandle = pcopen(1, "P", @P, ch("radius"), chi("max_points"));

@group_active = 0;
if (pcnumfound(pchandle) > 0) {
@group_active = 1;

v@active_center = pcfilter(pchandle, "P"); // avg(centers);
}

// set_vel (only applied to active group)
if (v@active_center[2] > @P.z) {
vector to_active_center = (@active_center - @P) * {1,0,1};
v@dir = normalize(to_active_center) * -1 * {0.5, 0, 1};
if (@dir[2] > 0) {
@dir[2] *= -1;
}

float dist = distance(v@active_center, @P);
float dist_scaling = `chs("../find_active_and_dir/radius")` - dist;
v@vel = @dir * ch("vel_scale") * dist_scaling;
}
``````

Motion test

Currently this overly uses above (greater z), like to determine the hemisphere. This breaks down if you rotate the points as well. This should be turned into forward, say with a dot product of an initial forward vector

## What could be better

Unfortunately when you spend your time recreating something, it becomes more about copying exactly than just making something good in its own way.

I’m not 100% happy with the motion, and I think it’s still simpler than what happens in the real sequence. For example, I’m fundamentally lacking these interactions:

Delay: the bottom yellow stripe grows after the initial movement, but is at the center of impact

Expand: the red stripe grows outward after the inital burst, making it more growth-like and not like a rounded lump

I’m not convinced this is a differential curve growth like where I first saw shots from the title sequence, but the motion is definitely more entrancing than what I have.