15
\$\begingroup\$

I have a texture loaded in three.js, then passed to the shaders. In the vertex shader I compute the normal, and I save into a variable the uv vector.

<script id="vertexShader" type="x-shader/x-vertex">

                varying vec3 N,P;
                varying vec2 UV;

                void main() {
                    gl_Position= projectionMatrix * modelViewMatrix * vec4(position,1.0);
                    P= position;
                    N= normalMatrix * vec3(normal);
                    UV= uv;
                }
            </script>
            <script id="fragmentShader" type="x-shader/x-fragment">

                varying vec3 N,P;
                varying vec2 UV;
                uniform sampler2D texture;

                void main() {
                    gl_FragColor= texture2D(texture,UV);
                }

            </script>

How do I compute the T and B vectors?

\$\endgroup\$
3
  • 2
    \$\begingroup\$ do you want the algorithm in general or specifically for your library of choice ? \$\endgroup\$
    – concept3d
    Commented Jan 9, 2014 at 12:27
  • \$\begingroup\$ If I can compute it with three.js it would be better. \$\endgroup\$ Commented Jan 9, 2014 at 13:07
  • \$\begingroup\$ You may want to look up the MikkTSpace algorithm, which is a standardised way of generating a tangent basis that's designed to inter-operate well between different software packages. \$\endgroup\$
    – DMGregory
    Commented Aug 11, 2022 at 9:17

1 Answer 1

35
\$\begingroup\$

First of all, for every 3D vertex there is infinite tangent and bi-tangent vectors. The below image explains why there is an infinite number of tangent spaces for each vertex, the tangent and bitangent can have any direction in the shown plane.

Infinite number of tangent spaces for each vertex

So in order to properly calculate the most useful1 tangent space, we want our tangent space to be aligned such that the x axis (the tangent) corresponds to the u direction in the bump map and the y axis (bitangent) corresponds to the v direction in the bump map, we should already have normal of the vertex which already corresponds to the Z direction in tangent space.

(1) most useful because in the end we want normal vectors to be sampled from the texture

That best be explained with pictures, we want our tangent space to be aligned like (u, v) shown below.

enter image description here

Source of the image though not strictly related to computer graphics

In computer graphics developers usually use (u,v) also known as texture coordinates. We will assume T is the tangent and B is the bitangent, and P0 is our target vertex, that is part of the triangle (P0,P1,P2).

First remember what we wanted to do, is to calculate tangent and bitanget that:

  1. T aligned with u and B aligned with v.
  2. T and B lays in the plane with the vertex normal (the plane shown in the above image).

The point is we already assumed that T and B lays in the same plane and corresponds to U and V now if we can know their values we can cross product and the third vector to construct a transformation matrix from world to tangent space.

enter image description here

Given that we know that any 2D vector can be written as a linear combination of two independent vectors2 and since we already have the triangle points (edges), shown in the above image. We can write:

E1 = (u1-u0)T + (v1-v0)B

E2 = (u2-u0)T + (v2-v0)B

(2) actually that's is how basis matrix is derived

The above equation can be written in a matrix form,

| E1x E1y E1z |   | deltaU1 deltaV1 | * | Tx Ty Tz |
| E2x E2y E2z | = | deltaU2 deltaV2 |   | Bx By Bz |

By solving the matrixs equation we can determine T and B values we can construct a transformation matrix.

The full source code in C++

#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;
    ZeroMemory(tan1, 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;
}

Full source code and derivation can be found here.

\$\endgroup\$
12
  • 1
    \$\begingroup\$ What if I don't have a triangle? In my case I have a texture that should be applied on a sphere. How to adapt it to this case? \$\endgroup\$ Commented Jan 10, 2014 at 12:05
  • \$\begingroup\$ @RamyAlZuhouri isn't the sphere build from triangles? You just loop over the vertices like in the code. If your sphere isn't triangle based that's a totally different story. \$\endgroup\$
    – concept3d
    Commented Jan 10, 2014 at 12:26
  • \$\begingroup\$ I'm using three.js SphereGeometry (in javascript). Maybe I should pass the face property to the shaders? The sphere I draw has 1089 vertices and 1084 faces. \$\endgroup\$ Commented Jan 10, 2014 at 12:33
  • 1
    \$\begingroup\$ you calculate tangent space and then pass the tangents to the shaders. And you should have access to face/vertices in order to calculate tangent space. \$\endgroup\$
    – concept3d
    Commented Jan 10, 2014 at 13:45
  • \$\begingroup\$ In my case I'll have 1084 tangents, how do I map the tangents with the vertices? \$\endgroup\$ Commented Jan 11, 2014 at 11:35

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .