Finding correct tangent vectors

Hi all!

I have a problem to calculate the correct tangent vector. The result is looking very odd, please have look at the image where i have used the tangent vector as the color channel. You will notice the sharp vertical ploygon edges.

Im using the following code to generate the tangents:

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;

Has anyone an idea whats wrong? :confused:

Thank you in advance!
Greetings Mario

You don’t appear to set your normal so it’s pretty tricky to figure out what’s up.

Basically your indices or values of that term are set incorrectly. I don’t think it’s glShademodel because there’s some indication of smooth shading within the image.

n % t

What’s that, operator overloading?

Heck just a transpose & sign switch would create your tangent vector from your coordinate and you normal would equal your coordinate too (relative to teh sphere origin) but you haven’t set things up this way. You have a sphere here, you can exploit that to keep your math really simple.

Keep all the indices the same, set normals to equal coords (-sphere position) and normalize if non unit. Then the tangent vector is a 90 degree rotation about the horizontal plane. That makes teh rotation trivial. tangent.x=-normal.y tangent.y=normal.x but that’s just for xy = horizontal, you may have set things up where z is your horizontal plane.

Either way you have an issue with that parameter in the vertices or it’s index matching for verts on both sides of the primitive.

if you just want to do a sphere and are using a cylindrical mapping (most common case), then all you need to do is take the normal and build an orthonormalized matrix with the axis of your sphere being the ‘up’ vector.

the rotational component vectors of the matrix will be your u and v tangents and normal respectibly.

if you want to build tangents based on your texture coordinates, you can probably use the same approach, you will just need to choose your ‘up’ vector judiciously based on the texture coordinates projected into the normals local space.


to be honest though, i could be wrong, but i believe you can only get good results out of this if your texture map is axis aligned at every triangle. that generally requires disconnecting all of your triangles in your texture map and realigning them, them being forced to render your geometry as unconnected triangles. i believe this technique is used for deformable models where tangent space is desired to be reconstructed from texture coordinates somehow rather than being transformed.


briefly, to build an orthonormalize matrix:

U = UPxN (cross product)

be sure up and n are normalized and then normalize u.

V = NxU (don’t forget to normalize V)

you can put UV&N together to form a rotation matrix.

U and V are your tangents, and N is your normal.

Your picture is correct, tangent space is always discontinuous.

The reason is because tangent space is a derivative of the piecewise linear texture mapping function, for which the derivative (the speed of du/dx etc) is constant across a triangle then jumps to the du/dx of the next triangle.

It is not due to the normals, which may be smooth, the tangent space is still discontinuous.

When you look at your picture, you will notice that there are small color gradients across triangles. These come from the smooth normals. The hard edges come from the discontinuity of the derivative.

i get the same results from generating tangentmatrices from a sphere (though mine are smoothshaded)
is there any idea on how to fix these issues (not the flat shading) but the fact that the colors all run into the point at the top of the sphere, my normals are correct btw

Yup my bad, the derivatives of s & t aren’t being averaged here (my bad I never grok’d what was going on).

its true that the tangent is the derivite, of what is literally in this case a linear surface… but in reality it appears that the aproximate a true sphere is in order in this case, which is at least a 3rd order surface assuming you are only interested in geometry, in which case linearly smooth tangents would better aproximate a 3rd order smooth sphere. if a linear sphere was desirable, why use smooth/vertex normals?

doesn’t the tangent matrix need to be affine in order to accurately transform the light or whatever into its local space? so it would reason that the normals and tangent vectors should be as orthogonal as possible right?

by the way, i’m fairly curious about this bit of code:

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

what is the use of using the w component? shader specific i imagine. i’m just curious how it is being used. is it for reconstructing the ‘binormal’ with a specific sign? i’m curious if it wouldn’t necesarrilly be a good idea to always keep tangent vector and normal axies coordinate systems conformant. ie. ‘right/left handed’ coordinate systems.

i’ve personally never tried to construct tangent vectors from an arbitrary triangle mesh and texture mapping. i’m pretty curious about the best way to go about it myself.

BTW, the above code can be found here:

http://www.terathon.com/code/tangent.html

The % operator performs a cross product, and the * operator (between two vectors) performs a dot product.

The w component is used for meshes that could have some mirroring in the texture mapping. It gives the direction of the bitangent after you take the cross product between the normal and tangent in the vertex program. It’s not necessary for geometry that is mapped so that the tangent space is always right-handed, but it doesn’t hurt.

– Eric Lengyel

Suppose there are 2 triangles, A and B. The texture image is mapped with an 1:1 aspect ratio on trianlge A, but with 1:1.5 aspect ratio on triangle B.

When lighting both triangles with a normal map, you want the same apparent light direction L on the surfaces of both triangles. But on triangle B, the texture image is stretched, so the lighting in the tangent space of triangle B must be done differently than for triangle A, to have the same apparent light direction when the texture images are projected on screen.

Since the aspect ratio of a texture image is always constant across a triangle, your tangent space needs to be discontinuous to reflect this. The same goes for rotation and shearing.