Tangent space calcualtion

I can’t figure out the correct Tangent, Binormal, and Normal. The code seems to work fine for cubes and planes but once a sphere or something more complex is used it goes wrong. It shows up as only half the triangles have textures and the other half is black, usually.

this is the code I’m using to get the tangent bais:
int TS_Basis(float v0[3], float v1[3], float v2[3], float uv0[2], float uv1[2], float uv2[2],
float tangentMatrix)
{
/

Tx Bx Nx *
Ty By Ny *
Tz Bz Nz *
* * * *
T are the tangent vectors
B are the binormals
N are the normals
* = the homogenous thingy
*/
float e0[3] = {0.0f, 0.0f, 0.0f};
float e1[3] = {0.0f, 0.0f, 0.0f};
float te0[2] = {0.0f, 0.0f};
float te1[2] = {0.0f, 0.0f};
float row0[3] = {0.0f, 0.0f, 0.0f};
float row1[3] = {0.0f, 0.0f, 0.0f};

//vector from vertex 0 to vertex 1
e0[0] = v0[0] - v1[0];
e0[1] = v0[1] - v1[1];
e0[2] = v0[2] - v1[2];
//vector from vertex 1 to vertex 2
e1[0] = v1[0] - v2[0];
e1[1] = v1[1] - v2[1];
e1[2] = v1[2] - v2[2];
//vector from UV 0 to UV 1
te0[0] = uv0[0] - uv1[0];
te0[1] = uv0[1] - uv1[1];
//vector from UV 1 to UV 2
te1[0] = uv1[0] - uv2[0];
te1[1] = uv1[1] - uv2[1];

//Calculates the normal for the face
float Normal[3];
crossprod(e0, e1, Normal);
vecNormalize(Normal);
tangentMatrix[2] = Normal[0];
tangentMatrix[6] = Normal[1];
tangentMatrix[10] = Normal[2];

//Calculate the Tangent vector
row0[0] = te0[0];
row0[1] = te0[1];
row0[2] = 1.0f;
row1[0] = te1[0];
row1[1] = te1[1];
row1[2] = 0.0f;

if(row0[0] == 0.0f)
{
	row0[0] += row1[0];
	row0[1] += row1[1];
	row0[2] += row1[2];
}
row0[2] /= row0[0];
row0[1] /= row0[0];
row0[0] /= row0[0];

row1[2] -= row0[2]*row1[0];
row1[1] -= row0[1]*row1[0];
row1[0] -= row0[0]*row1[0];

row1[2] /= row1[1];
row1[0] /= row1[1];
row1[1] /= row1[1];

row0[0] -= row1[0]*row0[1];
row0[2] -= row1[2]*row0[1];
row0[1] -= row1[1]*row0[1];


float Tangent[3];
Tangent[0] = row0[2]*e0[0] + row1[2]*e1[0];
Tangent[1] = row0[2]*e0[1] + row1[2]*e1[1];
Tangent[2] = row0[2]*e0[2] + row1[2]*e1[2];

vecNormalize(Tangent);
tangentMatrix[0] = Tangent[0];
tangentMatrix[4] = Tangent[1];
tangentMatrix[8] = Tangent[2];

//Calculate BiNormal from the Cross Product of the Normal and Tangent
float BiNormal[3];
crossprod(Normal, Tangent, BiNormal);

tangentMatrix[1] = BiNormal[0];
tangentMatrix[5] = BiNormal[1];
tangentMatrix[9] = BiNormal[2];
vecNormalize(BiNormal);
tangentMatrix[1] = BiNormal[0];
tangentMatrix[5] = BiNormal[1];
tangentMatrix[9] = BiNormal[2];

return 1;

}

not very efficent but I don’t see what’s wrong with it. Different sources seem to have slightly different ways of finding the tangent basis and almost all the examples on the web use planes or cubes.

This is the code that I use to calculate the tangent coords. It doesn’t output the binormal, because I use a cross product in a vertex shader to generate that, however dt_dx, dt_dy, and dt_dz is the binormal vector.

void CalculateTangents(int NumVertex, float *Vertex, float *UV, int NumFaces, unsigned short *Face, float *Tangent)
{
float v0[3], v1[3], cross[3], mag;
float ds_dx=0.0f, ds_dy=0.0f, ds_dz=0.0f;
float dt_dx=0.0f, dt_dy=0.0f, dt_dz=0.0f;
int i, j;

memset(Tangent, 0, NumVertex3sizeof(float));

for(i=0;i<NumFaces;i++)
{
v0[0]=Vertex[3Face[3i+1]]-Vertex[3Face[3i]];
v0[1]=UV[2Face[3i+1]]-UV[2Face[3i]];
v0[2]=UV[2Face[3i+1]+1]-UV[2Face[3i]+1];

  v1[0]=Vertex[3*Face[3*i+2]]-Vertex[3*Face[3*i]];
  v1[1]=UV[2*Face[3*i+2]]-UV[2*Face[3*i]];
  v1[2]=UV[2*Face[3*i+2]+1]-UV[2*Face[3*i]+1];

  cross[0]=(v0[1]*v1[2]-v0[2]*v1[1]);
  cross[1]=(v0[2]*v1[0]-v0[0]*v1[2]);
  cross[2]=(v0[0]*v1[1]-v0[1]*v1[0]);

  if(fabs(cross[0])>0.000001f)
  	ds_dx=-cross[1]/cross[0];

  if(fabs(cross[0])>0.000001f)
  	dt_dx=-cross[2]/cross[0];

  v0[0]=Vertex[3*Face[3*i+1]+1]-Vertex[3*Face[3*i]+1];
  v0[1]=UV[2*Face[3*i+1]]-UV[2*Face[3*i]];
  v0[2]=UV[2*Face[3*i+1]+1]-UV[2*Face[3*i]+1];

  v1[0]=Vertex[3*Face[3*i+2]+1]-Vertex[3*Face[3*i]+1];
  v1[1]=UV[2*Face[3*i+2]]-UV[2*Face[3*i]];
  v1[2]=UV[2*Face[3*i+2]+1]-UV[2*Face[3*i]+1];

  cross[0]=(v0[1]*v1[2]-v0[2]*v1[1]);
  cross[1]=(v0[2]*v1[0]-v0[0]*v1[2]);
  cross[2]=(v0[0]*v1[1]-v0[1]*v1[0]);

  if(fabs(cross[0])>0.000001f)
  	ds_dy=-cross[1]/cross[0];

  if(fabs(cross[0])>0.000001f)
  	dt_dy=-cross[2]/cross[0];

  v0[0]=Vertex[3*Face[3*i+1]+2]-Vertex[3*Face[3*i]+2];
  v0[1]=UV[2*Face[3*i+1]]-UV[2*Face[3*i]];
  v0[2]=UV[2*Face[3*i+1]+1]-UV[2*Face[3*i]+1];

  v1[0]=Vertex[3*Face[3*i+2]+2]-Vertex[3*Face[3*i]+2];
  v1[1]=UV[2*Face[3*i+2]]-UV[2*Face[3*i]];
  v1[2]=UV[2*Face[3*i+2]+1]-UV[2*Face[3*i]+1];

  cross[0]=(v0[1]*v1[2]-v0[2]*v1[1]);
  cross[1]=(v0[2]*v1[0]-v0[0]*v1[2]);
  cross[2]=(v0[0]*v1[1]-v0[1]*v1[0]);

  if(fabs(cross[0])>0.000001f)
  	ds_dz=-cross[1]/cross[0];

  if(fabs(cross[0])>0.000001f)
  	dt_dz=-cross[2]/cross[0];

  for(j=0;j<3;j++)
  {
  	Tangent[4*Face[3*i+j]]+=ds_dx;
  	Tangent[4*Face[3*i+j]+1]+=ds_dy;
  	Tangent[4*Face[3*i+j]+2]+=ds_dz;
  	Tangent[4*Face[3*i+j]+3]=1.0f;
  }

}

for(i=0;i<NumVertex;i++)
{
mag=1/(float)sqrt((Tangent[4i]Tangent[4i])+(Tangent[4i+1]Tangent[4i+1])+(Tangent[4i+2]Tangent[4i+2]));
Tangent[4
i]=mag;
Tangent[4
i+1]=mag;
Tangent[4
i+2]*=mag;
}
}

NitroGL I have tried out the code from the sample program on your website and ran into similar problems, it works fine for planes and cubes. I couldn’t try it with a sphere because milkshape refuses to export to md2. I’ll check out your new code, maybe it’ll solve my problems.

Incorporateing the
Tangent[4Face[3i+j]]+=ds_dx;
helped out a lot but I’m still getting seams where the textures don’t match well and it looks weird.

Also the line:
cross[1]=(v0[2]*v1[0]-v0[0]*v1[2]);
appears to be incorrect. Cross product for the j component should be (-1)(v0[2]*v1[0]-v0[0]*v1[2])
thanks

[This message has been edited by BillyBOb (edited 05-31-2002).]

[This message has been edited by BillyBOb (edited 05-31-2002).]

Hi,

I think that I maybe can help you, since my current research project includes finding the pricipal curvature directions of a given vertex from a surface approximation. What is confusing for me in your question is, that I dont understand which tangents you need.
As you know, every surface point (on a smooth surface) has a tangent space (a plane) consisting an infinite number of tangents.

So what I dont understand is which of them you need:

  1. to have the so called gauss trieder(not sure this is called so in english ) you need f(x,y)=z representation for which you need some parametrization for the cell of every vertex.
  2. to find the tangents in which the curvature has its min and max is another thing - it is not very hard, but they also form an orthogonal basis…
  3. to find just an (orthogonalal, orthonormal) basis in the tangent space is very easy…

So please tell me what you need more specific, and I may have the code for it…

Regards
Martin

martin_marinov, I think what they need are two orthogonal vectors in the tangent plane, which in some loose meaning “match” best the texture mapping (as expressed by the UV coordinates per vertex). In this way, hopefully the bump texture will match the color texture…

Here is my code for it. The basis is a routine that calculates the tangent vector which together with the given normal , form orthonormal basis to the tangent space at the vertex. This routine takes as input also the texture mapping coords , and this for ALL 3 VERTICES of the triangle. This is critical (the fact that it works on triangle level). Then you use this routine for all triangles to which your vertex belongs, and decide which of the results to average. Similar to ‘normal smoothing’.
(v0x,v0y,v0z) (v1x,v1y,v1z) (v2x,v1y,v1z) – the vertex positions
(s0x,s0y) (s1x,s1y) (s2x,s2y) – the vertex texture mappings
(n0x,n0y,n0z) – the normal at vertex 0
(X_x,X_y,X_z) – the output – the tangent vector.

Together with the given normal, you can derive the third vector by vector product. This should be done after averaging (and normalizing) (X_x,X_y,X_z) from contributions of all triangles to which the vertex belongs

S1x = s1x - s0x; S1y = s1y - s0y;
S2x = s2x - s0x; S2y = s2y - s0y;
V1x = v1x - v0x; V1y = v1y - v0y; V1z = v1z - v0z;
V2x = v2x - v0x; V2y = v2y - v0y; V2z = v2z - v0z;

X_x = -S2y * V1x + S1y * V2x;
X_y = -S2y * V1y + S1y * V2y;
X_z = -S2y * V1z + S1y * V2z;
g = sqrt(X_xX_x + X_yX_y + X_z*X_z);
X_x /=g ; X_y /=g ; X_z /= g;

g = X_xn0x + X_yn0y + X_z*n0z;
X_x -= g * n0x;
X_y -= g * n0y;
X_z -= g * n0z;

g = sqrt(X_xX_x + X_yX_y + X_z*X_z);
X_x /=g ; X_y /=g ; X_z /= g;

I think what they need are two orthogonal vectors in the tangent plane, which in some loose meaning “match” best the texture mapping (as expressed by the UV coordinates per vertex). In this way, hopefully the bump texture will match the color texture…

To be more precise, the “Tangent” and “Binormal” vectors are supposed to point in the directions of the U and V coordinates of the bump map. This allows the creation of an appropriate tangent-space transformation matrix to transform the light direction into tangent-space (or texture-space if you prefer, but tangent-space is more accurate).

The most important thing I want to note is that the tangent and binormal need not be orthogonal. With complex models and texture mapping, the U and V directions may not be orthogonal to one another. And, since the tangent and binormal vectors for the tangent-space computations are supposed to come from the U and V directions, they may not be orthogonal. This is very important for complex skinned objects, as the sheering on the textures can warp the tangent/binormal vectors significantly.

There was a lengthy discussion of bump mapping spaces on the GD-Algorithms list a few months ago. I believe the end result was that it’s probably better to bump map in object space, rather than tangent space. Then you tranform the light into object space before rendering.

This works even if you’re using deformable geometry, because you can skin the light using the inverse of the matrix you’d normally use to skin the normal. Thus, you send in a light vector per vertex instead of a normal vector per vertex – and the normal gets pulled from the normal map.

This turns out to be more efficient than tangent space bump mapping (less data to transfer) and a great deal easier to implement, as well. At least, so I’m told; I haven’t put it to code yet (seems straightforward enough, though!)

I have been wanting to do my bumpmapping and stuff in object space but how do i transform my normals from the bumpmap (normal map) to object space? Is there a texture shader op that will do this?

-SirKnight

Originally posted by SirKnight:
I have been wanting to do my bumpmapping and stuff in object space but how do i transform my normals from the bumpmap (normal map) to object space? Is there a texture shader op that will do this?

For less capable hardware this will require a unique normal map for all surfaces ( using deformable models will therefore not be practical ). If you have a GeForce3 or better you could transform your normals into the space you want. The only benefit I can see for doing that is for bumpy environment mapping and similar effects.

In any case, it’s not practical for ordinary bump mapping on current hardware ( you’ll be wasting texture stages just for the transformations with no room left for other things ).

Ah well i guess doing it in object space isn’t really as good as i thought. I’ll just keep doing it the ol tangent space way. If i can ever get it to work.

-SirKnight

This is all so confusing. It seems like different people have different ways of calculating the OrthoNormal basis.

martin_marinov: As far as I understand it, which is not a whole lot, I’m just looking for the tangent of the plane(since a triangle has to be a plane) along the positive uv direction?

Korval: doesn’t the matrix have to come out as orthogonal? The binormal is the tangent cross the normals. The tangent of a plane is certainly orthogonal to the normal of the plane. So if you cross those two you should also get another vector(binormal) orthogonal to the normal and the tangent.

jwatte: is there a website for the GD-Algorithms list?

thanks for all the comments and suggestions everyone.

SirNight,

You don’t transform your normal map into object space. You create your normal map in object space for the object in question in identity pose. Thus, as long as the object doesn’t rotate or articulate, you’re already done.

Now, if the object rotates, all you have to do is to un-rotate the light vector to transform the light vector into object space.

If the object articulates, you have to un-rotate the light vector based on how the object articulates, which means that you have to skin the light with the inverse of the object articulation joints. As long as you don’t allow scaling in your joints, that can actually be done by pulling data out of the same joint matrices (transposed) but if you do it in a shader, you might need to provide both joint matrix and its rotational inverse for each joint, eating a bit more constant register space.

Whether this means that you have to have one bump map per mesh or not is more up to the tenacity of your mapping artists than any inherent limitation (although it’s somewhat harder to share maps between dissimilar objects compared to tangent space maps).

Originally posted by BillyBOb:
This is all so confusing. It seems like different people have different ways of calculating the OrthoNormal basis.

No :-), this are just different basises in the tangent space…

martin_marinov: As far as I understand it, which is not a whole lot, I’m just looking for the tangent of the plane(since a triangle has to be a plane) along the positive uv direction?

the plane doesn’t have tangent space since it is not smooth enough. You may define tangent of the plane to be every vector coplanar with it, if you need it to have.
But for what I understood from the other people here, you dont need this. Just use some of the code above. I also may suggest you to read a book in differencial geometry, if you are interested in the topic.

Korval: doesn’t the matrix have to come out as orthogonal? The binormal is the tangent cross the normals. The tangent of a plane is certainly orthogonal to the normal of the plane. So if you cross those two you should also get another vector(binormal) orthogonal to the normal and the tangent.

What Korval means is that you dont need the second tangent( the binormal) to be orthogonal to the first. It is still orthogonal to the normal, since it is in the tangent plane. So the used tangent space basis will be not orthogonal… But you don’t need always to have a orthogonal basis, do you?

Regards
Martin

Remember that, in terms of tangent-space bump-mapping, you are transforming the light direction into the space of the texture at the given vertex. Texture coordinates don’t have to be orthogonal, so neither does the tangent-space matrix.