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:
- Get the object’s rotation in the material
- 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.