• Advertisement
Sign in to follow this  

Computing tangents

This topic is 4261 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

From what I understood, in order to do normal map bump mapping I need to build up the inverse of the TBN (orthonormal, so this is easy), use it to transform the light position and then apply the lighting equation. To compute the TBN I need normal, tangent and bitangent vectors, then in the vertex shader I transform them with modelview matrix and build the transpose of the TBN. I can calculate the bitangent multiplying the normal and the tangent in the shader. My question is, how do I compute the tangent in the first place? Normals are easy, but what about tangents? Thank you! [edit: fixed your thread title. -Superpig]

Share this post


Link to post
Share on other sites
Advertisement
Do not forget that TBN(Tangent,Binormal,and Normal)represents the X,Y,Z as Orthonormal which means 90 degree apart. So based on that information in order to get The Tangent you can do Binormal Cross Normal. If you are using D3D you can also used the D3DXCreateTangent to Calculate your TBN. Once i get that calculate it i do in the Vertex Shader/Pixel Shader like so.


float4x4 WorldView;
float4x4 WorldViewProjection;
float4x4 World;
float4 lposition={0,0,1.0f,1.f}; //Point Light
texture diffuse;
texture normal;

sampler2D diffusesampler=sampler_state
{
Texture=(diffuse);
MinFilter=Linear;
MagFilter=Linear;
MipFilter=Linear;
};

sampler2D normalsampler=sampler_state
{
Texture=(normal);
MinFilter=Linear;
MagFilter=Linear;
MipFilter=Linear;
};

struct VS_INPUT
{
float4 p:POSITION;
float2 tex0:TEXCOORD0;
float2 tex1:TEXCOORD1;
float4 tangent:TANGENT;
float4 binormal:BINORMAL;
float4 normal:NORMAL;
}

struct VS_OUTPUT
{
float4 p:POSITION;
float2 tex0:TEXCOORD0;
float2 tex1:TEXCOORD1;
float3 lhtPos:TEXCOORD2;
}

VS_OUTPUT BumpShader(VS_INPUT inp)
{
VS_OUTPUT v_out=(VS_OUTPUT)0;
v_out.p=mul(inp.p,WorldViewProjection);
v_out.tex0=inp.tex0;
v_out.tex1=inp.tex1;

//Convert from local to world.
float3 localvertextoworld=mul(inp.p,World);

float3 ldiff= normalize (lposition-localvertextoworld);

float3x3 tbnMatrix=float3x3(inp.tangent,inp.binormal,inp.normal);
tbnMatrix[0]=mul(tbnMatrix[0],WorldView);
tbnMatrix[1]=mul(tbnMatrix[1],WorldView);
tbnMatrix[2]=mul(tbnMatrix[2],WorldView);

v_out.lhtPos=mul(tbnMatrix,ldiff);
v_out.lhtPos=normalize(v_out.lhtPos);
}

float4 BumpPixelShader(VS_OUTPUT ot)
{
float4 d=tex2D(diffusesampler,ot.tex0);
float4 n=2.0f*tex2D(normalsampler,ot.tex1).rgb-1.f;
float dif=saturate(dot(n*v_out.lhtPos));
return dif*d;
}

technique s
{
pass p0
{
Lighting=TRUE;
vertexshader=compile vs_2_0 BumpShader();
pixelshader=compile ps_2_0 BumpPixelShader();
}
}



[Edited by - BornToCode on May 7, 2006 1:37:23 PM]

Share this post


Link to post
Share on other sites
Well if you use D3D you can just use the D3DXComputeTangentFrameEx() function..this is an extrememely useful function, never leave home without it :)
I dont know if OpenGl has a similar function..

Share this post


Link to post
Share on other sites
Thank you, but I use opengl. I've found some code to calculate the tangent, but it's faster to understand how to code it myself than trying to figure it out whats'up in the code and then modify it to fit my needs :-)

Share this post


Link to post
Share on other sites
Since source code for this problem for OpenGL is easy to find (for example, check the Irrlicht sources for inspiration), I'll explain the mechanics. In general a tangent only requires to be perpendicular to the normal, so an arbitrary vector perpendicular to the normal could be chosen. The binormal then simply is cross(normal, tangent). However, since you wish to use these for bumpmapping, there are additional requirements to the tangent and binormal.

They will be used to determine the direction of the texture to properly interpret the normal values stored in the texture. This means that tangent and binormal should be alligned with the direction of the texturecoordinates. Therefore, in this context, the tangent and binormal are usually referred to as s tangent and t tangent, corresponding to the s and t texture directions the correspond to. Hence, to calulate the tangent and binormals for the 3 vertices of a bumpmapped triangle, we need the values of the 3 vertices, their respective normals and their texture coordinates.

For a more thourough explanaition and the important formulas I suggest this webpage: Bump Mapping Using CG. The part describing the theory is not specific to CG and can be applied to any shader type.

Tom

Share this post


Link to post
Share on other sites
Thank you all, now I have many different implementations of the algorithm, and some more general description of it. I wil try to figure it out from them.
Thank you again!

EDIT: I've just read the article "Bump mapping using GC" and perhaps I'm able to understand things with this article. I've one question though: In the algorithm the normal is computed with a cross product between tangent and bitangent. But in the general case the normal is already there, that is the mesh already has normals (perhaps averaged among triangles sharing vertices). How should I behave in this case? Should I rotate my resulting tangent to be perpendicular to the normal? How do I perform this?
Thank you.

[Edited by - cignox1 on May 9, 2006 12:42:13 PM]

Share this post


Link to post
Share on other sites
Usually you do it the other way round,
you have the normal, calculate the tangent and the bitangent is computed via the cross product.

If you use your algorithm right, you usually should get a tangent that is perpendicualar to the normal (think of the three axis of a coordinate system), pointing whitin the plane, your triangle describes, in the direction in which the s or x coordinate of your texture increases.

Hope this helps for understanding (-;

Share this post


Link to post
Share on other sites
Quote:
Original post by cignox1
Thank you all, now I have many different implementations of the algorithm, and some more general description of it. I wil try to figure it out from them.
Thank you again!

EDIT: I've just read the article "Bump mapping using GC" and perhaps I'm able to understand things with this article. I've one question though: In the algorithm the normal is computed with a cross product between tangent and bitangent. But in the general case the normal is already there, that is the mesh already has normals (perhaps averaged among triangles sharing vertices). How should I behave in this case? Should I rotate my resulting tangent to be perpendicular to the normal? How do I perform this?
Thank you.


Usually, generating normals works like this:
1. calculate normals for triangles
2. calculate normals per vertex for smooth shading (as yous said, often by taking the average vector of all normals of the triangles meeting in that vertex)

I believe that when also using s and t tangents, the process is similar:
1. calculate normal and s and t vectors for the triangles (s and t tangents simply being the actual directions of the texture coordinates in the plane of the triangle)
2. calculate normals, s and t tangents per vertex for smooth shading (this time getting 3 average vectors instead of 1)

If, for example, the normals come from a model file, they already went through step 1 and 2. You can then either do the calculation only for the tangents (which may or may not look weird depending on how much your algorithm for obtaining the smooth vectors differs from the one used when creating the model) or you can throw away the normals and do the entire thing yourself.

Tom

Share this post


Link to post
Share on other sites
The concise description of tangent calculation is as follows:
The normal, tu, and tv axes of a surface form a coordinate basis. We require an orthonormal basis to do any kind of transformations. So by orthonormalizing these axes, we get normal, tangent, and binormal vectors that represent an orthonormal basis matrix that can be used to transform from object space to the local tangent space that our surface occupies.

Depending on how comfortable with linear algebra you are, that either makes perfect sense, or you have no idea what the hell I just said.

Share this post


Link to post
Share on other sites
Thank you for the help, I've always known to be sort a stupid when math is involved :-(
Quote:
Original post by Promit
The concise description of tangent calculation is as follows:
The normal, tu, and tv axes of a surface form a coordinate basis. We require an orthonormal basis to do any kind of transformations. So by orthonormalizing these axes, we get normal, tangent, and binormal vectors that represent an orthonormal basis matrix that can be used to transform from object space to the local tangent space that our surface occupies.

Depending on how comfortable with linear algebra you are, that either makes perfect sense, or you have no idea what the hell I just said.


This makes perfect sense, but I don't know how to achieve this :-) Well, let's see:
-tu, tv: I suppose that are texture coordinates directions. That is, if I have A, B and C as the vertices of my triangle, then, in A, I obtain:
tu = (B.u - A.u, B.v - A.v)
tv = (C.u - A.u, C.v - A.v)

These two vectors are in a 2D space. To convert them to 3d space I use the following code:

float r = 1.0F / (s1 * t2 - s2 * t1);
float sdir_x = (t2 * x1 - t1 * x2) * r;
float sdir_y = (t2 * y1 - t1 * y2) * r;
float sdir_z = (t2 * z1 - t1 * z2) * r;
float tdir_x = (s1 * x2 - s2 * x1) * r;
float tdir_y = (s1 * y2 - s2 * y1) * r;
float tdir_z = (s1 * z2 - s2 * z1) * r;



Now I have sdir = tangent and tdir = bitangent. Now I have to orthonormalize them with the normal I already have. How to do that? Since I use the whole triangle to find the sdir and tdir, could I do as dimebolt suggested? Taking the average of the tangent and bitangent among the polygons?

Share this post


Link to post
Share on other sites
Graham-Schmidt orthnonormalization is fairly popular. I don't know if it's the best way, but it works, it's easy to do, and it gives good results. Just start with the normal vector first, since you don't actually want to alter that one.

At this point you'll have per-poly tangents. To get per vertex tangents, you do pretty much exactly the same thing that you do with normals.

Share this post


Link to post
Share on other sites
Quote:
Original post by Promit
Graham-Schmidt orthnonormalization is fairly popular. I don't know if it's the best way, but it works, it's easy to do, and it gives good results. Just start with the normal vector first, since you don't actually want to alter that one.

At this point you'll have per-poly tangents. To get per vertex tangents, you do pretty much exactly the same thing that you do with normals.


Why do I have per polygon tangent if I'm starting with the normal? This is how I currently do:
This is the function to create the tangent and the bitangent of the polygon:

bool Loader3DS::Tangents(float* va, float *vb, float *vc, float* uva, float* uvb, float* uvc, float* tangent, float* bitangent)
{
if(!va || !vb || !vc || !uva || !uvb || !uvc || !tangent || !bitangent) return false;

float a[3], b[3], c[3];
float b_a_T, b_a_B, c_a_T, c_a_B;

a[0] = vb[0] - va[0];
a[1] = vb[1] - va[1];
a[2] = vb[2] - va[2];
b[0] = vc[0] - va[0];
b[1] = vc[1] - va[1];
b[2] = vc[2] - va[2];

b_a_T = uvb[0] - uva[0];
b_a_B = uvb[1] - uva[1];
c_a_T = uvc[0] - uva[0];
c_a_B = uvc[1] - uva[1];

float r = 1.0 / (b_a_T*c_a_B - c_a_T*b_a_B);

tangent[0] = (c_a_B*a[0] - b_a_B*b[0]) * r;
tangent[1] = (c_a_B*a[1] - b_a_B*b[1]) * r;
tangent[2] = (c_a_B*a[2] - b_a_B*b[2]) * r;
bitangent[0] = (c_a_T*a[0] - b_a_T*b[0]) * r;
bitangent[1] = (c_a_T*a[1] - b_a_T*b[1]) * r;
bitangent[2] = (c_a_T*a[2] - b_a_T*b[2]) * r;

return true;
}


The next computes the tangent for a list of polygons:

bool Loader3DS::FlatTangents(int nfaces, unsigned short int *faces, float *vertices, float* uvcoords, float* tangents, float* bitangents)
{
if(!faces || !vertices || !uvcoords || !tangents || !bitangents) return false;
for(int i = 0; i < nfaces; i++)
{
Tangents(&vertices[faces[i*3]*3], &vertices[faces[i*3+1]*3], &vertices[faces[i*3+2]*3],
&uvcoords[faces[i*3]*2], &uvcoords[faces[i*3+1]*2], &uvcoords[faces[i*3+2]*2], &tangents[i*3], &bitangents[i*3]);
}
return true;
}


And now I compute smooth tangents and bitangents averaging the polygons tangents:

bool Loader3DS::SmoothTangents(unsigned int nfaces, unsigned short int *faces, float *vertices, float* uvcoords, float* tangents, float* bitangents)
{
if(!faces || !vertices || !uvcoords || !tangents || !bitangents) return false;

//First calculate the faces normals
float* temp_t = new float[nfaces*3];
if(!temp_t) return false;
float* temp_b = new float[nfaces*3];
if(!temp_b) { delete[] temp_b; return false; }
FlatTangents(nfaces, faces, vertices, uvcoords, temp_t, temp_b);

//Now calculate the smoothed normals
float t[3];
float b[3];
int c = 0;
float len = 0.0;
for(int i = 0; i < nfaces * 3; i++)
{
c = 0;
t[0] = t[1] = t[2] = 0.0;
b[0] = b[1] = b[2] = 0.0;
for(int j = 0; j < nfaces; j++)
{
if(faces[j*3] == i || faces[j*3+1] == i || faces[j*3+2] == i)
{
t[0] += temp_t[j*3];
t[1] += temp_t[j*3+1];
t[2] += temp_t[j*3+2];

b[0] += temp_b[j*3];
b[1] += temp_b[j*3+1];
b[2] += temp_b[j*3+2];
++c;
}
}
if(c != 0) //Take the average of the normals
{
//Average and normalize tangents
t[0] /= c;
t[1] /= c;
t[2] /= c;
len = sqrt(t[0]*t[0] + t[1]*t[1] + t[2]*t[2]);

tangents[i*3] = t[0] / len;
tangents[i*3+1] = t[1] / len;
tangents[i*3+2] = t[2] / len;

//Average and normalize bitangents
b[0] /= c;
b[1] /= c;
b[2] /= c;
len = sqrt(b[0]*b[0] + b[1]*b[1] + b[2]*b[2]);

bitangents[i*3] = b[0] / len;
bitangents[i*3+1] = b[1] / len;
bitangents[i*3+2] = b[2] / len;
}
}
delete[] temp_t;
delete[] temp_b;
return true;
}


I expect the following code:

float *n = &(model->normals[j]);
float *t = &(model->tan[j]);

float dot = n[0]*t[0] + n[1]*t[1] + n[2]*t[2];


to put 0 in dot, wich doesn't. Any idea?

Share this post


Link to post
Share on other sites
Does your code works ok?

I have problems with the normal/tangets in TangentSpace! (ObjectSpace Ok)
Normals made with NVIDIA Melody!


core::vector3df& normal,
core::vector3df& tangent,
core::vector3df& binormal,
core::vector3df& vt1, core::vector3df& vt2, core::vector3df& vt3 // vertices
core::vector2df& tc1, core::vector2df& tc2, core::vector2df& tc3 // texture coords

#ifdef USE_BOGDAN_VERSION

normal.set(0,0,0);
tangent.set(0,0,0);
binormal.set(0,0,0);

// normal
core::vector3df v1 = vt1 - vt2;
core::vector3df v2 = vt3 - vt1;
normal = v2.crossProduct(v1);
normal.normalize();

// tangent & binormal
core::vector3df deltaV2V1 = vt2 - vt1;
core::vector3df deltaV3V1 = vt3 - vt1;
f32 deltaC2C1T = tc2.X - tc1.X;
f32 deltaC2C1B = tc2.Y - tc1.Y;
f32 deltaC3C1T = tc3.X - tc1.X;
f32 deltaC3C1B = tc3.Y - tc1.Y;
f32 mTex = 1.0f / (deltaC2C1T*deltaC3C1B - deltaC3C1T*deltaC2C1B);

tangent = (deltaV2V1 * deltaC3C1B - deltaV3V1 * deltaC2C1B) * mTex;
tangent.normalize();

binormal = (deltaV2V1 * deltaC3C1T * -1.0f + deltaV3V1 * deltaC2C1T) * mTex;
binormal.normalize();

#endif // USE_BOGDAN_VERSION

Share this post


Link to post
Share on other sites
Quote:
Original post by cignox1
This makes perfect sense, but I don't know how to achieve this :-) Well, let's see:
-tu, tv: I suppose that are texture coordinates directions. That is, if I have A, B and C as the vertices of my triangle, then, in A, I obtain:
tu = (B.u - A.u, B.v - A.v)
tv = (C.u - A.u, C.v - A.v)

These two vectors are in a 2D space. To convert them to 3d space I use the following code:
*** Source Snippet Removed ***

Now I have sdir = tangent and tdir = bitangent. Now I have to orthonormalize them with the normal I already have. How to do that? Since I use the whole triangle to find the sdir and tdir, could I do as dimebolt suggested? Taking the average of the tangent and bitangent among the polygons?


It seems that you don't know where that code came from originally -- this might help a little:

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

Share this post


Link to post
Share on other sites
Some problems I see, or maybe I am wrong:

1] If you smooth the tangent/bi tangent per vertex you don't lost the orthonormal basis? (no more 90)

2] How do I know what are the correct tangent/bi tangent when I use the NormalMaps generate my NVidia Tool (Melody) or ATI Normal Mapper?

3) I sow somewhere that I must flip the y (green) channel of the NormalMap (NVidai Melody)? Why is that?

So pls can anyone clarify me about this? (Normal mapping in ObjectSpace works OK but I don't need any tangent/bi tangent)

Share this post


Link to post
Share on other sites
Is anyone who had correct calculated the normal/tangent/bitagent for TangetSpace? (normal-maps generated but ATI NormalMapper or NVIDIA Melody)

Share this post


Link to post
Share on other sites
I think that my code works now. That is, this what I can say when I look to my bump mapped monster on the screen :-)
PM me if you need some code, but be aware that I don't make use of Normal Mapper nor Melody...

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement