DOT3 Bump Mapping advices wanted :)

Hi,

I read through a few demos, that implement the simplest form of DOT3 Bump Mapping (only diffuse with N.L) via the GL_ARB_texture_env_combine and GL_ARB_texture_env_dot3 Extensions.
I tried to play arround with Matrix math, too.
I never really did a deep look into it (I´m ashamed for that g), because the things I did with OpenGL worked without big Matrix knowledge .

This is the code I´m currently using (big parts are based on the simple DOT3 ATI demo, but I do some things in another way):

// light position in object space
float lpOS[3] = {-20.0f, 0.0f, 10.0f};
// light vector
float lv[3];
// light vector in tangent space
float lv_ts[3];

float modelViewMatrix[16];

float tangentMatrix[16] =
{1.0f, 0.0f, 0.0f, 0.0f,
0.0f,-1.0f, 0.0f, 0.0f,
0.0f, 0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 0.0f, 1.0f};

while(bExitMainLoop == false)
{
glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);

glLoadIdentity();
glTranslatef(0.0f, 0.0f, -20.0f);
glRotatef(fRotate, 0.0f, 1.0f, 0.0f);

glPushAttrib(GL_TEXTURE_BIT);

// TEX UNIT 0
glActiveTextureARB(GL_TEXTURE0_ARB);
glBindTexture(GL_TEXTURE_2D, TEXTURES.Return_TextureIDs(4));
glEnable(GL_TEXTURE_2D);

glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_ARB);
glTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_DOT3_RGBA_ARB);

glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_TEXTURE);
glTexEnvf(GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR);

glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_PRIMARY_COLOR_ARB);
glTexEnvf(GL_TEXTURE_ENV, GL_OPERAND1_RGB_ARB, GL_SRC_COLOR);

// TEX UNIT 1
glActiveTextureARB(GL_TEXTURE1_ARB);
glBindTexture(GL_TEXTURE_2D, TEXTURES.Return_TextureIDs(2));
glEnable(GL_TEXTURE_2D);

glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_ARB);
glTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_MODULATE);

glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_PREVIOUS_ARB);
glTexEnvf(GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR);

glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_TEXTURE);
glTexEnvf(GL_TEXTURE_ENV, GL_OPERAND1_RGB_ARB, GL_SRC_COLOR);

glGetFloatv(GL_MODELVIEW_MATRIX, modelViewMatrix);

glBegin(GL_TRIANGLE_STRIP);

// compute L and store as vertex color
lv[0] = (lpOS[0] + 5.0f);
lv[1] = (lpOS[1] + 5.0f);
lv[2] = (lpOS[2] - 0.0f);
Vector_3D_Matrix_3x3_Multiply(lv, modelViewMatrix, lv);
Vector_3D_Normalize(lv);

// convert to tangent space
Vector_3D_Matrix_3x3_Multiply(lv, tangentMatrix, lv_ts);

// scale and bias
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;

glMultiTexCoord2fARB(GL_TEXTURE0_ARB, 0.0, 0.0);
glMultiTexCoord2fARB(GL_TEXTURE1_ARB, 0.0, 0.0);
glColor3fv(lv_ts);
glNormal3f(0.0, 0.0, 1.0);
glVertex3f(-5.0, -5.0, 0.0);

// compute L and store as vertex color
lv[0] = (lpOS[0] - 5.0f);
lv[1] = (lpOS[1] + 5.0f);
lv[2] = (lpOS[2] - 0.0f);
Vector_3D_Matrix_3x3_Multiply(lv, modelViewMatrix, lv);
Vector_3D_Normalize(lv);

// convert to tangent space
Vector_3D_Matrix_3x3_Multiply(lv, tangentMatrix, lv_ts);

//scale and bias
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;

glMultiTexCoord2fARB(GL_TEXTURE0_ARB, 1.0, 0.0);
glMultiTexCoord2fARB(GL_TEXTURE1_ARB, 1.0, 0.0);
glColor3fv(lv_ts);
glNormal3f(0.0, 0.0, 1.0);
glVertex3f(5.0, -5.0, 0.0);

// compute L and store as vertex color
lv[0] = (lpOS[0] + 5.0f);
lv[1] = (lpOS[1] - 5.0f);
lv[2] = (lpOS[2] - 0.0f);
Vector_3D_Matrix_3x3_Multiply(lv, modelViewMatrix, lv);
Vector_3D_Normalize(lv);

// convert to tangent space
Vector_3D_Matrix_3x3_Multiply(lv, tangentMatrix, lv_ts);

// scale and bias
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;

glMultiTexCoord2fARB(GL_TEXTURE0_ARB, 0.0, 1.0);
glMultiTexCoord2fARB(GL_TEXTURE1_ARB, 0.0, 1.0);
glColor3fv(lv_ts);
glNormal3f(0.0, 0.0, 1.0);
glVertex3f(-5.0, 5.0, 0.0);

// compute L and store as vertex color
lv[0] = (lpOS[0] - 5.0f);
lv[1] = (lpOS[1] - 5.0f);
lv[2] = (lpOS[2] - 0.0f);
Vector_3D_Matrix_3x3_Multiply(lv, modelViewMatrix, lv);
Vector_3D_Normalize(lv);

// convert to tangent space
Vector_3D_Matrix_3x3_Multiply(lv, tangentMatrix, lv_ts);

// scale and bias
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;

glMultiTexCoord2fARB(GL_TEXTURE0_ARB, 1.0, 1.0);
glMultiTexCoord2fARB(GL_TEXTURE1_ARB, 1.0, 1.0);
glColor3fv(lv_ts);
glNormal3f(0.0, 0.0, 1.0);
glVertex3f(5.0, 5.0, 0.0);

glEnd();
glPopAttrib();

fRotate += 0.05f;

SwapBuffers(GLINIT.Return_HDC());

GetGLError();
}

Now I´ve got a few question on my code and I really would like to get some feedback from the “professionals” .

One thing before I ask some “general” questions.

Am I calculating the light vector in the right way?

light_vector = (light_position_OS + vertex_position_OS) * current modelviel matrix

In the ATI demo it was:
light_vector[0] = light_position_OS + (-vertex_position_OS)
light_vector[1] = light_position_OS + (- vertex_position_OS)
light_vector[2] = light_position_OS + (- vertex_position_OS)
But there were some additional matrix calculations in, so that the Bump Mapping worked, if the bump mapped object rotated.
That didn´t work for me, because I left the additional matrix math out (didn´t understand everything).
And so I thought it should work, if I multiply ligth position + vertex position with the current modelviel matrix, after doing my object translations and rotations, but perhaps my math is wrong .

Here the general questions:

  1. Are there errors in the code (I assume the Matrix / Vector Math functions work correctly)?
  2. Do I really need a glNormal3f call or is it just a cosmetical thing?
  3. What could be optimized?
  4. Any other suggestions for me ?

Diapolo

[This message has been edited by Diapolo (edited 02-25-2002).]

I didn’t really find ATI’s simple DOT3 sample very much help at all (the other DOT3 samples are OK though).

Also, just to get my plug in, I have a DOT3 demo/tutorial (can’t really call it a tutorial though) on my site (http://nitrogl.cjb.net).

OK, last night + morning I read through many documents, that describe, how DOT3 Bump Mapping works.

I have to put a normalized light vector into the vertex color (compressed to [1,0] range) and then convert this into tangent space.
Tangent space is a local coordinate system, based on the vertex normal, the tangent to the surface (with help of TexCoords) and a Bi-normal.
I guess I have to multiply the vertex with a generated tangent space matrix, that is 3x3?

Please correct me, if I made an error in the above text.

But the question for me now, is how to implement all this stuff (seems quite a big math thing to do per vertex and every rendered frame, isn´t it?).

Things I have (I know to compute):

  • vertex position (object space / X, Y, Z)
  • light position (object space / X, Y, Z)
  • light vector (normalized)
  • vertex normal
  • current moddelview matrix (dunno if needed)
  • TexCoords per Vertex (S and T)
  • Bi-Normal (crossprod of normal and tangent)
  1. How do I get the tangent?
  2. How do I get the tangent matrix to work with?

Please help me … THANKS!

Diapolo

OK, I modified my code quite a bit, but I still have got the problem, on HOW to compute the tangent for a vertex … in my current function I have got a static (non moving / rotating) object and I only move the light source (so that I can use a static tangent matrix for now).

Perhaps one could look through the new code and help me with finding errors?

void MainLoop(void)
{
// Quad Vertices
float fQuad_XYZ[4][3] = {{-50.0f, -50.0f, 0.0f},
{ 50.0f, -50.0f, 0.0f},
{-50.0f, 50.0f, 0.0f},
{ 50.0f, 50.0f, 0.0f}};

// Quad Normals
float fQuad_Normal[4][3] = {{0.0f, 0.0f, 1.0f},
                            {0.0f, 0.0f, 1.0f},
                            {0.0f, 0.0f, 1.0f},
                            {0.0f, 0.0f, 1.0f}};
// Quad Tangents
float fQuad_Tangent[4][3] = {{1.0f, 0.0f, 0.0f},
                             {1.0f, 0.0f, 0.0f},
                             {1.0f, 0.0f, 0.0f},
                             {1.0f, 0.0f, 0.0f}};
// Quad Binormals
float fQuad_Binormal[4][3];
// calculate Binormals
Vector_3D_CrossProduct(fQuad_Normal[0], fQuad_Tangent[0], fQuad_Binormal[0]);
Vector_3D_CrossProduct(fQuad_Normal[1], fQuad_Tangent[1], fQuad_Binormal[1]);
Vector_3D_CrossProduct(fQuad_Normal[2], fQuad_Tangent[2], fQuad_Binormal[2]);
Vector_3D_CrossProduct(fQuad_Normal[3], fQuad_Tangent[3], fQuad_Binormal[3]);
// build static tangent Matrix
float tangentMatrix[16] = {fQuad_Tangent[0][0], fQuad_Tangent[0][1], fQuad_Tangent[0][2], 0.0f,
                           fQuad_Binormal[0][0], fQuad_Binormal[0][1], fQuad_Binormal[0][2], 0.0f,
                           fQuad_Normal[0][0], fQuad_Normal[0][1], fQuad_Normal[0][2], 0.0f,
                           0.0f, 0.0f, 0.0f, 1.0f};
// Light Position in Object Space
float fLightPosition_OS[3] = {-200.0f, 0.0f, -140.0f};
// Light Vector in Object Space
float fLightVector_OS[3];
// Light Vector in Tangent Space
float fLightVector_TS[3];
float modelViewMatrix[16];
float modelViewMatrixInverse[16];
while(bExitMainLoop == false)
{
    glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
    glLoadIdentity();
    glTranslatef(0.0f, 0.0f, -200.0f);
    glRotatef(fRotate, 0.0f, 1.0f, 0.0f);
    glPushAttrib(GL_TEXTURE_BIT);
    // UNIT 0
    glActiveTextureARB(GL_TEXTURE0_ARB);
    glBindTexture(GL_TEXTURE_2D, TEXTURES.Return_TextureIDs(4));
    glEnable(GL_TEXTURE_2D);

    glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_ARB);
    glTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_DOT3_RGBA_ARB);

    glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_TEXTURE);
    glTexEnvf(GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR);
    glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_PRIMARY_COLOR_ARB);
    glTexEnvf(GL_TEXTURE_ENV, GL_OPERAND1_RGB_ARB, GL_SRC_COLOR);

    // UNIT 1
    glActiveTextureARB(GL_TEXTURE1_ARB);
    glBindTexture(GL_TEXTURE_2D, TEXTURES.Return_TextureIDs(2));
    glEnable(GL_TEXTURE_2D);

    glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_ARB);
    glTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_MODULATE);

    glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_PREVIOUS_ARB);
    glTexEnvf(GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR);
    glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_TEXTURE);
    glTexEnvf(GL_TEXTURE_ENV, GL_OPERAND1_RGB_ARB, GL_SRC_COLOR);
    glGetFloatv(GL_MODELVIEW_MATRIX, modelViewMatrix);
    Matrix_4x4_Inverse(modelViewMatrix, modelViewMatrixInverse);
    glBegin(GL_TRIANGLE_STRIP);

//------------

    // compute L and store as vertex color
    Vector_3D_Subtract(fLightPosition_OS, fQuad_XYZ[0], fLightVector_OS);
    Vector_3D_Matrix_4x4_Multiply(fLightVector_OS, modelViewMatrixInverse, fLightVector_OS);
    Vector_3D_Normalize(fLightVector_OS);
    // convert to tangent space
    Vector_3D_Matrix_3x3_Multiply(fLightVector_OS, tangentMatrix, fLightVector_TS);
    // scale and bias
    fLightVector_TS[0] = fLightVector_TS[0] * 0.5 + 0.5;
    fLightVector_TS[1] = fLightVector_TS[1] * 0.5 + 0.5;
    fLightVector_TS[2] = fLightVector_TS[2] * 0.5 + 0.5;
    glMultiTexCoord2fARB(GL_TEXTURE0_ARB, 0.0, 0.0);
    glMultiTexCoord2fARB(GL_TEXTURE1_ARB, 0.0, 0.0);
    glColor3fv(fLightVector_TS);
    glNormal3fv(fQuad_Normal[0]);
    glVertex3fv(fQuad_XYZ[0]);

//------------

    // compute L and store as vertex color
    Vector_3D_Subtract(fLightPosition_OS, fQuad_XYZ[1], fLightVector_OS);
    Vector_3D_Matrix_4x4_Multiply(fLightVector_OS, modelViewMatrixInverse, fLightVector_OS);
    Vector_3D_Normalize(fLightVector_OS);
    // convert to tangent space
    Vector_3D_Matrix_3x3_Multiply(fLightVector_OS, tangentMatrix, fLightVector_TS);
    // scale and bias
    fLightVector_TS[0] = fLightVector_TS[0] * 0.5 + 0.5;
    fLightVector_TS[1] = fLightVector_TS[1] * 0.5 + 0.5;
    fLightVector_TS[2] = fLightVector_TS[2] * 0.5 + 0.5;
    glMultiTexCoord2fARB(GL_TEXTURE0_ARB, 1.0, 0.0);
    glMultiTexCoord2fARB(GL_TEXTURE1_ARB, 1.0, 0.0);
    glColor3fv(fLightVector_TS);
    glNormal3fv(fQuad_Normal[1]);
    glVertex3fv(fQuad_XYZ[1]);

//------------

    // compute L and store as vertex color
    Vector_3D_Subtract(fLightPosition_OS, fQuad_XYZ[2], fLightVector_OS);
    Vector_3D_Matrix_4x4_Multiply(fLightVector_OS, modelViewMatrixInverse, fLightVector_OS);
    Vector_3D_Normalize(fLightVector_OS);
    // convert to tangent space
    Vector_3D_Matrix_3x3_Multiply(fLightVector_OS, tangentMatrix, fLightVector_TS);
    // scale and bias
    fLightVector_TS[0] = fLightVector_TS[0] * 0.5 + 0.5;
    fLightVector_TS[1] = fLightVector_TS[1] * 0.5 + 0.5;
    fLightVector_TS[2] = fLightVector_TS[2] * 0.5 + 0.5;
    glMultiTexCoord2fARB(GL_TEXTURE0_ARB, 0.0, 1.0);
    glMultiTexCoord2fARB(GL_TEXTURE1_ARB, 0.0, 1.0);
    glColor3fv(fLightVector_TS);
    glNormal3fv(fQuad_Normal[2]);
    glVertex3fv(fQuad_XYZ[2]);

//------------

    // compute L and store as vertex color
    Vector_3D_Subtract(fLightPosition_OS, fQuad_XYZ[3], fLightVector_OS);
    Vector_3D_Matrix_4x4_Multiply(fLightVector_OS, modelViewMatrixInverse, fLightVector_OS);
    Vector_3D_Normalize(fLightVector_OS);
    // convert to tangent space
    Vector_3D_Matrix_3x3_Multiply(fLightVector_OS, tangentMatrix, fLightVector_TS);
    // scale and bias
    fLightVector_TS[0] = fLightVector_TS[0] * 0.5 + 0.5;
    fLightVector_TS[1] = fLightVector_TS[1] * 0.5 + 0.5;
    fLightVector_TS[2] = fLightVector_TS[2] * 0.5 + 0.5;
    glMultiTexCoord2fARB(GL_TEXTURE0_ARB, 1.0, 1.0);
    glMultiTexCoord2fARB(GL_TEXTURE1_ARB, 1.0, 1.0);
    glColor3fv(fLightVector_TS);
    glNormal3fv(fQuad_Normal[3]);
    glVertex3fv(fQuad_XYZ[3]);

//------------

    glEnd();
    glPopAttrib();
    fLightPosition_OS[0] += 0.4f;
    SwapBuffers(GLINIT.Return_HDC());
    GetGLError();
}
glFinish();

}

Diapolo

[This message has been edited by Diapolo (edited 02-27-2002).]

I only know of two ways to calcualte the tangent vector (both of which are kinda the same)

void tangent_basis(float v0[3], float v1[3], float v2[3], float t0[2], float t1[2], float t2[2], float *tangent)
{
float crossproduct[3];
float edge0[3]={ v1[0]-v0[0], t1[0]-t0[0], t1[1]-t0[1] };
float edge1[3]={ v2[0]-v0[0], t2[0]-t0[0], t2[1]-t0[1] };

crossproduct[0]=edge0[1]*edge1[2]-edge0[2]*edge1[1];
crossproduct[1]=edge0[2]*edge1[0]-edge0[0]*edge1[2];
crossproduct[2]=edge0[0]*edge1[1]-edge0[1]*edge1[0];

tangent[0]=-crossproduct[1]*(1/crossproduct[0]);

edge0[0]=v1[1]-v0[1];
edge1[0]=v2[1]-v0[1];

crossproduct[0]=edge0[1]*edge1[2]-edge0[2]*edge1[1];
crossproduct[1]=edge0[2]*edge1[0]-edge0[0]*edge1[2];
crossproduct[2]=edge0[0]*edge1[1]-edge0[1]*edge1[0];

tangent[1]=-crossproduct[1]*(1/crossproduct[0]);

edge0[0]=v1[2]-v0[2];
edge1[0]=v2[2]-v0[2];

crossproduct[0]=edge0[1]*edge1[2]-edge0[2]*edge1[1];
crossproduct[1]=edge0[2]*edge1[0]-edge0[0]*edge1[2];
crossproduct[2]=edge0[0]*edge1[1]-edge0[1]*edge1[0];

tangent[2]=-crossproduct[1]*(1/crossproduct[0]);
}

Where v0, v1, v2, and t0, t1, t2 are the 3 vertices of the triangle, and the texture coords.

and this one:

float v0[4], v1[4], tangent[3];

v0[0]=TriVert1.x-TriVert0.x;
v0[1]=TriVert1.y-TriVert0.y;
v0[2]=TriVert1.z-TriVert0.z;
v0[3]=TriVert1.v-TriVert0.v; //V = V texture coord

v1[0]=TriVert2.x-TriVert0.x;
v1[1]=TriVert2.y-TriVert0.y;
v1[2]=TriVert2.z-TriVert0.z;
v1[3]=TriVert2.v-TriVert0.v;

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

Hope that helps.

Great, Thank you NitroGL .
But a few questions are left.

In the second function, the V part of the texture coordinate is used.
V in this case is the T value of a ST texture coord pair, right?
Because in tangent space the tangent points in the direction of the increasing T value.
But I´m asking myself, why it´s common to use U and V … what do these 2 stand for?

I allways need more than one vertex in order to compute the tangent for a vertex, is there any other solution for this?

But OK, I´ll try to implement the code you posted into my current function .

I guess more questions are to come !

Thanks again NitroGL!

Diapolo

Yeah, T and V are the same.

Not sure why it’s V though…

OK, here is my next question g.
I coded a function, based on NitroGL´s 2nd code and it seems to work, well half of the time.

I currently use a single Quad, based on a Triangle-Strip (1-2-3 / 4-3-2).

3-------4

--------
--------
1-------2

It seems there is a rule on which vertex to give to the function first, in order to get the correct Tangent.

For my simple Quad, the tangents should all be 1.0f, 0.0f, 0.0f.
But after I used the function I get:
1.0f, 0.0f, 0.0f
-1.0f, 0.0f, 0.0f
1.0f, 0.0f, 0.0f
-1.0f, 0.0f, 0.0f
Which is wrong.
So it seems not to work for the vertices 1 and 4.

// Quad Vertices
float fQuad_XYZ[4][3] = {{-50.0f, -50.0f, 0.0f},
                         { 50.0f, -50.0f, 0.0f},
                         {-50.0f,  50.0f, 0.0f},
                         { 50.0f,  50.0f, 0.0f}};
// Quad Texture Coordinates
float fQuad_ST[4][2] = {{0.0f, 0.0f},
                        {1.0f, 0.0f},
                        {0.0f, 1.0f},
                        {1.0f, 1.0f}};
// Quad Tangents
float fQuad_Tangent[4][3];

// compute and normalize Tangents
Vector_3D_Tangent(fQuad_XYZ[0], fQuad_ST[0], fQuad_XYZ[1], fQuad_ST[1], fQuad_XYZ[2], fQuad_ST[2], fQuad_Tangent[0]);
Vector_3D_Normalize(fQuad_Tangent[0]);
Vector_3D_Tangent(fQuad_XYZ[1], fQuad_ST[1], fQuad_XYZ[0], fQuad_ST[0], fQuad_XYZ[2], fQuad_ST[2], fQuad_Tangent[1]);
Vector_3D_Normalize(fQuad_Tangent[1]);
Vector_3D_Tangent(fQuad_XYZ[2], fQuad_ST[2], fQuad_XYZ[0], fQuad_ST[0], fQuad_XYZ[1], fQuad_ST[1], fQuad_Tangent[2]);
Vector_3D_Normalize(fQuad_Tangent[2]);
Vector_3D_Tangent(fQuad_XYZ[3], fQuad_ST[3], fQuad_XYZ[1], fQuad_ST[1], fQuad_XYZ[2], fQuad_ST[2], fQuad_Tangent[3]);
Vector_3D_Normalize(fQuad_Tangent[3]);

Diapolo

[This message has been edited by Diapolo (edited 02-27-2002).]

Well I figured it out … I have to input the Vertex Data in CCW order.

Vector_3D_Tangent(fQuad_XYZ[0], fQuad_ST[0], fQuad_XYZ[1], fQuad_ST[1], fQuad_XYZ[2], fQuad_ST[2], fQuad_Tangent[0]);
Vector_3D_Normalize(fQuad_Tangent[0]);
Vector_3D_Tangent(fQuad_XYZ[1], fQuad_ST[1], fQuad_XYZ[2], fQuad_ST[2], fQuad_XYZ[0], fQuad_ST[0], fQuad_Tangent[1]);
Vector_3D_Normalize(fQuad_Tangent[1]);
Vector_3D_Tangent(fQuad_XYZ[2], fQuad_ST[2], fQuad_XYZ[0], fQuad_ST[0], fQuad_XYZ[1], fQuad_ST[1], fQuad_Tangent[2]);
Vector_3D_Normalize(fQuad_Tangent[2]);
Vector_3D_Tangent(fQuad_XYZ[3], fQuad_ST[3], fQuad_XYZ[2], fQuad_ST[2], fQuad_XYZ[1], fQuad_ST[1], fQuad_Tangent[3]);
Vector_3D_Normalize(fQuad_Tangent[3]);

Diapolo

Glad you figured it out!