15

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?

Ramy Al Zuhouri
  • 479
  • 1
  • 6
  • 26

1 Answers1

35

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 &lt; triangleCount; a++)
{
    long i1 = triangle-&gt;index[0];
    long i2 = triangle-&gt;index[1];
    long i3 = triangle-&gt;index[2];

    const Point3D&amp; v1 = vertex[i1];
    const Point3D&amp; v2 = vertex[i2];
    const Point3D&amp; v3 = vertex[i3];

    const Point2D&amp; w1 = texcoord[i1];
    const Point2D&amp; w2 = texcoord[i2];
    const Point2D&amp; 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 &lt; vertexCount; a++)
{
    const Vector3D&amp; n = normal[a];
    const Vector3D&amp; 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]) &lt; 0.0F) ? -1.0F : 1.0F;
}

delete[] tan1;

}

Full source code and derivation can be found here.

Pikalek
  • 12,372
  • 5
  • 43
  • 51
concept3d
  • 12,706
  • 4
  • 45
  • 57
  • 1
    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? – Ramy Al Zuhouri Jan 10 '14 at 12:05
  • @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. – concept3d Jan 10 '14 at 12:26
  • 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. – Ramy Al Zuhouri Jan 10 '14 at 12:33
  • 1
    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. – concept3d Jan 10 '14 at 13:45
  • In my case I'll have 1084 tangents, how do I map the tangents with the vertices? – Ramy Al Zuhouri Jan 11 '14 at 11:35
  • each vertex will have a tangent a bitangent. You can either have 3 arrays. array for vertices and array for tangents etc or use one array where tangents and bitangent and veritces interleave – concept3d Jan 11 '14 at 12:56
  • Sorry but I still haven't understood a thing. After I compute the tangents and have this array to pass to shaders, how do I associate each TBN space with the vertices? Do I have to calculate inside which face is each vertex? – Ramy Al Zuhouri Jan 13 '14 at 12:47
  • @RamyAlZuhouri the vertex shader is executed once per vertex. You calculate the TBN for each vertex in the vertex shader. – concept3d Jan 13 '14 at 16:03
  • @concept3d I don't understand why one needs to do so many calculations for tangents.I mean,looks here https://learnopengl.com/#!Advanced-Lighting/Normal-Mapping ,that's much simpler. What is the benefit to do it this way? – Michael IV Jul 25 '17 at 18:38
  • @MichaelIvanov I didn't follow the steps in the link, but it looks similar to me – concept3d Jul 26 '17 at 08:53
  • @concept3d handedness, is it mandatory? – Michael IV Jul 26 '17 at 08:56
  • @Michael IV I believe this is one of the easiest approaches. This solution is consistent with your learnopengl link (look for "Tangent space" section). Another approach is calculating TBN in shader : https://gamedev.stackexchange.com/questions/86530/is-it-possible-to-calculate-the-tbn-matrix-in-the-fragment-shader . – cppBeginner Jan 08 '19 at 04:22