Problem with Tangent and Bitangent calculation

Started by
3 comments, last by Eric Lengyel 15 years, 9 months ago
Hello, I want to do some normal mapping with OpenGL and GLSL. To do this I need a way to calculate the tangent space base vectors. I do this by using the code posted on Computing tangent space basis vectors for an arbitrary mesh By the way, my application uses right-handed coordinates. I wrote a simple test program to test the output of the tangent calculation function and there is a problem: I pass a single triangle in the X/Y plane with normal (1,0,0)(faces to the viewer, OpenGL RH) to the function. My tangent is OK but my bitangent is (0,-1,0). I think it should be (0,1,0). What's the problem? I hope you can help me! Here is the source code of my test program and the output of it:

#include <stdio.h>
#include <string.h>
#include "Vector4D.h"


struct Triangle
{
    unsigned short  index[3];
};


void CalculateTangentArray(long vertexCount, const Point3D *vertex, const Vector3D *normal,
        const Point2D *texcoord, long triangleCount, const Triangle *triangle, Vector4D *tangent)
{
    Vector3D *tan1 = new Vector3D[vertexCount * 2];
    Vector3D *tan2 = tan1 + vertexCount;
    // ClearMemory(tan1, vertexCount * sizeof(Vector3D) * 2);		Not available
	memset(tan1, NULL, 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];
        
        // Gram-Schmidt orthogonalize
        tangent[a] = (t - n * Dot(n, t)).Normalize();
        
        // Calculate handedness
        tangent[a].w = (Dot(Cross(n, t), tan2[a]) < 0.0F) ? -1.0F : 1.0F;
    }
    
    delete[] tan1;
}


int main()
{
	Point3D vertices[3];
	Point2D texcoords[3];
	Vector3D normals[3];
	Vector4D tangents[3];
	Vector3D bitangents[3];
	struct Triangle Tri = { 0, 1, 2 };
	int count = 0;


	vertices[0].x = -0.5;	vertices[0].y = -0.5;	vertices[0].z = -2.0;	// left, down
	vertices[1].x = 0.5;	vertices[1].y = -0.5;	vertices[1].z = -2.0;	// right, down
	vertices[2].x = -0.5;	vertices[2].y = 0.5;	vertices[2].z = -2.0;	// left, up

	texcoords[0].x = 0.0;	texcoords[0].y = 0.0;							// left, down
	texcoords[1].x = 1.0;	texcoords[1].y = 0.0;							// right, down
	texcoords[2].x = 0.0;	texcoords[2].y = 1.0;							// left, up

	normals[1].x = 0.0;		normals[0].y = 0.0;		normals[0].z = 1.0;		// faces to the viewer
	normals[2].x = 0.0;		normals[1].y = 0.0;		normals[1].z = 1.0;		// faces to the viewer
	normals[2].x = 0.0;		normals[2].y = 0.0;		normals[2].z = 1.0;		// faces to the viewer


	CalculateTangentArray(3, vertices, normals, texcoords, 1, &Tri, tangents);

	for (count = 0; count < 3; count++)
	{
		// BiTangent = cross(tangent, normal) * tangent.w;
		bitangents[count] = (tangents[count] % normals[count]) * tangents[count].w;
	}


	for (count = 0; count < 3; count++)
	{
		printf("\n\nTriangle Index: %d\n", count);
		printf("-----------------\n");
		printf("Vertex:               X: %f  Y: %f  Z %f\n", vertices[count].x, vertices[count].y, vertices[count].z);
		printf("Texture Coordinate:   U: %f  V: %f\n", texcoords[count].x, texcoords[count].y);
		printf("Normal:               X: %f  Y: %f  Z: %f\n", normals[count].x, normals[count].y, normals[count].z);
		printf("Tangent:              X: %f  Y: %f  Z: %f    W: %f\n", tangents[count].x, tangents[count].y, tangents[count].z, tangents[count].w);
		printf("BiTangent:            X: %f  Y: %f  Z: %f\n", bitangents[count].x, bitangents[count].y, bitangents[count].z);
	}

	return 0;
}

Here is the output: Triangle Index: 0 ----------------- Vertex: X: -0.500000 Y: -0.500000 Z -2.000000 Texture Coordinate: U: 0.000000 V: 0.000000 Normal: X: 0.000000 Y: 0.000000 Z: 1.000000 Tangent: X: 1.000000 Y: 0.000000 Z: -0.000000 W: 1.000000 BiTangent: X: 0.000000 Y: -1.000000 Z: 0.000000 Triangle Index: 1 ----------------- Vertex: X: 0.500000 Y: -0.500000 Z -2.000000 Texture Coordinate: U: 1.000000 V: 0.000000 Normal: X: 0.000000 Y: 0.000000 Z: 1.000000 Tangent: X: 1.000000 Y: 0.000000 Z: 0.000000 W: 1.000000 BiTangent: X: 0.000000 Y: -1.000000 Z: 0.000000 Triangle Index: 2 ----------------- Vertex: X: -0.500000 Y: 0.500000 Z -2.000000 Texture Coordinate: U: 0.000000 V: 1.000000 Normal: X: 0.000000 Y: 0.000000 Z: 1.000000 Tangent: X: 1.000000 Y: 0.000000 Z: 0.000000 W: 1.000000 BiTangent: X: 0.000000 Y: -1.000000 Z: 0.000000
Advertisement
I calculate my Bitangents with that same code,

bitangents[a] = Cross(n, tangents[a]);
// Calculate handedness
bitangents[a] *=(Dot(Cross(n, t), tan2[a]) < 0.0F) ? -1.0F : 1.0F;

otherwise you need to do it in the VS and use the tangent.w component to flip the bitangent. IIRC
This line is the problem:

bitangents[count] = (tangents[count] % normals[count]) * tangents[count].w;


Your cross product should be Cross(normal, tangent). By reversing the order of the operands, you've negated your bitangent.
Thank you Eric, you make my day!

Two more questions regarding your "very welcome" code:

1.
Handedness of the vertex data doesn't matter. As long as you multiply the bitangent by the w component of the tangent you get correct vectors which uses the handedness of the input vertex data.
Is this correct?

2.
I believe I read somewhere that a perfect aligned cube (a origin aligned cube) can cause troubles with this code? I think it has to do with divison by zero?
Are there any problems?

Thank you very much, you helped me alot!
Quote:Original post by DOT31.
Handedness of the vertex data doesn't matter. As long as you multiply the bitangent by the w component of the tangent you get correct vectors which uses the handedness of the input vertex data.
Is this correct?


Yes, that's correct. Multiplying by the w component of the tangent after computing N x T takes care of orienting the bitangent to reflect the handedness at a vertex.

Quote:Original post by DOT32.
I believe I read somewhere that a perfect aligned cube (a origin aligned cube) can cause troubles with this code? I think it has to do with divison by zero?
Are there any problems?


No, this is not a problem. The only way you can get a division by zero is if two vertices are in the exact same (x,y,z) position (which would be a bad mesh with degenerate triangles) or two vertices in a triangle have exactly the same (s,t) texture coordinates (which would be bad texture mapping).

Now there are other code snippets floating around out there that will react badly to perfectly aligned vertices or texture mapping (e.g., there are two vertices in a triangle that have exactly the same x coordinate, but differ in y and z), but those pieces of code are flawed. I amazed to see how much of that still exists even though I published the correct solution way back in 2001.

This topic is closed to new replies.

Advertisement