Finding the tangent vector

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 ))

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

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]

tmx[0]=1
tmx[1]=0
tmx[2]=0
tmx[3]=0

tmx[4]=0
tmx[5]=-1
tmx[6]=0
tmx[7]=0

tmx[8]=0
tmx[9]=0
tmx[10]=1
tmx[11]=0

tmx[12]=0
tmx[13]=0
tmx[14]=0
tmx[15]=1

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)
lpes[0]=lx
lpes[1]=ly
lpes[2]=lz
Else
TFormPoint lx,ly,lz,sys\cam,0
lpEs[0]=TFormedX()
lpes[1]=TFormedY()
lpes[2]=TFormedZ()
EndIf

vecMatMult(lpES, modelViewMatrixInverse, lpOS);  

For b.bump=Each bump
sc=CountSurfaces( b\mesh)
For s=1 To sc
srf=GetSurface( b\mesh,s)
vc=CountVertices(srf)
For v=0 To vc-1
vx#=VertexX(srf,v)
vy#=VertexY(srf,v)
vz#=VertexZ(srf,v)

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

        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);
  ;glColor3fv(lv_ts);

rd#=128

VertexColor srf,v,rd+rdlv_ts[0],rd+rdlv_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)
  		;nx=255-tl
  		;ny=255-tl
  		;nz=255-tl
  		;If nx<0 nx=0
  		;If ny<0 ny=0
  		;If nz<0 nz=0
  		;Else
  		;	nx=tl
  		;	ny=tl
  		;	nz=tl
  		;	If nx>255 nx=255
  		;	If ny>255 ny=255
  		;	If nz>255 nz=255
  		;EndIf
  				
  	;	VertexColor srf,v,nx,ny,nz
  	Next
  Next

Next

I feel your pain :slight_smile:
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.
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dndrive/html/directx11192001.asp

Still lost on me 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

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.

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.

void TangentBasis( const VECTOR &vertex, const VECTOR &vertex2, const VECTOR &vertex3,
const float texcoords, const float texcoords2, const float texcoords3,
const VECTOR &polynormal, VECTOR &tangent, VECTOR &binormal, VECTOR &normal )
{
VECTOR txb;
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.

-SirKnight

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

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.

Thanks guys

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

Checking that tutorial out now.

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.

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:

http://www.terathon.com/books/mathgames2.html

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& v1 = vertex[i1];
  const Point3D& v2 = vertex[i2];
  const Point3D& v3 = vertex[i3];

  const Point2D& w1 = texcoord[i1];
  const Point2D& w2 = texcoord[i2];
  const Point2D& 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;

  triangle++;

}

for (long a = 0; a < vertexCount; a++)
{
const Vector3D& n = normal[a];
const Vector3D& 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).]

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.

BTW, thanks for sharing that code. I’ll be sure to scrap my old tangent space making function for this one.

-SirKnight

I second that. At first I “fought” 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!!

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?

-SirKnight

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?

Y.

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
else
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?)

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.

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 )

SeskaPeel.

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.)

@SeksaPeel:

You are right, just a damn’ typing error. I hate notebook keyboards

@Eric:

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.
Thanks for the detailed explanation.

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

The One True ™ notation is to use * for cross product, and / for dot product.

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

/me ducks