World space normal?

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.

Of course, thanks for the quick reply and making me feel stupid, it’s all good now :smile:

It does :smile:

Note: I Think SCALED_NORMAL is a unity 3 macro.

mul((float3x3)_Object2World, SCALED_NORMAL);
1 Like

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 :

float3 worldNormal = mul( _Object2World, float4( v.normal, 0.0 ) ).xyz;
  • 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.

7 Likes

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.

1 Like

I love you guys for this thread! Aside from helping to fix my current shader issue, my world now makes sense. :slight_smile:

In case someone still has a problem using

float3 worldNormal = mul( _Object2World, float4( v.normal, 0.0 ) ).xyz;

it works if the transformation of the object is not scaled, if you apply scale in your transforms use this:

float3 worldNormal = mul( float4( v.normal, 0.0 ), _World2Object ).xyz;

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:

float3 worldNormal = mul( float4( v.normal, 0.0 ), _World2Object ).xyz;

Has the normal as the first parameter and the matrix as the second.

2 Likes

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.
14 Likes

Man you are super cool :smile: You are everywhere and that’s awesome

5 Likes