Skip to main content
gamedev.net
🔒 Locked

worldspace normalmap - tangent space normalmap

Started by remdul Oct 2, 2008 at 5:27 PM 12 replies 6.2k views
Original Post
remdul
remdul
I'm trying to convert a world space normal map to tangent space normal map, with the help of existing TBN matrix (I cannot change these). How do I do this? Someone suggested to me that all I have to do is rotate the world space normal by the TBN matrix, but it doesn't give correct results... Thanks.
swiftcoder
swiftcoder
Quote:
Original post by remdul
Someone suggested to me that all I have to do is rotate the world space normal by the TBN matrix, but it doesn't give correct results...
My intuition (which has been known to fail) says that you need to rotate by the inverse of the TBN matrix.
Tristam MacDonald. Ex-BigTech Software Engineer. Future farmer. [https://trist.am]
Ashkan
Ashkan
Normals must be transformed by the transpose of the inverse of the transformation matrix that's used to transform geometry, so if the matrix used to transform geometry is called M, then you must use the matrix, N, below to transform normals of this geometry.

N =  transpose( inverse( M ) )

Further reading.

BTW, don't forget to set the w component of your normals to zero if you're working in homogeneous space. Otherwise, your normals would be affected by translations which is not correct.
remdul
remdul
Whoa whoa.

The TBN matrix is a 3x3 rotation matrix. So transpose == inverse. Inverse transpose would do exactly nothing then, right?

Also my source normals do not have a W component, they come from a simple world space normal map texture (rgb -> xyz).

Or am I missing something here?

*Edit: changed for clarity.

[Edited by - remdul on October 3, 2008 12:24:16 PM]
haegarr
haegarr
Quote:
Original post by remdul
The TBN matrix is a 3x3 matrix. So the transpose == inverse rotation. Inverse transpose would do exactly nothing then, right?

Its not a matter whether the matrix is affine or homogeneous, its a matter whether the matrix contains some scaling. If so, them the transpose of the inverse is no longer equal to the original, otherwise it is.

Quote:
Original post by remdul
Also my source normals do not have a W component, they come from a simple world space normal map texture (rgb -> xyz).

Normal vectors are direction vectors, and as such (would) have a homogeneous co-ordinate of 0, be it explicit (if using homogeneous vectors) or implicit (if using affine vectors).


AFAIK, if you have a TBN matrix M given for a point on a surface, and you have a normal vector nT in tangent space, then
nO := M * nT
would convert the normal to object space, assuming that the TBN is given in object space. E.g. if nT is [ 0 0 1 ] you would pick out the N column vector of M what is absolutely okay. Hence, the other way around is
nT := M-1 * nO

What begun me to wonder was the usage of the term "world space" normal map. I had expected an "object space" normal map instead. So, are you sure that the space in which the TBNs are given is the same in which the original normal map is given, or at least the transformation between the 2 spaces is the identity transformation when you perform the conversion?
remdul
remdul
Yes, when I say 'world space' it is the same as 'object space', because the geometry isn't rotated.

A quick illustration of what I'm trying to do:

:-P

The reason for this is that the current game engine we're working with doesn't allow us to change the TBNs nor shaders. The normal maps rendered in 3dsmax use a different TBN convention. So we're trying to compensate for this by adjusting the tangent space normal map to the geometry (tangents) instead. Well not really 'adjusting'; generating them from scratch using a world/object space normal map rendered in 3dsmax because that's easier (or so I thought).

I will try nT := M-1 * nO, if I can figure out what that means in practice.
remdul
remdul
Uh, scratch that, it is irrelevant because the matrix is a rotation matrix.

Is this even possible?
haegarr
haegarr
Quote:
Original post by remdul
I will try nT := M-1 * nO, if I can figure out what that means in practice.

You have already tried this (as you've stated in a post above): It just means to multiply the object space normals by the inverse of the TBN. I've written that formula to show that the assumption to go that way seems me correct from a mathematical point of view. So I assumed (and still do) that the problem is hidden elsewhere. Perhaps it is actually the definition of TBN as used by 3dmax (which I don't know); e.g. is the LHS/RHS convention correctly considered?
remdul
remdul
Yes, I'm thinking about the same, I may have the normal/tangent/bi tangent vectors mixed up and when I assemble them to construct rotation matrix it has not the correct orientation.

Another minor complication is that the b-tangent (or perhaps it is really the tangent) isn't stored in data. It is implicit, that is, has to be reproduced by taking the cross-product of the stored normal and tangent. But AFAIK this should, in the worst case, result only in flipped normals on one axis (e.g. we keep the normal vector but lose the sign). The normal map should be otherwise relatively correct (and mostly blue-ish as opposed to the colorful mess I get now).

Thanks for your thoughts. :)
haegarr
haegarr
Quote:
Original post by remdul
Yes, I'm thinking about the same, I may have the normal/tangent/bi tangent vectors mixed up and when I assemble them to construct rotation matrix it has not the correct orientation.

Another minor complication is that the b-tangent (or perhaps it is really the tangent) isn't stored in data. It is implicit, that is, has to be reproduced by taking the cross-product of the stored normal and tangent. But AFAIK this should, in the worst case, result only in flipped normals on one axis (e.g. we keep the normal vector but lose the sign). The normal map should be otherwise relatively correct (and mostly blue-ish...

I think so, too. Whether the stored tangent is the one or other (i.e. bi-)tangent should also play no role, as long as it is ever used with the same semantic. The semantic is prescribed by the shader script, and hence choosing the wrong semantic may let the light appear to be relocated by a 90° rotation, but that has obviously no effect already on the look of the tangent space texture. The only condition is that the normal is used as the Z axis vector if a blue-ish texture is expected to result at the end.

Quote:
Original post by remdul
...as opposed to the colorful mess I get now).

Perhaps the underlying algorithm (or its implementation) is problematic? The only (not optimized) algorithm coming ATM to my mind is as follows:
(1) Iterate over the texels of the texture of the object space normal map.
(2) For each normal use the (u,v) belonging to the texel to find the mesh's triangle covered by the texel.
(3) Fetch the 3 inverted TBN matrices belonging to the 3 vertices of the found triangle.
(4) Interpolate the 3 matrices weighted as prescribed by the relation of the texel's (u,v) to the (u,v)s of the 3 vertices.
(5) Tranform the object space normal by the interpolated matrix.
(6) Store the transformed vector at the texel's (u,v) in the texture of the tangent space map.
What algorithm do you use?
remdul
remdul
I figured out the problem, turns out I neglected to flip the normal/tangent/bitangent on the x-axis. My program uses different convention than the geometry so I mirrored it in the modelview matrix, but had totally forgotten to take this in account when constructing the tangent matrix! :-O

So yes I've got everything working now, and got correct tangent space normal maps now. :-)

So the correct way indeed is to rotate the world/object space normal by the inverse (==transpose in this case) tangent matrix.

By the way, the algorithm I use is the other way around, and a bit more efficient:
1) loop through all triangles
2) fetch normal/tangent/bi-tangent
3) lerp them using UVs
4) normalize the three vectors
5) construct the rotation matrix
6) take the object space normal from source texture, rotate it by inverse matrix
7) rasterize the resulting vector

I then also run a post-pass on the resulting texture to 'fill out' pixels on triangle edges to avoid texture bleeding on the UV seams.

Cheers!
haegarr
haegarr
Quote:
Original post by remdul
...
So yes I've got everything working now, and got correct tangent space normal maps now. :-)

Fine :)

Quote:
Original post by remdul
By the way, the algorithm I use is the other way around, and a bit more efficient:
1) loop through all triangles
2) fetch normal/tangent/bi-tangent
3) lerp them using UVs
4) normalize the three vectors
5) construct the rotation matrix
6) take the object space normal from source texture, rotate it by inverse matrix
7) rasterize the resulting vector

I then also run a post-pass on the resulting texture to 'fill out' pixels on triangle edges to avoid texture bleeding on the UV seams.

The avoidance of such post processing is just the reason I've thought to iterate over the texels instead. Iteration over texels means using as well as reproducing exact (u,v) co-ordinates, and no original vector can be missed. This expects the source (i.e. world space texture) to be perfect in this sense, of course.
remdul
remdul
Actually rasterization as I described above is pixel perfect (if properly implemented of course), it doesn't leave gaps between connected triangles. The only reason I run that post pass is to add some pixels near UV seams, not triangle edges (this is often called 'padding', it is exactly the same as what many lightmap renderers do to avoid pixel bleed).

You are correct that your method will reproduce the exact same UV coordinates, but that's not a requirement as there are no textures being sampled (effectively only the rotation matrix is rasterized). And on triangle edges, aliasing may occur just the same with either method, unless some form of AA/super sampling is implemented.

And indeed, the source texture must be correct, but I think my rasterization perfectly matches that of 3dsmax so it doesn't cause any trouble with pixels being missed.

Oh, and I realized that I didn't have to mirror/flip the x component of the normal/tangent/bi-tangent when constructing the matrix, just flipping the red (x) component in the source (object space) normal map resulted in the same correct solution.

Cheers.

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.