V-Ray NormalBump pass Screen Space to World Space conversion


I’m trying to transform normals from V-Ray NormalBump Pass https://docs.chaosgroup.com/display/VMAYA/BumpNormals to world-space

Unfortunately it’s not well documented in what space these normals really are. They just call it ‘screen space’.
It’s not the same screen-space normals as in realtime render engines though, since normals on flat surfaces are not constant.

It “looks” like it’s the normals in ray-space, with the reference frame being built as a left-handed system where Z goes in opposite direction of the ray (so, towards the camera), X points perpendicularily to the left and Y points perpendicularily upwards (and then the range is remapped from [-1, 1] to [0,1]. Note that the example image in the link does not seem to show normals remapped to unsigned interval, but it’s incorrect. The actual output looks like the one in this link: https://docs.chaosgroup.com/display/VMAYA/Normals which is clearly remapped to [0,1])

So I tried to compute the world-normals by:

For each pixel, compute the corresponding world-space ray.

Build the ray-space as (world-space is right handed):

Vector3 ray_frame_Z_axis = normalize(-ray_dir_in_world_space);
Vector3 ray_frame_X_axis = normalize(cross(ray_frame_Z_axis , camera.up_vector_in_world_space));
Vector3 ray_frame_Y_axis = cross(ray_frame_X_axis , ray_frame_Z_axis );

And then computing the world-space normals as

Vector3 signed_normal_in_ray_space = normalbump_pass_pixel_val * 2 - 1;  // map back from [0,1] to [-1, 1]
Vector3 world_normal = signed_normal_in_ray_space .X * ray_frame_X_axis  + signed_normal_in_ray_space .Y * ray_frame_Y_axis + signed_normal_in_ray_space .Z * ray_frame_Z_axis ;

but I’m not getting correct results.

Does anyone know where I’m going wrong or how exactly to do this?