I’m trying to convert the vertex normals to world space, however for some reason the methods that work fine for position don’t seem to work with normals.
Here’s an example of a position conversion I found here on the forum:
float3 worldPosition = mul ((float4x4)_Object2World, v.vertex );
If that works, I’d expect the following to work fine too:
float3 worldNormal = mul ((float4x4)_Object2World, v.normal );
I’ve tried other methods but I just can’t get it to compile for some reason. Any clues why that doesn’t work and how you’re supposed to do it?
Normals are vectors and vectors behave differently than points (this is why transform has both TransformPoint and TransformDirection functions).
You will need to use a matrix that doesn’t contain the translation component to transform vectors. Since you’re not doing projection, this would be the same matrix but without the 4th column and 4th row (so it’s a 3x3 matrix). I don’t know if casting the matrix to float3x3 does this for you, but you could at least try that first.
Actually, the best methods yet is to write this for points : float3 worldPosition = mul( _Object2World, float4( v.vertex, 1.0 ) ).xyz;and this for vectors :
When you append 1 at the end of a float3 like (X,Y,Z,1) this means the multiplication with a matrix will add the translation part of the matrix during the operation : so a point gets rotated, scaled and translated.
When you append 0 at the end of a float3 like (X,Y,Z,0) this only rotates and scales the vector, it doesn’t translate it. Which is what we want for normals. And anyway it makes no sense to translate a vector : only points can be translated.
Anyway, when dealing with homogeneous coordinates (i.e. the fourth W component of a float4), try to always make the distinction between points and vectors and know what you are manipulating :
You can add 2 vectors : this yields a new vector
You can subtract 2 vectors : this yields a new vector
You can add a point and a vector : this yields a new point (i.e. a translated point)
You can subtract 2 points : this yields a vector (i.e. the vector going from the first point to the second one)
You CANNOT add 2 points together
This is basic geometry but the distinction between a vector (X,Y,Z,0) and a point (X,Y,Z,1) is all important here.
This is because object to world matrices containing scaling mess up the normals:
Notice that the normal in the triangle of the right is no longer perpendicular to it’s surface.
There’s a really nice explanation of this in “Introduction to 3D Game Programming with DirectX 9.0c a shader approach” by Frank D. Luna, pages 246 - 247. If you read it, you’ll notice that the correct matrix is the inverse transpose of the _Object2World matrix, that’s why the multiplication here:
There’s a Unity function for this that’s in UnityCG.cginc: float3 worldNormal = UnityObjectToWorldNormal(v.normal);
It’s basically just doing the equivalent of your code but broken out to: return normalize(_World2Object[0].xyz * norm.x + _World2Object[1].xyz * norm.y + _World2Object[2].xyz * norm.z);
Note: mul( v.normal, (float3x3)_World2Object ) also works and is more efficient than your example, and should result in identical code to Unity’s function. Doing a float4(xyz, 0.0) results in identical results, but means the shader might spend time multiplying things by zero that you don’t even want.*
Most shader compilers while likely optimize this away as it knows it’s a zero, but not all.