Node puzzles № 8 explanation

From time to time I post challenges, called Node puzzles, for Unreal users on twitter. Usually the solutions are simple enough to be tweeted as well, however #8 a bit more complex, justifying an explanation that’s longer than 140 characters.

See the effect bellow:

Here is the final material graph, driving a masked unlit material. It differs from the on originally posted as I simplified it a bit to make the mechanics clearer.

 

Node puzzles № 8 explanation NodePuzzles08 01 1600x475
Opacity
Node puzzles № 8 explanation NodePuzzles08 02

The simplest part is the opacity which is just a distance calculation and a ceiling for a black and white disc, located in the 0..1 UV area.

Camera facing

Turning the polygons toward the camera is done using the World Position Offset: we pass a vector for each vertex which moves them from their original position onto a plane that is parallel to the screen but passing through the pivot of the mesh.

(Note: Although in node previews we can see most values calculated per pixel, since we are feeding the World Position Offset material property, only the values right on a vertices will be used.)

Node puzzles № 8 explanation NodePuzzles08 04

To see how this works first let’s do something simpler: lay out the vertices on the floor based on their UV coordinates. The graph on the left does just that.

The U and V values are used as position offsets on world X and Y and we just fill the Z with 0. Since they are offsets they will be added to the original vertex world position, under the hood. However sometimes we would like to directly set the coordinates for vertices because it’s easier to think about the logic that way, like in this case. (“Go there →”) To achieve that we just subtract Absolute World Position from our values because that will negate the hidden “Add Absolute World Position” (aka offset) operation appended to our logic.

Node puzzles № 8 explanation NodePuzzles08 05

So, at this point the whole mesh is really tiny because the entire 0..1 UV space covers a 1×1 unit area in the world. To make things bigger we scale things up by 128 which is just the size of the preview plane, it could be anything really.

To make the UV layout more apparent here is what happens when the preview cylinder is used (I animated the values for clarity).

Note how the mesh splits at discontinuous UV’s, due to the duplicated vertices there.

Node puzzles № 8 explanation NodePuzzles08 06

Now instead of the world ground plane let’s flatten the mesh on a plane parallel to the screen. We can think of this as giving a different meaning to the number: “These coordinates are in screen space, not world space.” To express this change in meaning we use the Transform Vector node so it converts the incoming data from screen space (how we meant that vector) to world space (how the World Position Offset property expects it).

 

The X component is multiplied by -1 so the polygons (pancaked onto a plane parallel to the view) actually turned back toward the camera, otherwise we’d be looking at the (not rendered) backfaces. We will have to compensate for this X coordinate mirroring when we fake the lighting.

Node puzzles № 8 explanation NodePuzzles08 16

At this point we have a the mesh laid out so it’s facing the camera, regardless which preview mesh we select. And since all of them fully cover the 0..1 UV space, the circle what we use as an opacity mask is fully visible.

 

There are two notable things regarding the shadows: First, the distance field shadow on the ground still reflects the original shape of the mesh since that data is precalculated. The other interesting thing is that the cast shadow is always a disc too, facing the light source. This is because the view-world transformation of vertex positions also executed when the shadows are calculated: then the “view” is the point of view of the light source.

The ball
Node puzzles № 8 explanation NodePuzzles08 08

To make this disc look like a sphere we’ll need to generate spherical normals, to be used in our simple, custom fake lighting later on.

As you can see from the graph on the left, the X and Y components are simply horizontal and vertical gradients, from -1 to 1. (With X multiplied by -1 to match the flip done earlier at the WPO step.) The Z component is a bit more complicated: it changes the shape of a radial gradient from upside down cone to a half sphere. If you were to display the math on a graph (essentially representing a cross section of the shape) you would get a half circle instead of a V.

Node puzzles № 8 explanation NodePuzzles08 17

At this stage we have this colorful gradient on screen, with neither it’s colors nor it’s shape changing, no matter how we rotate the camera. So this is something view relative which we have to convert into the world coordinate system before we can use it for fake lighting. The output of the Transform Vector node is already starting to look like a ball.

Node puzzles № 8 explanation NodePuzzles08 11
Node puzzles № 8 explanation NodePuzzles08 12

The next 3 operations produce the fake lighting, where the light’s world relative (non-normalized) direction is defined by a vector 3 constant. The -1 on Z indicates that the light is pointing downwards. Using the same idea we also add sky occlusion.

Node puzzles № 8 explanation NodePuzzles08 13

A white ball is somewhat boring so let’s add a texture to it. To produce the UVs for a texture node we go back and use the output of the view-world Transform Vector node.

The Arctangent2 node creates an angular gradient which is sweeping around the center, in the -Pi..Pi range. The add and divide nodes convert that range to 0..1 which ensures proper tiling. That’s the U values down.

For the V we just crate a vertical gradient (-1..1) similarly how the sky occlusion worked, with it’s profile tweaked with the Power node for a visually more pleasant texture projection.

After appending the U and V branches and adjusting tiling the vector 2 data can be used as UVs for the texture sampler. I used the DefaultAlphaTexture asset from the Engine Content folder, for a black and white checkers image. It is the alpha in a lerp to blend between two colors. The result is then multiplied by the fake lighting.

Node puzzles № 8 explanation NodePuzzles08 14

The final touch is making the ball rotate by itself. To do that we have to go back and modify the output of the Transform Vector node just before it is used for generating the texture UVs. (The lighting related branches remain untouched.)

So the idea here is quite simple: take the “normals” describing our ball according to the current view, and rotate them by some pitch, yaw and roll values. To make it a bit more chaotic, those incoming numbers change at different rates.

The RotateVectorEuler is a material function doing the heavy lifting. I did not come up with the math inside it and unfortunately I can’t remember where I found the algorithm I copied. Probably Stack Overflow. 🙂

So that’s about it. I hope it makes sense but if it doesn’t then don’t hesitate to contact me, I’d be happy to (try to) answer your questions.

I’ll leave you with an alternate version of the effect: