How to calculate world and view normals

Godot Version

v4.2.1

Question

I’m trying to find the equivalent of unity’s “UnityObjectToWorldNormal()” and “UNITY_MATRIX_V”.

I have been messing around with VIEW_MATRIX, INV_VIEW_MATRIX, MODEL_MATRIX but I haven’t been able to come up with something that works like I expect it to.

Unity Code:

float3 worldNormal = UnityObjectToWorldNormal(v.normal);
float3 viewNormal = mul((float3x3)UNITY_MATRIX_V, worldNormal);

After some digging I found multiple sources stating the following line as a way to get the world space normal:

vec3 worldNormal = (INV_VIEW_MATRIX * vec4(NORMAL.xyz, 0.0)).xyz;

Which I already tried, and running this while passing the world normal to ALBEDO it clearly changes color when rotating the camera, it’s still unlike the view space normal but it’s not a static value based on the orientation in world space like I’m used to in other engines.

Can anyone confirm this is indeed the correct way to get the world space normal?
If so i’m doing something wrong.

vec3 worldNormal = (INV_VIEW_MATRIX * vec4(NORMAL, 0.0)).xyz;
vec3 viewNormal = (VIEW_MATRIX * vec4(worldNormal, 0.0)).xyz;

Ok, so these lines do reproduce the lines in my first post, but only in the frag shader.
I have been and still am trying to get it to work in vert, so that’s where the issue is.

Not sure who I’m asking at this point but third time’s the charm, any suggestions on running it in vert and why this doesn’t work?

Mm… I’ll try to help you here, but I’m not exactly a shader expert.

In your last reply, you set the w-coordinate of the vec4 normal to 0.0 in your matrix multiplication.

By making use of the visual shader editor in Godot, I am able to see what Godot does when performing the same matrix multiplication.

By default, Godot sets the w-coordinate in the vec4 to 1.0, not 0.0.

My suggestion:
Try to set the w-coordinate to 1.0 instead of 0.0 in your normal.

Auto generated godot visual shader code:
vec3 world = (INV_VIEW_MATRIX * vec4(NORMAL, 1.0)).xyz;

A possible helpful resource:

I tried that before and when visualizing it by passing the normal to ALBEDO the result is not what I would expect, not even when moving the line to the fragment part which works fine with a value of 0.0.

So the calculations run as expected in the frag shader using the lines in my last post, the world normal corresponds to worldspace when rotating objects and displays a static color when rotating the camera, but when doing the same calculation in vert rotating the camera will influence the normal as well.

It’s weird to me that it runs correctly in frag but does something else in vert (mainly the camera rotation suddenly having an effect)

Alright. I went through the Godot Docs and I believe I found the root cause of what you are experiencing.

From Godot Docs:

Vertex data (VERTEX , NORMAL , TANGENT , BITANGENT ) are presented in local model space

Users can disable the built-in modelview transform (projection will still happen later) and do it manually with the following code:

shader_type spatial;
render_mode skip_vertex_transform;

void vertex() {
    VERTEX = (MODELVIEW_MATRIX * vec4(VERTEX, 1.0)).xyz;
    NORMAL = normalize((MODELVIEW_MATRIX * vec4(NORMAL, 0.0)).xyz);
    // same as above for binormal and tangent, if normal mapping is used
}

The reason it works for you in the fragment shader is because the normals, at that point in time, has been transformed into the space you are expecting (view space).

Summary

In vertex shader: normals are in local coordinates
In fragment shader: normals are in view space

More importantly, this shows how useful the documentation can be.

Here is the relevant documentation page for spatial (3D) shaders in Godot. In your case, scroll down to the Vertex built-ins section.

1 Like

Ah i missed that one, this works now in the vert shader, thanks!

NORMAL = normalize((MODELVIEW_MATRIX * vec4(NORMAL, 0.0)).xyz);
vec3 world_normal = (INV_VIEW_MATRIX * vec4(NORMAL, 0.0)).xyz;
vec3 view_normal = (VIEW_MATRIX * vec4(world_normal, 0.0)).xyz;
1 Like

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.