How to: Get object rotation in material, recolor the normal map based on rotation, for UDK world-space tiling--with correct normals even if you rotate the mesh

Hi everyone. Sorry, but the Legacy section is still broken and I can’t post down there. I’m putting this up here so that at least the information is available to anyone searching for it. And maybe the principles will be useful for someone learning UE4 too.


This is something that has been bothering me for years and I finally sat down and figured it out. World-space tiling is relatively easy. The problem is that your normal maps will break if they are tiled in world-space and you rotate the mesh.

Example:


In this image, the tops of the two meshes have the same normal map tiled in world space, but one has been rotated 180 degrees, so the normal map is wrong and it appears to be lit from the wrong direction.

In order to fix this issue, we need to do two things:

  1. Get the object’s rotation in the material
  2. Recolor the normal map to account for the object’s rotation

I have no idea if this has already been implemented in UE4. But this is how you have to do it in UDK or UE3.

At the bottom right, we are taking two vectors (1,0,0) and (0,1,0) and transforming them from tangent to world space. [Edit: Now that I’m looking closer at it, I think that works for me because the surface is pretty close to flat. Local space to world space might be a better transformation if your surface isn’t flat with the XY-plane.] That is, we are taking the X and Y directions from the original mesh, and finding out what those vectors are in world-space in case the mesh is rotated. Then we take the dot product to get one value telling us how aligned the mesh-X is with world-X, and another value telling us how aligned mesh-Y is with world-X. Then we feed those values into a custom node.

float pi = 3.141592653589793238;
if(DotRed < 0)
{
	return pi +  atan(DotGreen / DotRed);
}
if(DotRed > 0)
{
	return atan(DotGreen / DotRed);
}
if(DotGreen > 0)
{
	return pi / 2;
}
return -pi / 2;

This code converts the DotRed and DotGreen values into Theta, measuring yaw in radians.

Then we feed that value into another custom node.

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;

This custom node takes a normal map, converts the (R,G,B) vector of the normal map into polar coordinates, rotates those coordinates by dTheta, and then converts those polar coordinates into a new (R,G,B) normal map.

The end result is world-space tiling where you can rotate your meshes at an arbitrary yaw with a correct normal.

I have no idea about what to do here, but it might be some use to have a look in the current engine version of the node ‘world aligned normals’ ( I think it’s called ). And then just basically copy that code into your shader.

Node internals as snippet

Thanks for the info. That looks pretty complicated. I think I like my solution more. I wasn’t really looking to change my solution. Mostly I was posting to let others know how to solve the problem. I haven’t tried the world aligned normal node. Maybe it works perfectly well for UE4 and UE5 users. But in case anyone wants to know how to implement this in UE3, or just wants to know how to manipulate normal maps or how to work with object rotations in the material, I think this is a good resource.

In practice, the only thing I had to change was clamping the final output from -1 to 1 because some pixels ended up buggy. Combining this with triplanar projection, I’m able to put lots of meshes in my levels, cover them with the same material, and then make them any size or rotation I want. And the material covers them all seamlessly.

Beg your pardon, I thought it was a question :smiley:

Nice solution.

( maybe move it to the tutorial section? )

1 Like

Done! I didn’t even realize there was a tutorial section. I need to spend some time checking out the rest of the community instead of hiding down in the Legacy section.

2 Likes