Stylized Culling

This post is part one in the post series on my re-implementation of the GDC talk “The Inner Workings of Fortnite’s Shader Based Procedural Animations”. For the other parts, click here.

Above, you can find an export for the web player. It features a set of objects randomly spawned on a plane. Move around using WASD and the mouse. Press space to see a visualization where the cull-value is shown as black and white color.

The idea is that objects that are about to become visible will grow from a single point to their full size and do a little “bounce” where they are shortly larger than they are. The following diagram shows the function (the x-axis is the distance scaled to the [0-1]-range, with 0 being at the far clipping plane of the camera and 1 at the distance where the effect should stop being active).

Stylized Culling

The way the shaders are implemented in Unreal Engine is that they are built with the Unreal Material Editor. I am looking closely at Unreal now that it has become very much affordable, but for the time being I’m using Unity. In Unity, except for plugins, you have to write your shaders by hand, so I had to figure out ways to build the shaders with the same effects as the nodes in Unreal.

The stylized culling relies on a node which provides a value ranging from 0 to 1 (or from black to white) that is animated when objects are about to be culled. To develop the shader, I first created a shader with a float property “Cull” that was intended to be fed with this value. Then the calculations are pretty easy: You take a vector that results from subtracting the position of the input vertex by the pivot point/origin of the object. This creates a vector that points from the input vertex to the pivot. Offsetting the input position by this vector will pull the vertices to a single point at the pivot location. Scaling the vector by the animated “Cull” value will create an animation that will scale the object from original size to size 0.

The second part of the animation is to add the bounce. In Unreal Engine, this is achieved with a 3PointLevels which will take a gradient and change the output when driven with this gradient, interpolating between the new white, black and gray points. In the shader in Unity, we can re-create this effect by calculating two values derived from the cull input. Note that we first shape the function in the range 0-1, otherwise, the multiplication that is coming up would distort values of the other flank. The first one is the increasing part which should reach 1 at 0.75 and stay there. This means we multiply the input value by 4/3 and clamp the result to the range 0-1. The other flank should be 1 until 0.75 and go down to 1/1.25 at 1. This is achieved by the formula min (1, (-4.0/5.0) * x + (8.0/5.0)). You can find this of course by taking two points on the linear function and solving it. After multiplying this with 1.25, we get the correct animation values for the bounce, landing at 1 from x = 1 onward. The following diagrams show these two parts. When multiplied with each other, the the parts where they are 1 are multiplied with the rising/falling value of the other function, resulting in the combination of the two.

Finally, the culling has to be driven by the actual culling system. I’m not sure how Unreal handles this part, but I imagine that the action is coded into the culling system somehow. In Unity, there is to my knowledge no hook into this system. This meant I created a new component that is attached to the camera, reads the culling distance from the camera and calculates the cull animation value from the distance. Until a short distance from the culling distance, the value should be 0 and then linearly rise to 1 when the object is culled.

Takeaways
This shader is, at least in Unity, somewhat silly. I go through all the objects in the scene in a large loop and feed the distance to the shader. It would probably be much more efficient to just set the scale right there. But writing the shader and figuring out how to translate the black/white gradients in the talk to functions was interesting.

You can find the source code to this and the other shaders on github.