heres a simple sniplet : tested with CCW triangles. just flip order for CW.
public static vec3 getTangent ( vec3 v0, vec3 v1, vec3 v2, // three vertices of a triangle/face
vec2 st0, vec2 st1, vec2 st2 ) // texture-coords for these vertices
{
float x1 = v1.x - v0.x;
float x2 = v2.x - v0.x;
float y1 = v1.y - v0.y;
float y2 = v2.y - v0.y;
float z1 = v1.z - v0.z;
float z2 = v2.z - v0.z;
float s1 = st1.x - st0.x;
float s2 = st2.x - st0.x;
float t1 = st1.y - st0.y;
float t2 = st2.y - st0.y;
float r = 1.0f / ( s1 * t2 - s2 * t1 );
vec3 tangent = new vec3 (( t2 * x1 - t1 * x2 ) * r, ( t2 * y1 - t1 * y2 ) * r, ( t2 * z1 - t1 * z2 ) * r );
// vec3 bitangent = new vec3 ((s1 * x2 - s2 * x1) * r, (s1 * y2 - s2 * y1) * r, (s1 * z2 - s2 * z1) * r); // compute this in a shader instead
return normalize(tangent);
}
vertex-shader could do something like …
#version 140
in vec3 tangent; // incoming tangent as computed above
[...]
out mat3 tbn; // fragment shader input
void main()
{
vec3 normal = gl_NormalMatrix * gl_Normal;
vec3 T = normalize ( gl_NormalMatrix * tangent );
vec3 B = normalize ( cross ( T, normal ) );
tbn = mat3( T, B, normal );
[...]
}
in the fragment shader somthing like
vec3 normal = normalize(tbn * vec3(0.0,0.0,1.0))
should work.
disclaimer : there is at least one bug in that code! 