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:

offf by night 2016 mill recreation

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.

offf by night 2016 mill sop node graph

SOP level node graph

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

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.

offf by night 2016 mill solver node graph

Solver node graph

debug velocities

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:

offf by night the mill delayed effect

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

offf by night the mill expand effect

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.