Skip to main content
gamedev.net
🔒 Locked 🎮 Unity

Tangent Space

Started by StasB Mar 20, 2009 at 10:07 AM 6 replies 2.1k views
Original Post
StasB
StasB
I'm trying to implement tangent-space bump-mapping, which is graphics-related, but I'm going to post my questions here, since they have more to do with math. After reading a couple of articles on the subject, I have arrived at the conclusion that they don't make any of sense to me at all, which is making me REALLY nervous. So we have three points. They have the coordinates (x0, y0, z0), (x1, y1, z1), (x2, y2, z2) in world-space, and (u0, v0, 0), (u1, v1, 0), (u2, v2, 0) in "tangent space". Given the world coordinates of a fourth point, I need to find its corresponding coordinates in the tangent space. The articles talk about finding two vectors in world-space with the directions in which the u and v coordinates increase, and call those the tangent and bi-tangent (T, B). Then, they build this matrix: |Tx Ty Tz| |Bx By Bz| |Nx Ny Nz| (Where N is normal to the triangle formed by our three points.) They then say that multiplying a point in tangent space by this matrix will transform it into world space. Well, surely, it would transform it into SOME other space, but why this space would be exactly world space is beyond me. Let alone the fact that I don't get the derivations of T and B, I wouldn't know how to use them correctly even if I had them given. First of all, the articles all seem to solve the problem backwards. Why not build a matrix to transform from world to tangent space in the first place? What difference could there possibly be in the operations that need to be taken? Second, isn't multiplying a 3D vector V by the above matrix the same as taking the dot-products between V and T, B, N? But this would give us a point relative to the origin of T, B, N in world space. No one seems to tell how to find this origin, though and I don't see why it should be (0, 0, 0). They all seem to imply that T, B, N are normalized and normal to each other. I don't see no reason for any of that to be true. Am I going crazy here? Clarifications highly appreciated!
Bungo
Bungo
If you start with a triangle in world space, there's more than one valid basis for the tangent space with respect to that triangle.

A basis for any 3-dimensional space is simply three orthogonal unit vectors, and a basis for the tangent space with respect to a given triangle can be formed by taking ANY two orthogonal vectors that lie in the plane defined by the triangle, along with a third vector that is orthogonal to those two (a surface normal), which can point either "outward" or "inward" depending on what you want to achieve.

So using your coordinates, I start by finding one vector in the triangle's plane. Simply subtracting any two triangle coordinates will do this, e.g.:

v1 = (x1-x0,y1-y0,z2-z0)

Then I can normalize this vector by its length and call it my tangent vector:

T = v1 / ||v1||

where ||v1|| is the length of v1.

Next, I want a unit vector B perpendicular to T but also lying in the plane defined by the triangle. Once T is defined, this narrows my options for B to exactly two choices, and in fact it's really only ONE choice once I decide whether I want my N vector to point outward or inward, and assuming I want a right-hand coordinate system.

I assume you can work out how to obtain B and N. (Actually it's easiest to find N first, by computing the cross product of ANY two vectors in the triangle plane and normalizing and choosing the sign to point the way you want.)

So great, now you have T, B, and N which are unit vectors that form a basis for the tangent space with respect to the triangle. We have specified the coordinates for T, B, and N in world coordinates. T, B, and N are normalized because we have DEFINED them that way. This makes the transformation matrix between the two coordinate systems very simple: it is simply a rigid rotation matrix without any unwanted "stretching".

Where is the origin of the tangent space? That's a great question, and the answer is that it hasn't been chosen yet! Since T, B, and N are perfectly valid at ANY (interior) point of the triangle, you could in principle choose the origin to be any point on the triangle.

The matrix (call it M for short)

[Tx Ty Tz]
[Bx By Bz]
[Nx Ny Nz]

converts RELATIVE vectors in the tangent space into RELATIVE vectors in world space.

Regardless of where you pick your origin on the triangle, its coordinates in tangent space will be by definition (0,0,0).

M maps this to M * (0,0,0)' = (0,0,0)', which is obviously NOT the world coordinates of your tangent space origin. [The primes denote transposes from row vector to column vector, just so it's easier to type.]

You need one more piece: the WORLD coordinates of your tangent space origin, call them (x0,y0,z0). Then the full mapping from tangent space to world space is

(x,y,z)' = M*(u,v,w)' + (x0,y0,z0)'

which we call an affine transformation (it moves the origin to the right place).

Finally, to "undo" this mapping or to go from world space to tangent space, just solve for (u,v,w)' above:

M*(u,v,w)' = (x,y,z)' - (x0,y0,z0)'

and since M is an orthonormal matrix, it not only has an inverse but its inverse is also simply its transpose:

(u,v,w)' = M' * ((x,y,z)' - (x0,y0,z0)')

[Edited by - Bungo on March 21, 2009 10:09:44 AM]
bzroom
bzroom
Bungo got pretty deep into it so i'm just going to pitch my $0.02 in there.

The TBN matrix you've illustrated is the inverse, so it will map from TBN space to world space rather than world space to TBN space.

Also, that's a 3x3 matrix, so it only transforms vectors, not points. That might make things a bit easier to understand.

Other than that I must admit, I take tangent vector calculation for granted since 3ds max generates them for me.
StasB
StasB
@Bungo: Thank you very much for your explanation! The way you explain tangent-space actually makes sense. The only problem now is that I don't really see how it relates to bump-mapping. O_o
With bump-mapping (and texture-mapping in general), we have a triangle in world-space and its counterpart in another space, where it could be not only rotated and translated, but also scaled and skewed. If the world-space triangle is scaled and skewed in another space, i.e. the both the lengths and ratios between the triangle sides change, then I absolutely can't imagine how it could possibly be that the axes of the other space would be orthonormal in world space.
This brings me to another point: If the triangle is simply translated and rotated in tangent-space and we talk about relative coordinates, or vectors, then like you say, there's an infinite amount of basis vectors for the tangent-space and they can be derived using only the world coordinates. But in my case, I suspect that there's only so many basis vectors that would transform my world-space coordinates into the given tangent-space coordinates, or am I talking nonsense again? Perhaps I simply misuse the term "tangent-space" and refer to something different?
Bungo
Bungo
OK, so let's say you start with a triangle in one world space. For brevity I'm going to say that this world space uses the "W1" coordinate system. Further, let's say you have specified a set of orthonormal basis vectors for the tangent space of the triangle, expressed in W1 coordinates. Let's call these vectors T1, B1, and N1.

Now you take that triangle and map it to some new world space, say with the "W2" coordinate system. As before, there is a linear (3x3 matrix) component plus a translation to move the origin. Let's use A to refer to just the matrix part.

It's quite true that if you map T1, B1, and N1 through the same transformation, obtaining A(T1), A(B1), and A(N1), they will no longer (in general) be orthonormal, because the if the triangle's shape has changed, then effectively the plane in which the triangle lives has been "stretched" differently in each of its two dimensions. Therefore while T1 and B1 formed a nice right angle, A(T1) and A(B1) don't necessarily do so. They also are not necessarily unit vectors anymore.

However, A(T1) and A(B1) do live in the same plane as the new triangle, and unless the mapping was degenerate (collapsing one or more of the triangle's edges to zero length), they will be linearly independent vectors. (i.e., at nonzero angle to one another). Therefore their cross product will point in the direction normal to the surface.

You can therefore construct an orthonormal basis for the new tangent space the same way you built the basis for the original tangent space:

N2 = normalize(A(T1) cross A(B1))
T2 = normalize(A(T1))
B2 = N2 cross T2

As for your second point: "If the triangle is simply translated and rotated in tangent-space and we talk about relative coordinates, or vectors, then like you say, there's an infinite amount of basis vectors for the tangent-space and they can be derived using only the world coordinates. But in my case, I suspect that there's only so many basis vectors that would transform my world-space coordinates into the given tangent-space coordinates, or am I talking nonsense again? Perhaps I simply misuse the term "tangent-space" and refer to something different?"

The tangent SPACE exists independently of which coordinates you use to represent it. It is simply the usual 3-dimensional Euclidean space with the additional constraint that the first two coordinates are understood to lie in the plane containing the triangle.

Within that constraint, you're free to choose any two orthonormal vectors that lie in the plane, along with a third one that is orthonormal to the first two and points either "upward" or "downward" depending on which direction you want your surface normal to be, and whether you want a right-hand coordinate system or left-hand.

If on the other hand, you already HAVE "given tangent-space coordinates," then this IMPLIES that you have a set of basis vectors for the tangent space. You can't talk about coordinates unless you have underlying basis vectors! The coordinates simply tell you how far to move along each of the basis vectors.

So I'm not sure if you're talking nonsense, but perhaps you can clarify exactly what you're starting with and what you want to achieve?
StasB
StasB
As my original post says:

I have three points. Their coordinates in 3D space A are (x0, y0, z0), (x1, y1, z1), (x2, y2, z2). Their coordinates in 3D space B are (u0, v0, 0), (u1, v1, 0), (u2, v2, 0). That's what I start out with. Now I'm given a fourth point. Its coordinates in 3D space A are (x3, y3, z3). Where is this point located in 3D space B?

[EDIT]

Here's the best I could come up with:

a*(x1 - x0) + b*(y1 - y0) + c*(z1 - z0) = u1 - u0
a*(x2 - x1) + b*(y2 - y1) + c*(z2 - z1) = u2 - u1
a*(x0 - x2) + b*(y0 - y2) + c*(z0 - z2) = u0 - u2

The purpose of this system was to find out how changes in x, y, z affect changes in u. If I could solve for a, b and c, I could then do something like:

a*(xp - x0) + b*(yp - y0) + c*(zp - z0) = up - u0

For some arbitrary point p, to find its u coordinate.
A similar system could be constructed for v etc.
This simply doesn't work, though. Any idea why? :/
It looks solvable, but the solution causes division by zero for any points I try.

[Edited by - StasB on March 21, 2009 11:59:50 AM]
Bungo
Bungo
Quote:
Original post by StasB
As my original post says:

I have three points. Their coordinates in 3D space A are (x0, y0, z0), (x1, y1, z1), (x2, y2, z2). Their coordinates in 3D space B are (u0, v0, 0), (u1, v1, 0), (u2, v2, 0). That's what I start out with. Now I'm given a fourth point. Its coordinates in 3D space A are (x3, y3, z3). Where is this point located in 3D space B?


OK, there are actually two separate issues to address. First, why did your attempt fail, and second, if you can make it work, is it enough to answer "where does the fourth point map to?"

To make things a little simpler let's introduce some vector notation:

p0 = (x0, y0, z0)'
p1 = (x1, y1, z1)'
p2 = (x2, y2, z2)'
p3 = (x3, y3, z3)'

q0 = (u0, v0, 0)'
q1 = (u1, v1, 0)'
q2 = (u2, v2, 0)'
q3 = ?

Let's further denote by T the transformation that maps as follows:

T(pn) = qn (for n = 0, 1, 2, 3)

And we'll postulate that T is of the form of a matrix multiplication plus an offset to move the origin:

T(p) = M*p + q_origin

where p is any vector in space A, and q_origin is the point in space B to which T should map space A's origin (0,0,0).

To fully specify T, you need to know the elements of the 3x3 matrix M, plus the coordinates of the point q_origin. Thus you need to specify 9 + 3 = 12 numbers.

You have specified how T should map three vectors in A. This means you have given enough information to specify 9 of the 12 numbers (assuming p0,p1,p2 are linearly independent). You need to specify what T should do to a fourth vector in order to completely define T! You could answer this, for example, by specifying where A's origin (0,0,0) should be mapped to in B. (i.e., specify q_origin).

Now, what went wrong with your attempted solution? First, you only provided three equations, which is enough to specify the M part of T, but not the q_origin part. Second, the particular vectors you chose aren't enough even to specify M, and I'll explain now why that is the case.

I'll use my vector notation from above to keep the equations simpler. You are trying to find a matrix M that satisfies:

M(p1 - p0) = q1 - q0
M(p2 - p1) = q2 - q1
M(p0 - p2) = q0 - q2

In general terms, you are trying to find M to satisfy:

M(a0) = b0
M(a1) = b1
M(a2) = b2

There are three possible outcomes for a system of linear equations:

1) exactly one solution
2) infinitely many possible solutions
3) no solution

Possibility (1) occurs if {a0,a1,a2} are linearly independent AND {b0,b1,b2} are linearly independent. In this case, M is an invertible matrix ("has full rank") which means that given ANY b in space B, there is exactly one a in space A such that M(a) = b: namely, a = inv(M)*b.

One way for possibility (2) [the underdetermined case] to occur is if {a0,a1,a2} spans a subspace of smaller dimension than {b0,b1,b2} spans. This can happen, for example, if the {a0,a1,a2} are linearly dependent but {b0,b1,b2} are linearly independent. For example, in the most degenerate case if you picked a0, a1, a2 to all be the same vector (they all point in the same direction), then of course that's not enough information to completely determine M: there are infinitely many choices. The same thing occurs if a0, a1, a2 all lie in the same PLANE.

Possibility (3) [the inconsistent case] can happen if you specify more equations than unknowns, and/or the equations are inconsistent, e.g., if they try to map the same input vector to two different output vectors.

Your problem is possibility (2). Your three vectors all lie in the same plane because you "connected the dots" between each pair of vertices of the triangle. Therefore they only span a subspace of dimension 2.

Instead, try this:

M(p1 - p0) = q1 - q0
M(p2 - p1) = q2 - q1

as above. Now you want a third vector that is NOT in the same plane as (p1-p0) and (p2-p1). One way to do this is to find a vector that is PERPENDICULAR to both of them (normal to the triangle's surface). You can do this using the cross product:

px = (p1 - p0) cross (p2 - p1).

You would presumably want to map this to the analogous surface normal vector in space B, which is

qx = (q1 - q0) cross (q2 - q1).

Then your third equation would be

M(px) = qx

If you do this, then you will be able to solve for all the coefficients of M.

You still won't be able to answer "where does a fourth point (x3,y3,z3) map to?" because specifying M is only enough information to answer where do (relative) vectors map to (i.e., differences between points). You still have an additional degree of freedom left to specify: e.g., where does the origin of space A map to in space B?

[Edited by - Bungo on March 21, 2009 1:18:58 PM]
StasB
StasB
Oh! I get it now! Using the normals indeed fixed it. You're a genius. :P
Thanks a lot for all your help!

Did a quick check and the following code a appears to work.
I tested it by generating random (valid) world-space triangles and corresponding tangent-space triangles, then I generated some random barycentric coordinates to find a point in the world-space triangle and the corresponding point in tangent-space. Then I compared these to the output of my transformation inside a loop, which would notify me every time there's a serious difference between the two results. I let it run for a few minutes but it never found any differences.

It's kind of nasty, though... Doing some hundred multiplications per triangle.

void get_axes(VEC3F wp1, VEC3F wp2, VEC3F wp3, VEC3F tp1, VEC3F tp2, VEC3F tp3, VEC3F *A, VEC3F *B, VEC3F *C){ VEC3F wd1, wd2, wd3, td1, td2, td3; VEC3F wn = vec3f_normal(wp1, wp2, wp3), tn = vec3f_normal(tp1, tp2, tp3); float k; wd1 = VEC3F_DIFF(wp1, wp2); wd2 = VEC3F_DIFF(wp2, wp3); wd3 = VEC3F_DIFF(wp3, wn); td1 = VEC3F_DIFF(tp1, tp2); td2 = VEC3F_DIFF(tp2, tp3); td3 = VEC3F_DIFF(tp3, tn); k = 1.0 / (wd1.x * (wd2.y * wd3.z - wd3.y * wd2.z) -            wd2.x * (wd1.y * wd3.z - wd3.y * wd1.z) -            wd3.x * (wd2.y * wd1.z - wd1.y * wd2.z)); A->x =  (wd1.y * (wd2.z * td3.x - wd3.z * td2.x) +          wd1.z * (wd3.y * td2.x - wd2.y * td3.x) +          td1.x * (wd2.y * wd3.z - wd3.y * wd2.z)) * k; A->y = -(wd2.x * (wd3.z * td1.x - wd1.z * td3.x) +          wd2.z * (wd1.x * td3.x - wd3.x * td1.x) +          td2.x * (wd1.z * wd3.x - wd1.x * wd3.z)) * k; A->z =  (wd3.y * (wd2.x * td1.x - wd1.x * td2.x) +          wd2.y * (wd1.x * td3.x - wd3.x * td1.x) -          wd1.y * (wd2.x * td3.x - wd3.x * td2.x)) * k; B->x =  (wd1.y * (wd2.z * td3.y - wd3.z * td2.y) +          wd1.z * (wd3.y * td2.y - wd2.y * td3.y) +          td1.y * (wd2.y * wd3.z - wd3.y * wd2.z)) * k; B->y = -(wd2.x * (wd3.z * td1.y - wd1.z * td3.y) +          wd2.z * (wd1.x * td3.y - wd3.x * td1.y) +          td2.y * (wd1.z * wd3.x - wd1.x * wd3.z)) * k; B->z =  (wd3.y * (wd2.x * td1.y - wd1.x * td2.y) +          wd2.y * (wd1.x * td3.y - wd3.x * td1.y) -          wd1.y * (wd2.x * td3.y - wd3.x * td2.y)) * k; C->x =  (wd1.y * (wd2.z * td3.z - wd3.z * td2.z) +          wd1.z * (wd3.y * td2.z - wd2.y * td3.z) +          td1.z * (wd2.y * wd3.z - wd3.y * wd2.z)) * k; C->y = -(wd2.x * (wd3.z * td1.z - wd1.z * td3.z) +          wd2.z * (wd1.x * td3.z - wd3.x * td1.z) +          td2.z * (wd1.z * wd3.x - wd1.x * wd3.z)) * k; C->z =  (wd3.y * (wd2.x * td1.z - wd1.x * td2.z) +          wd2.y * (wd1.x * td3.z - wd3.x * td1.z) -          wd1.y * (wd2.x * td3.z - wd3.x * td2.z)) * k;}void transform_to_space(VEC3F A, VEC3F B, VEC3F C, VEC3F wp1, VEC3F tp1, VEC3F wp, VEC3F *tp){ VEC3F v = VEC3F_DIFF(wp, wp1); tp->x = tp1.x + VEC3F_DOT_PRODUCT(v, A); tp->y = tp1.y + VEC3F_DOT_PRODUCT(v, B); tp->z = tp1.z + VEC3F_DOT_PRODUCT(v, C);}

Topic Locked

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

Sign in to reply to this topic.