This is craving my brain. It’s a small test app in C# using OpenTK:
private void myGLControl1_Paint(object sender, PaintEventArgs e)
{
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
// Draw triangle with texture
GL.Color3(1.0f, 1.0f, 1.0f);
GL.Enable(EnableCap.Texture2D);
tex.Bind();
GL.PolygonMode(MaterialFace.FrontAndBack, PolygonMode.Fill);
GL.Begin(BeginMode.Triangles);
GL.TexCoord2(t0.X, t0.Y);
GL.Vertex3(p0.X, p0.Y, p0.Z);
GL.TexCoord2(t1.X, t1.Y);
GL.Vertex3(p1.X, p1.Y, p1.Z);
GL.TexCoord2(t2.X, t2.Y);
GL.Vertex3(p2.X, p2.Y, p2.Z);
GL.End();
// Draw triangle in wireframe
GL.Color3(1.0f, 1.0f, 0.0f);
GL.PolygonMode(MaterialFace.FrontAndBack, PolygonMode.Line);
GL.LineWidth(3);
GL.Disable(EnableCap.Texture2D);
GL.Begin(BeginMode.Triangles);
GL.Vertex3(p0.X, p0.Y, p0.Z);
GL.Vertex3(p1.X, p1.Y, p1.Z);
GL.Vertex3(p2.X, p2.Y, p2.Z);
GL.End();
// Compute triangle basis
Vector3f v0 = p1 - p0;
Vector3f v1 = p2 - p0;
Vector3f n = Vector3f.CrossProduct(v0, v1);
//n.Normalize(); // not really needed
Matrix4f A = new Matrix4f(v0, v1, n, p0);
// Point to locate tangent and bitangent origin somewhere outside the triangle
Vector3f temp = A.TransformPoint(new Vector3f(-0.1f, -0.1f, 0.0f));
GL.PointSize(4);
GL.Color3(1.0f, 0.0f, 1.0f);
GL.Begin(BeginMode.Points);
GL.Vertex3(temp.X, temp.Y, temp.Z);
GL.End();
// Texture basis
Vector3f uv0 = t1 - t0;
Vector3f uv1 = t2 - t0;
Vector3f uvn = Vector3f.CrossProduct(uv0, uv1);
Matrix4f B = new Matrix4f(uv0, uv1, uvn);
B.Invert();
//Matrix4f C = A * B;
Matrix4f C = B * A;
// Draw tangent vector
Vector3f tan = C.TransformVector(new Vector3f(1.0f, 0.0f, 0.0f)) * 0.5f;
GL.Color3(1.0f, 0.0f, 0.0f);
GL.Begin(BeginMode.Lines);
GL.Vertex3(temp.X, temp.Y, temp.Z);
GL.Vertex3(temp.X+tan.X, temp.Y+tan.Y, temp.Z+tan.Z);
GL.End();
// Draw bitangent (or binormal)
Vector3f bitan = C.TransformVector(new Vector3f(0.0f, 1.0f, 0.0f)) * 0.5f;
GL.Color3(0.0f, 1.0f, 0.0f);
GL.Begin(BeginMode.Lines);
GL.Vertex3(temp.X, temp.Y, temp.Z);
GL.Vertex3(temp.X + bitan.X, temp.Y + bitan.Y, temp.Z + bitan.Z);
GL.End();
// Overimpose full texture quad over the triangle
Vector3f c0 = C.TransformPoint(new Vector3f(0, 0, 0));
Vector3f c1 = C.TransformPoint(new Vector3f(1, 0, 0));
Vector3f c2 = C.TransformPoint(new Vector3f(1, 1, 0));
Vector3f c3 = C.TransformPoint(new Vector3f(0, 1, 0));
GL.Enable(EnableCap.Blend);
GL.BlendFunc(BlendingFactorSrc.SrcAlpha, BlendingFactorDest.OneMinusSrcAlpha);
GL.Color4(1.0f, 1.0f, 1.0f, 0.5f);
GL.PolygonMode(MaterialFace.FrontAndBack, PolygonMode.Fill);
GL.Enable(EnableCap.Texture2D);
GL.Begin(BeginMode.Quads);
GL.TexCoord2(0, 0);
GL.Vertex3(c0.X, c0.Y, c0.Z);
GL.TexCoord2(1, 0);
GL.Vertex3(c1.X, c1.Y, c1.Z);
GL.TexCoord2(1, 1);
GL.Vertex3(c2.X, c2.Y, c2.Z);
GL.TexCoord2(0, 1);
GL.Vertex3(c3.X, c3.Y, c3.Z);
GL.End();
GL.Disable(EnableCap.Blend);
GL.Disable(EnableCap.Texture2D);
myGLControl1.SwapBuffers();
}
It works perfectly as long as B is identity i.e. tex coords (0,0) (1,0) (0,1)
Matrix multiplication, inversion et al had all been proved good with hierarchical transforms, camera transform (inverse of normal transform) and skinning