top of page

For an upcoming game, we wanted to create procedural foliage bending in the material that went beyond what pivot painter could do. To do this, we needed to be able to calculate the rotation.

​

We could use the standard RotateAboutAxis node, but that’s a bit expensive. We needed something cheaper for our foliage master.

 

Unreal has a builtin material function called RotateAboutWorldAxis_Cheap, which sounds great; it can spit out the x and y axis rotations and I can manually combine them. But there’s a few things wrong with the function.

For an upcoming game, we wanted to create procedural foliage bending in the material that went beyond what pivot painter could do. To do this, we needed to be able to calculate the rotation.

​

We could use the standard RotateAboutAxis node, but that’s a bit expensive. We needed something cheaper for our foliage master.

 

Unreal has a builtin material function called RotateAboutWorldAxis_Cheap, which sounds great; it can spit out the x and y axis rotations and I can manually combine them. But there’s a few things wrong with the function.

Improving Unreal's Material Functions

RotateAboutWorldAxis Node

For an upcoming game, we wanted to create procedural foliage bending in the material that went beyond what pivot painter could do. To do this, we needed to be able to calculate the rotation.

​

We could use the standard RotateAboutAxis node, but that’s a bit expensive. We needed something cheaper for our foliage master.

 

Unreal has a builtin material function called RotateAboutWorldAxis_Cheap, which sounds great; it can spit out the x and y axis rotations and I can manually combine them. But there’s a few things wrong with the function.

rotate1.png

Aside from the random “add 0” nodes, the first thing that stood out to me was that it calculates the sin and cos of the Rotation Amount 4 times for X, 4 times for Y, and 4 times for Z

rotate2.png

12 total trig calls on the same value!

​

Referencing this Instruction Cycles spreadsheet, with each sin and cos being 20 cycles, that can add up to be a performance hit. We really only need 2 of those, one sin and one cos, and that can be distributed to the 3 axes.

​

The second thing I noticed was that the Y axis is actually calculated wrong. When I try to test out each of the rotations on a mesh (the nodes below plugged into WPO), this is what I see:

rotate3.png

At a glance they appear to be all rotating correctly, but the Y rotation actually starts partially through the rotation cycle (0 rotation causes it to appear clipping into the ground sideways). This error is caused by a simple switch-up, where they accidentally swapped what X and Z should be in the function. Here is their incorrect formula: [Zcosθ - Xsinθ; Y; Xcosθ + Zsinθ]

​

The actual formula for rotating along the Y axis is: [Xcosθ + Zsinθ; Y; Zcosθ - Xsinθ]

​

After making that fix in my own material function, as well as reducing the unnecessary trig calls, we have a cheap rotation working!

rotate4.png

DeriveNormalZ Node

While trying to cut down on texture calls for a game, I came across the DeriveNormalZ shader node that Unreal has available. This node basically takes the R and G channels of a normal map and calculates the B channel using the following formula:

B = sqrt(1 - (x * x + y * y));

​

This seems like a great node because we can pack our textures more efficiently; rather than having a Color, Normal, and Mask map, we can have a Color (with alpha) and a XY Normal with B and A mask channels.

​

Sadly the DeriveNormalZ node has some problems. Sometimes it correctly recreates the normal, but sometimes it creates weird artifacting and tearing on the generated normal map:

tearingnormal.png

(Picture taken from tharlevfx’s video)

So I decided to create my own version of the node. The problem stems from using the square root on a value range that can possibly go negative; it’s not possible to square root a negative number. So I re-built their node in a material function, only this time I Saturated the values before the final square root:

2mapfunction.png

The result is now a correct reconstruction of the Normal map with no tearing. Below is an example of this in action with a Megascan plant.

3map.png
2map.png
Color_3map.png
Color_2map.png
Normal_3map.png
Normal_2map.png
  • Instagram
  • Vimeo
bottom of page