View Full Version : Finding the tangent vector

01-18-2004, 10:50 AM
This is driving my insane.

Every tutorial on the net appears to forget that it's a coding technique, and instead chooses to describe it in wierd symbols which are lost on me.

Basically, I have the model set up and rendering, mapped correctly(I.e normal map matching the texture) with dot3 etc.

And for each vertex I have,

the vector from vertex to light source,
light position.
Vertex Normal X,Y
Vertex U,V(Texture map coords)

Could someone please, just describe in code(C++/Basic, anything but 'The T is the derivite of the P over the magnitude of The l' I will shoot you. (j/k, it's just frustrating http://www.opengl.org/discussion_boards/ubb/wink.gif ))

I have the transforms set up already, for the tangent matrix,

so I really just need the code/help for the bit that fills/computes the tangent matrix.
On a 4x4 matrix,

i.e (This isn't correct)
tangnet(1,1) = vectoX*texU

The rest I have done, it's just this one bit.
I have read every tutorial and am now using my final life-line to ask you guys here. Any help will be rewarded with instant karma and riches beyond your wildest imagination. Or maybe just a thank you and a smile http://www.opengl.org/discussion_boards/ubb/wink.gif

01-18-2004, 10:55 AM
Here's the code as in, using Blitz3D, with a custom gl engine I wrote eventually but I just want to get it up and running before I make the switch(I.e so i know it's not a fault of my c++ code, which is much harder to debug)

The vector trasnforms etc are the ATI vector lib converted over.

tmx = the tanget matrix, this needs filling on a per vert basis I think.

Local tmx#[16]





Local lpES#[3]; // light position in Eye space
Local lpOS#[3]; // light position in Object space
Local lv#[3]; // vector from vertex to light
Local lv_ts#[3]; // light vector in tangent space
Local modelViewMatrix#[16];
Local modelViewMatrixInverse#[16]

fillMatrix( sys\cam,modelViewMatrix)
matrixInvert( modelViewMatrix,modelViewMatrixInverse)

If KeyDown(205)
TFormPoint lx,ly,lz,sys\cam,0

vecMatMult(lpES, modelViewMatrixInverse, lpOS);

For b.bump=Each bump
sc=CountSurfaces( b\mesh)
For s=1 To sc
srf=GetSurface( b\mesh,s)
For v=0 To vc-1

lv[0] = (lpOS[0] - vx);
lv[1] = (lpOS[1] - vy);
lv[2] = (lpOS[2] - vz);

vecMat3x3Mult(lv, tmx, lv_ts);

lv_ts[0] = lv_ts[0] * 0.5 + 0.5;
lv_ts[1] = lv_ts[1] * 0.5 + 0.5;
lv_ts[2] = lv_ts[2] * 0.5 + 0.5;

; glMultiTexCoord2f(GL_TEXTURE0_ARB, 0.0, 0.0);
; glMultiTexCoord2f(GL_TEXTURE1_ARB, 0.0, 0.0);

VertexColor srf,v,rd+rd*lv_ts[0],rd+rd*lv_ts[1],rd+rd*lv_ts[2]

; xd#=vx-lx
; yd#=vy-ly
; zd#=vz-lz
; DebugLog "vx>"+vx+" vy>"+vy+" vz>"+vz
; tl#=Sqr(xd*xd+yd*yd+zd*zd)
; xd=xd/tl
; yd=yd/tl
; zd=zd/tl

;DebugLog "xd>"+xd
;DebugLog "yd>"+yd
;DebugLog "zd>"+zd

; nx#=255+255*xd
; ny#=255+255*yd
; nz#=255+255*zd

;If KeyDown(203)
;If nx<0 nx=0
;If ny<0 ny=0
;If nz<0 nz=0
; nx=tl
; ny=tl
; nz=tl
; If nx>255 nx=255
; If ny>255 ny=255
; If nz>255 nz=255

; VertexColor srf,v,nx,ny,nz

01-18-2004, 11:42 AM
I feel your pain :)
Have a look at this page, don't stop reading because of the formulas on it, there's pseudo-code for it a bit further down.

01-18-2004, 12:06 PM
Still lost on me http://www.opengl.org/discussion_boards/ubb/smile.gif Thanks for the link though.

It used the dreaded 'Where $$ = the unit of'...and suddenly it made as much sense as a drunk sailor, collapsed on the floor mumbling something about moby dick.

Not a lot http://www.opengl.org/discussion_boards/ubb/wink.gif

So please guys, in return I'll be more than happy to help in other areas, like provide you with a rag-doll/hitman 2 type engine as a .dll (ultra fast, vertlet sys)

But failing bribary, I'm at your mercy. http://www.opengl.org/discussion_boards/ubb/wink.gif

01-18-2004, 12:35 PM
Here is a function I wrote a few years ago now that computes the tangent, binormal and normal to use with tangent space perpixel lighting/bumpmapping. The parameter list is rather nasty looking but it gets the job done. http://www.opengl.org/discussion_boards/ubb/biggrin.gif

void TangentBasis( const VECTOR &amp;vertex, const VECTOR &amp;vertex2, const VECTOR &amp;vertex3,
const float texcoords[], const float texcoords2[], const float texcoords3[],
const VECTOR &amp;polynormal, VECTOR &amp;tangent, VECTOR &amp;binormal, VECTOR &amp;normal )
VECTOR v1( vertex2.x - vertex.x, texcoords2[0] - texcoords[0], texcoords2[1] - texcoords[1] );
VECTOR v2( vertex3.x - vertex.x, texcoords3[0] - texcoords[0], texcoords3[1] - texcoords[1] );

txb = CrossProduct( v1, v2 );

if( fabs( txb.x ) > EPSILON )
tangent.x = -txb.y / txb.x;
binormal.x = -txb.z / txb.x;

v1.x = vertex2.y - vertex.y;
v2.x = vertex3.y - vertex.y;

txb = CrossProduct( v1, v2 );

if( fabs( txb.x ) > EPSILON )
tangent.y = -txb.y / txb.x;
binormal.y = -txb.z / txb.x;

v1.x = vertex2.z - vertex.z;
v2.x = vertex3.z - vertex.z;

txb = CrossProduct( v1, v2 );

if( fabs( txb.x ) > EPSILON )
tangent.z = -txb.y / txb.x;
binormal.z = -txb.z / txb.x;

Normalize( tangent );
Normalize( binormal );

// Make a normal based on the tangent and binormal b/c it may be different than the poly's
// normal, this normal being computed here is better
normal = CrossProduct( tangent, binormal );
Normalize( normal );

// Make tangent space vectors orthogonal by recomputing the binormal with the corrected
// tangent space normal.
binormal = CrossProduct( tangent, normal );
Normalize( binormal );

// Check the generated normal from the tangent and binormal vectors against the polygon normal
if( DotProduct( normal, polynormal ) < 0.0f )
normal.x = -normal.x;
normal.y = -normal.y;
normal.z = -normal.z;


This was based off a similar function I think Cass of nvidia wrote in one of those glh headers. I looked at the nvidia function to make the one I orginially wrote a bit faster.


[This message has been edited by SirKnight (edited 01-18-2004).]

01-18-2004, 12:45 PM
Already know this tutorial here : http://www.paulsprojects.net/tutorials/simplebump/simplebump.html ?
That's were I learned how to calculate the tangents, and even though I'm no math-guru I quickly figured out how to do it and moreover why it's the way it is.

01-18-2004, 12:55 PM
Thanks guys http://www.opengl.org/discussion_boards/ubb/smile.gif

SirKnight, Once it fills the tanget,bi-normal,and normal (The last three pars)
I assume I load these into the tangent matrix with which I transform the vector?

In what order though?

I mean,

row 1=
Row =2 =TAgnet vector
Row =3 Bi normal
row 4=?

But thanks for the function, should be a big help in coming to terms with my inability to grasp simple maths http://www.opengl.org/discussion_boards/ubb/wink.gif

Checking that tutorial out now.

01-18-2004, 01:11 PM
Great tutorial, it is so much more...english..than the rest.

Doesn't explain how to actually generate the tangents though unless I'm missing something, but the function sirKnight provided does that, so it's not a prob.

Thanks guys, your life savers.

Eric Lengyel
01-18-2004, 06:02 PM
Unfortunately, every online tutorial, presentation, and piece of sample code I've ever seen for calculating tangent vectors is just plain wrong. Please do not use the Nvidia method because it will not work correctly for all cases (and it's slow). If you really want to understand how to calculate tangents correctly for arbitrary geometry, please read Mathematics for 3D Game Programming and Computer Graphics, Section 6.8.3:


You will find that the method described there is very elegant. It's derivation is straightforward and doesn't make use of calculus. Here's the actual implementation that I use:

void CalculateTangentArray(long vertexCount, const Point3D *vertex, const Vector3D *normal, const Point2D *texcoord,
long triangleCount, const Triangle *triangle, Vector4D *tangent) const
Vector3D *tan1 = new Vector3D[vertexCount * 2];
Vector3D *tan2 = tan1 + vertexCount;
ClearMemory(tan1, vertexCount * sizeof(Vector3D) * 2);

for (long a = 0; a < triangleCount; a++)
long i1 = triangle->index[0];
long i2 = triangle->index[1];
long i3 = triangle->index[2];

const Point3D&amp; v1 = vertex[i1];
const Point3D&amp; v2 = vertex[i2];
const Point3D&amp; v3 = vertex[i3];

const Point2D&amp; w1 = texcoord[i1];
const Point2D&amp; w2 = texcoord[i2];
const Point2D&amp; w3 = texcoord[i3];

float x1 = v2.x - v1.x;
float x2 = v3.x - v1.x;
float y1 = v2.y - v1.y;
float y2 = v3.y - v1.y;
float z1 = v2.z - v1.z;
float z2 = v3.z - v1.z;

float s1 = w2.x - w1.x;
float s2 = w3.x - w1.x;
float t1 = w2.y - w1.y;
float t2 = w3.y - w1.y;

float r = 1.0F / (s1 * t2 - s2 * t1);
Vector3D sdir((t2 * x1 - t1 * x2) * r, (t2 * y1 - t1 * y2) * r, (t2 * z1 - t1 * z2) * r);
Vector3D tdir((s1 * x2 - s2 * x1) * r, (s1 * y2 - s2 * y1) * r, (s1 * z2 - s2 * z1) * r);

tan1[i1] += sdir;
tan1[i2] += sdir;
tan1[i3] += sdir;

tan2[i1] += tdir;
tan2[i2] += tdir;
tan2[i3] += tdir;


for (long a = 0; a < vertexCount; a++)
const Vector3D&amp; n = normal[a];
const Vector3D&amp; t = tan1[a];
tangent[a] = (t - n * (n * t)).Normalize();
tangent[a].w = (n % t * tan2[a] < 0.0F) ? -1.0F : 1.0F;

delete[] tan1;

As described in the book, the w-coordinate of the tangent represents the handedness of the basis set at each vertex. The normal direction is not modified, but a quick Gram-Schmidt step makes sure that the tangent and normal are orthogonal.

[This message has been edited by Eric Lengyel (edited 01-18-2004).]

01-18-2004, 06:39 PM
Please do not use the Nvidia method because it will not work correctly for all cases (and it's slow).

Hmm, I was not aware of this. I really need to get your book. http://www.opengl.org/discussion_boards/ubb/wink.gif

BTW, thanks for sharing that code. I'll be sure to scrap my old tangent space making function for this one. http://www.opengl.org/discussion_boards/ubb/biggrin.gif


01-28-2004, 03:55 PM
I second that. At first I "fought" http://www.opengl.org/discussion_boards/ubb/wink.gif through the NVMeshMender code to make it compatible. After some tweaking I got it to work, but some triangles had zero tangents and binormals.

After taking your code and adapting it, everything works fine!!

In one of the NV whitepapers they wrote about some issues like texture mirroring & co. Any ideas wether I should care about this with your code? I have indeed some mirrored texture coordinates....

Anyways, thanks a lot!!

01-28-2004, 05:58 PM
Omg that NVMeshMender thing was such a pain in the arse to get going. I spent so long hacking at it to not only compile wihtout a million errors, but to get the results I needed. I never really used it a whole lot, the last time I did I was just beginning into perpixel lighting and still wasn't 100% on what I was doing. I finally just used that old function I posted above for everything. Now that I have this new code thanks to Eric, which is a MUCH better solution to finding tangents for meshes, that old code is now useless to me.

ScottManDeath, you say you do have some mirrored texture coordinates, have you not tested the code to see if it handles them correctly?


01-29-2004, 12:14 AM
That's interesting, as everybody i've been fighting with the tangent space generation algorithm for quite some time now, tried 3 or 4 versions, all different, found on the web, and none working in 100% of the cases. I'll be sure to try that one when i'll have some time. Sounds quite simple compared to the others. But does it handle mirrored UVs?


01-29-2004, 02:20 AM
It handles also mirrored texture coordinates. I was not sure about this because at first I had to make a test model with mirrored texcoords.

As Eric wrote, the w coordinate of the tangent defines wether the tangent space is left or right handed. So I check

if w is -1.0, then
binormal = vertex_normal dot tangent
binormal = tangent dot vertex_normal

It seems to work, although I am not sure if it mathematically correct. (Maybe I should do, as suggested by Eric, a Gram Schmidt orthonormalization?)

01-29-2004, 03:32 AM
One thing I should add: I am using triangles, the converting to trianglestrips will be done later, so it could be then when I have to blend vertices and tangent spaces that mirrored texcoords make some problems when they zero out themeselves. But with I guess now that all tangent spaces are orientated proper, this should lead to no problem.

01-29-2004, 06:17 AM
ScottManDeath : maybe you meant cross rather than dot ?

I strongly recommend Eric's book, that is very short in explanations, but very clear. You have to read it slowly sevral times, but after, you completely understand everything on the topic. And this is true for almost all topics (Eric, now please send me your second edition for free http://www.opengl.org/discussion_boards/ubb/wink.gif)


Eric Lengyel
01-29-2004, 11:52 AM
For handling mirrored texture coordinates, the line

tangent[a].w = (n % t * tan2[a] < 0.0F) ? -1.0F : 1.0F;

(% is cross product, * is dot product) sets the w-coordinate of the tangent vector to 1 if B = N x T, and to -1 if B = -N x T. So, just send the 3D vector N and the 4D vector T to your vertex program and calculate

B = (N x T) * T.w,

ignoring the w-coordinates for the cross product.

ScottManDeath: The line

tangent[a] = (t - n * (n * t)).Normalize();

is the Gram-Schmidt step that makes sure the tangent is orthogonal to the normal. (The n * t is a dot product, and the n * () is a vector-scalar multiplication.)

01-30-2004, 06:58 AM

You are right, just a damn' typing error. I hate notebook keyboards http://www.opengl.org/discussion_boards/ubb/wink.gif


I know I could change my code because of the rules for the cross product. But as I calculate my binormals on the host, that doesn't matter. And I figured out the meaning of % and *; some time ago, one of my vector classes used also % for cross. http://www.opengl.org/discussion_boards/ubb/wink.gif
Thanks for the detailed explanation.

01-30-2004, 07:58 AM
nv_meshmender has needed a fix for a long time! We've finally done it. I want to thank Eric for his math which I'm using in the new version of nv_meshmender.

I've also modified the interface so it should be much easier to use now, and hopefully should handle all the corner cases.

I've been testing with a few developers over the last couple of weeks privately working kinks out, and will be posting it to the nvidia website very soon.

Eric, one thing I would have found helpful in your book when going over this again, would have been an explicit discussion on triangles that were degenerate, both in texture and in real space. And how to smooth them properly. I have the first edition though, maybe you've updated it in the second edition?

best regards,
-clint brewer

01-30-2004, 08:59 AM
The One True (tm) notation is to use * for cross product, and / for dot product.

Oh, and braces go on the line of the if().

/me ducks

page fault
01-31-2004, 01:06 AM
Eric, I just wanted to thank you for sharing that code. I've been working on getting per-pixel lighting with bumpmapping working on a quake 3 level for the past two weeks and was really stuck on computing the tangent basis. Seems everything I tried always ended up with zero vectors in some cases. Using your code as a reference I was able to get it working nicely. I'll be sure to check out your book.

Christian Schüler
02-02-2004, 09:58 AM
So you want us provide clean and neat code for you, eh? You are lucky, because I know partial derivatives and many other strange symbols that are not lost on me!

If the flipcode boards were online, I could provide a link to a post of mine where I described a similar approach to that of Eric. Since Eric has posted his code, just use his' -)

If you allow me to be a smart-ass, pay attention to triangles with smooth normals! Since you know that derivatives break the continuity of piecewise linear functions...

What this means is that even though your triangles may have smooth normals, the tangent-space will not!

Have fun!

02-02-2004, 01:53 PM
Originally posted by clint:
nv_meshmender has needed a fix for a long time! We've finally done it. I want to thank Eric for his math which I'm using in the new version of nv_meshmender.

I've also modified the interface so it should be much easier to use now, and hopefully should handle all the corner cases.

I've been testing with a few developers over the last couple of weeks privately working kinks out, and will be posting it to the nvidia website very soon.

Eric, one thing I would have found helpful in your book when going over this again, would have been an explicit discussion on triangles that were degenerate, both in texture and in real space. And how to smooth them properly. I have the first edition though, maybe you've updated it in the second edition?

best regards,
-clint brewer

I'm quite interested in a new and improved NVMeshMender. Tell us when the new verison is released, I'd like to give it a go. http://www.opengl.org/discussion_boards/ubb/smile.gif


04-02-2004, 06:10 AM
I just don't get it!
Computing the TS for the faces is a piece of cake, but then - computing the correct TS for the vertices...
NVMeshMender seems to tear vertices when two tangent spaces meet and they are quite 'different' (i.e. at least in the one axis dot( ts1.x, ts2.x ) < C). So, when mirrored textures meet in an edge, they'll get teared, and that is because single axis is just inverted.
The correct behaviur is to smooth them, but just invert one TS before smoothing, then invert back the correct smoothed TS, tear vertices and copy normal-smoothed TS into ones that belong to first face, and inverted-smoothed TS into the vertices of the second face.
And everywhere I look, there is just a sum, then orthogonalization...?!
Is it me, or just the meshes that this algorithms are run over?

04-05-2004, 01:16 PM
Well you have a discontinuity in the texture derivative if you mirror.

You're talking about averaging vectors for smooth shading where you have such a discontinuity and that is problematic.

The solution, if you know you have a mirrored edge is to reflect one of the vector sets then average in a unified reflected space then reflect it back for one of the mirrored versions. This will allow you to average vectors across the mirrored edge and produce a tangent space representation without discontinuities, provided your up vector is correctly represented in the bump map. Of course if you can't smoothly mirror the vector texture you're back to square one.

Christian Schüler
04-06-2004, 09:16 AM
Discontinuity of tanget spaces is not only an issue with mirroring.

If your UV-space is stretched and texels are not square (and they will be, however good your artists are), changes in texture gradient over a triangle border is always hard, never smooth. So better you make a threshold that if gradients change too abruptly, you duplicate the edge.

04-12-2004, 11:27 AM
Typically these can be averaged, when you mirror they cannot be.

04-16-2004, 03:02 AM
Originally posted by Eric Lengyel:
Unfortunately, every online tutorial, presentation, and piece of sample code I've ever seen for calculating tangent vectors is just plain wrong. Yes, and your method also is not perfect ;)

What wonders me most of all, is why everybody make different silent assumptions about tangent space and never verbalize it...

So, you posted a derivation of tangent/binormal vectors for the case of "tangent space -> world space" conversion. After solving a set of equations you orthonormalize your basis (throwing binormal vector completely, and if we have some shearing???). Okay, now we can quickly invert (read, transpose) this matrix to get "world space -> tangent space" conversion.

But if we build slightly another set of equations, we can derive formulas for "WS -> TS" conversion directly, preserving texture scaling and shearing.

Or I have missed something completely?

Best regards,

Christian Schüler
04-17-2004, 11:06 AM
Texture scaling and shearing UV-mappings is indeed a topic that is seldom touched in tangent-space literature.

I once built a system that was capable of correctly per-pixel-lighting stretched (but not sheared) texture mappings, however, the calculation was done in the CPU and the transformed light vector was sent to the card as DIFFUSE_COLOR :p