Here’s a short video we made on how we built the spinning gear material in Septaroad Voyager. We wanted to build the animation into the material so that we can copy it for use in many places without the setup of a skeletal mesh animation.

For anyone who wants to take a closer look at the HLSL without pausing and zooming in on the video, here it is:

float3 output;
float R = NormalIn[0];
float G = NormalIn[1];
float B = NormalIn[2];
float radius = sqrt((R * R) + (G * G));
float Theta;
if(radius == 0)
{
    return NormalIn;
}
R /= radius;
G /= radius;
Theta = asin(R);
if(G > 0)
{
    Theta -= dTheta;
}
else
{
    Theta += dTheta;
}
output[0] = sin(Theta) * radius;
output[1] = cos(Theta) * radius;
if(G < 0)
{
    output[1] *= -1;
}
output[2] = B;
return output;

Let’s take a look at what that code does.

Every texel in the normal map is a color. That color consists of red, green, and blue components. The red, green, and blue components represent a direction. In Unreal, green points down, red points right, and blue points straight up out of your monitor. Assuming you are using 8 bits per channel (and for this to work in your game engine, you probably do need to use 8 bits per channel), your image editing software will save each texel’s red, green, and blue components as a value from 0-255. When you import that file to Unreal 3 (not sure about new versions of Unreal), if you compress it as a normal map, then the game engine will re-scale the 0-255 to a float value between -1 and 1.

So every texel of our normal map, after being imported into the engine, will contain three values between -1 and 1. Those values are the components of a 3-dimensional vector pointing in some direction. The total length of the vector will be 1.

What our code does is convert the R and G (or X and Y) components of the vector into polar coordinates radius and Theta. We take a value dTheta as an input and add it to Theta, and convert that back into an RGB vector.

In other words, we are rotating the normal map’s frame of reference. If we start off with green pointing down and red pointing right, and we rotate the normal map by a dTheta of pi/2, then green now points to the right and red points up.

In the example video above, we combine this code with a rotation material node to rotate a normal map while keeping it consistent with the original frame of reference.