# Having a problem with tangent/bitangent calculation for my meshes

Hy Guys, I have a very specific problem according to normal mapping. I am writing a very simple 3D engine just for fun, following the tutorials at "LearnOpenGL.com". So far, it is working well. I am now trying to implement normal mapping to the engine, following the tutorial found here: "LearnOpenGL/Advanced Lighting/Normal mapping". I have a very simple shader with a point light:

#version 460 core
layout (location = 0) in vec3 vertexPosition_modelspace;
layout (location = 1) in vec3 vertexNormal;
layout (location = 2) in vec2 texture_coord;
layout (location = 4) in vec3 aTangent;
layout (location = 5) in vec3 aBitangent;

struct pointLight{
vec3 position;
float constant;
float linear;
vec3 ambient;
vec3 diffuse;
vec3 specular;
bool present;
};

uniform mat4 model;
uniform mat4 view;
uniform vec3 viewPos;
uniform mat4 projection;
uniform pointLight light;

out VS_OUT{
vec3 normal;
vec3 FragPos;
vec3 vColor;
vec2 texCoord;
vec3 TangentLightPos;
vec3 TangentViewPos;
vec3 TangentFragPos;
} vs_out;
void main()
{
vs_out.vColor = vec3(1.0);
vs_out.FragPos = vec3(model * vec4(vertexPosition_modelspace, 1.0));

mat3 normalMatrix = transpose(inverse(mat3(model)));
vec3 T = normalize(normalMatrix * aTangent);
vec3 N = normalize(normalMatrix * vertexNormal);
T = normalize(T - dot(T, N) * N);
vec3 B = cross(N, T);

mat3 TBN = transpose(mat3(T, B, N));
vs_out.TangentLightPos = TBN * light.position;
vs_out.TangentViewPos  = TBN * viewPos;
vs_out.TangentFragPos  = TBN * vs_out.FragPos;

vs_out.normal = vec3(vec4(normalMatrix * vertexNormal, 0.0));
vs_out.texCoord = texture_coord;
gl_Position = projection * view * vec4(vs_out.FragPos, 1.0);
}


#version 460 core
struct Material{
float roughness;
float shininess;
float alpha;
vec3 ambient;
vec3 diffuse;
vec3 specular;
sampler2D texture_diffuse1;
sampler2D texture_specular1;
sampler2D texture_normal1;
};

struct pointLight{
vec3 position;

float constant;
float linear;

vec3 ambient;
vec3 diffuse;
vec3 specular;
bool present;
};

//input data
in VS_OUT{
vec3 normal;
vec3 FragPos;
vec3 vColor;
vec2 texCoord;
vec3 TangentLightPos;
vec3 TangentViewPos;
vec3 TangentFragPos;
} fs_in;

// Values that stay constant for the whole mesh.
uniform vec3 viewPos;
uniform pointLight point_light;
uniform Material material;

// output data
out vec4 FragColor ;

vec3 CalcPointLight(pointLight light, vec3 normal, vec3 viewDir, vec3 material_tex, vec3 material_tex_specular);

void main()
{
vec3 material_tex = texture(material.texture_diffuse1, fs_in.texCoord).rgb;
vec3 material_tex_specular = texture(material.texture_specular1, fs_in.texCoord).rgb;

// obtain normal from normal map in range [0,1]
vec3 norm = texture(material.texture_normal1, fs_in.texCoord).rgb;
// transform normal vector to range [-1,1]
norm = normalize(norm * 2.0 - 1.0);  // this normal is in tangent space

vec3 result_light = CalcPointLight(point_light, norm, normalize(fs_in.TangentViewPos - fs_in.TangentFragPos), material_tex, material_tex_specular);
FragColor = vec4(result_light.rgb, material.alpha);
}

vec3 CalcPointLight(pointLight light, vec3 normal, vec3 viewDir, vec3 material_tex, vec3 material_tex_specular){
// ambient
vec3 ambient = light.ambient * material_tex;

// diffuse
vec3 lightDir = normalize(fs_in.TangentLightPos - fs_in.TangentFragPos);
float diff = max(dot(lightDir, normal), 0.0);
vec3 diffuse = light.diffuse * diff * material_tex;

vec3 halfwayDir = normalize(lightDir + viewDir);
float spec = pow(max(dot(normal, halfwayDir), 0.0), material.shininess);

vec3 specular = light.specular * spec * material_tex_specular;

// attenuation
float dist    = length(light.position - fs_in.FragPos);
float attenuation = 1.0 / (light.constant + light.linear * dist + light.quadratic * (dist * dist));

ambient  *= attenuation;
diffuse   *= attenuation;
specular *= attenuation;

if(material.shininess <= 0.0){
return (ambient + diffuse);
}else{
return (ambient + diffuse + specular);
}
}


With a mesh imported with the assimp importer library (github.com/assimp/assimp), this shader works really well - no issues at all. But in this case the tangent and bitangent are calculated by the assimp importer lib.

However, when it comes to my own meshes it is a different story. For my own meshes I have to generate the tangent and bitangent myself. Therefore I used the algorithm shown at LearnOpenGL/Normalmapping and extended it to be able to go over several triangles in a mesh (btw: my meshes are all index vertex arrays). This looks like this:

void my3DEngineVertexObject::calculateTangentsIndexed(vertex* vertex_array, size_t nr_vertices, GLuint* indices, size_t nr_indices) {
//iterate the indices array
for(size_t i = 0; i < nr_indices; i+=3){ //we need to handle 3 vertices --> one triangle
//calculate indices
unsigned int i1 = indices[i];
unsigned int i2 = indices[i + 1];
unsigned int i3 = indices[i + 2];

glm::vec3 edge1 = glm::vec3(vertex_array[i2].pos.x, vertex_array[i2].pos.y, vertex_array[i2].pos.z) - glm::vec3(vertex_array[i1].pos.x, vertex_array[i1].pos.y, vertex_array[i1].pos.z);
glm::vec3 edge2 = glm::vec3(vertex_array[i3].pos.x, vertex_array[i3].pos.y, vertex_array[i3].pos.z) - glm::vec3(vertex_array[i1].pos.x, vertex_array[i1].pos.y, vertex_array[i1].pos.z);
glm::vec2 deltaUV1 = glm::vec2(vertex_array[i2].texCoord.u, vertex_array[i2].texCoord.v) - glm::vec2(vertex_array[i1].texCoord.u, vertex_array[i1].texCoord.v);
glm::vec2 deltaUV2 = glm::vec2(vertex_array[i3].texCoord.u, vertex_array[i3].texCoord.v) - glm::vec2(vertex_array[i1].texCoord.u, vertex_array[i1].texCoord.v);

// calculate tangent.
float f = 1.0f / (deltaUV1.x * deltaUV2.y - deltaUV2.x * deltaUV1.y);
glm::vec3 tangent = f * (deltaUV2.y * edge1 - deltaUV1.y * edge2);

// calculate bitangent.
glm::vec3 bitangent = f * (-deltaUV2.x * edge1 + deltaUV1.x * edge2);

vertex_array[i1].tangent = tangent;
vertex_array[i2].tangent = tangent;
vertex_array[i3].tangent = tangent;

vertex_array[i1].bitangent = bitangent;
vertex_array[i2].bitangent = bitangent;
vertex_array[i3].bitangent = bitangent;
}
}


Since it worked for the tutorial, I thought I can extrapolate this to my vertex arrays and it should work these to - but it didn't, as you can see in the pictures below. My own plane (500x500 vertices) lit by a single point light and using this normal map, it looks quite off:

It seems that the normal mapping is "ok" for one half of the plane, the other half looks like it lays in darkness.

Similar problem for the cube with the brick wall (using this normal map):

I am not sure if it matters, but these are the vertices used for the cube:

static vertex cube_vertices[] = {
// positions             // normals             // TexCoord    // color
// back (red)
{{-0.5f, -0.5f, -0.5f},  { 0.0f,  0.0f, -1.0f}, {0.0f, 0.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.0f}},
{{ 0.5f, -0.5f, -0.5f},  { 0.0f,  0.0f, -1.0f}, {1.0f, 0.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.0f}},
{{ 0.5f,  0.5f, -0.5f},  { 0.0f,  0.0f, -1.0f}, {1.0f, 1.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.0f}},
{{-0.5f,  0.5f, -0.5f},  { 0.0f,  0.0f, -1.0f}, {0.0f, 1.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.0f}},
// front (green)
{{-0.5f, -0.5f,  0.5f},  { 0.0f,  0.0f,  1.0f}, {0.0f, 0.0f}, {0.0f, 1.0f, 0.0f}, {0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.0f}},
{{ 0.5f, -0.5f,  0.5f},  { 0.0f,  0.0f,  1.0f}, {1.0f, 0.0f}, {0.0f, 1.0f, 0.0f}, {0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.0f}},
{{ 0.5f,  0.5f,  0.5f},  { 0.0f,  0.0f,  1.0f}, {1.0f, 1.0f}, {0.0f, 1.0f, 0.0f}, {0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.0f}},
{{-0.5f,  0.5f,  0.5f},  { 0.0f,  0.0f,  1.0f}, {0.0f, 1.0f}, {0.0f, 1.0f, 0.0f}, {0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.0f}},
// left (blue)
{{-0.5f, -0.5f, -0.5f},  {-1.0f,  0.0f,  0.0f}, {0.0f, 0.0f}, {0.0f, 0.0f, 1.0f}, {0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.0f}},
{{-0.5f, -0.5f,  0.5f},  {-1.0f,  0.0f,  0.0f}, {1.0f, 0.0f}, {0.0f, 0.0f, 1.0f}, {0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.0f}},
{{-0.5f,  0.5f,  0.5f},  {-1.0f,  0.0f,  0.0f}, {1.0f, 1.0f}, {0.0f, 0.0f, 1.0f}, {0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.0f}},
{{-0.5f,  0.5f, -0.5f},  {-1.0f,  0.0f,  0.0f}, {0.0f, 1.0f}, {0.0f, 0.0f, 1.0f}, {0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.0f}},
// right (yellow)
{{ 0.5f, -0.5f, -0.5f},  { 1.0f,  0.0f,  0.0f}, {0.0f, 0.0f}, {1.0f, 1.0f, 0.0f}, {0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.0f}},
{{ 0.5f, -0.5f,  0.5f},  { 1.0f,  0.0f,  0.0f}, {1.0f, 0.0f}, {1.0f, 1.0f, 0.0f}, {0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.0f}},
{{ 0.5f,  0.5f,  0.5f},  { 1.0f,  0.0f,  0.0f}, {1.0f, 1.0f}, {1.0f, 1.0f, 0.0f}, {0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.0f}},
{{ 0.5f,  0.5f, -0.5f},  { 1.0f,  0.0f,  0.0f}, {0.0f, 1.0f}, {1.0f, 1.0f, 0.0f}, {0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.0f}},
// underside (magenta)
{{-0.5f, -0.5f, -0.5f},  { 0.0f, -1.0f,  0.0f}, {0.0f, 0.0f}, {1.0f, 0.0f, 1.0f}, {0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.0f}},
{{ 0.5f, -0.5f, -0.5f},  { 0.0f, -1.0f,  0.0f}, {1.0f, 0.0f}, {1.0f, 0.0f, 1.0f}, {0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.0f}},
{{ 0.5f, -0.5f,  0.5f},  { 0.0f, -1.0f,  0.0f}, {1.0f, 1.0f}, {1.0f, 0.0f, 1.0f}, {0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.0f}},
{{-0.5f, -0.5f,  0.5f},  { 0.0f, -1.0f,  0.0f}, {0.0f, 1.0f}, {1.0f, 0.0f, 1.0f}, {0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.0f}},
// upperside (cyan)
{{-0.5f,  0.5f, -0.5f},  { 0.0f,  1.0f,  0.0f}, {0.0f, 0.0f}, {0.0f, 1.0f, 1.0f}, {0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.0f}},
{{ 0.5f,  0.5f, -0.5f},  { 0.0f,  1.0f,  0.0f}, {1.0f, 0.0f}, {0.0f, 1.0f, 1.0f}, {0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.0f}},
{{ 0.5f,  0.5f,  0.5f},  { 0.0f,  1.0f,  0.0f}, {1.0f, 1.0f}, {0.0f, 1.0f, 1.0f}, {0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.0f}},
{{-0.5f,  0.5f,  0.5f},  { 0.0f,  1.0f,  0.0f}, {0.0f, 1.0f}, {0.0f, 1.0f, 1.0f}, {0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.0f}}
};


And this is my algorithm to generate the plane:

my3DEngineObjectPlane::my3DEngineObjectPlane(GLuint xRes, GLuint yRes) {
if (xRes > 1 && yRes > 1) {
vertex* vertices = new vertex[xRes * yRes]; //memory for vertices

unsigned int index = 0;

// create vertices
for (unsigned int y = 0; y < yRes; y++) {
for (unsigned int x = 0; x < xRes; x++) {

vertices[index].pos.x = ((GLfloat)x / (GLfloat)(xRes - 1)) - 0.5f; // -0.5 for centering the origin of the plane in the center of the plane
vertices[index].pos.y = 0.0f;
vertices[index].pos.z = ((GLfloat)y / (GLfloat)(yRes - 1)) - 0.5f;

vertices[index].color = glm::vec3(1.0f, 0.0f, 1.0f);

vertices[index].texCoord.u = ((GLfloat)x / (GLfloat)(xRes-1));
vertices[index].texCoord.v = ((GLfloat)y / (GLfloat)(yRes-1));

vertices[index].normal = glm::vec3(0.0f, 1.0f, 0.0f);

vertices[index].tangent = glm::vec3(0.0f, 0.0f, 0.0f);
vertices[index].bitangent = glm::vec3(0.0f, 0.0f, 0.0f);
index++;
}
}

// create indices for GL_TRIANGLES
GLuint* indices = new GLuint[(yRes - 1) * (xRes - 1) * 2 * 3]; // memory for indices - 2 triangles per square, 3 indices per triangle
index = 0;
for (unsigned int y = 0; y < yRes - 1; y++) {
for (unsigned int x = 0; x < xRes - 1; x++) {
indices[index++] = yRes * y + x;
indices[index++] = yRes * y + x + xRes;
indices[index++] = yRes * y + x + xRes + 1;

indices[index++] = yRes * y + x;
indices[index++] = yRes * y + x + xRes + 1;
indices[index++] = yRes * y + x + 1;
}
}

size_t vertex_size = (size_t)xRes * (size_t)yRes * sizeof(vertex);
size_t index_size = (size_t)((xRes - 1) * (yRes - 1) * 2 * 3) * sizeof(GLuint);

calculateTangentsIndexed(vertices, (size_t)(xRes*yRes), indices, (size_t)((yRes - 1) * (xRes - 1) * 2 * 3));

addVertexDataIndexed((GLfloat*)vertices, indices, vertex_size, index_size, GL_TRIANGLES, true, true, true, true);

if (vertices != nullptr) delete[] vertices;
if (indices != nullptr) delete[] indices;
}
}


I desperatly need some help from far more seasoned people then me here. It seems I am close, but missing some crucial detail. I first thought, that my base normals are off, but as you can see at the cube, the yellow normals seem alright for me. However, for the plane the normals seem to be off:

When I visualize the tangent from the sahder it looks like this:

TangentViewPos - changes with the camera movement:

TangentFragPos - something seems off here:

TangentLightPos - something is definitly off here:

• I found the Problem I had with the Plane. I organised my vertex arry as follows: pos, normal, tex coords, col, tangent and bitangent. unfortunatly I defined the struct used in the vertex buffer array as follows: pos, col, tex coords, normal, tangent and bitangent. Therefore the color values were treated as normals and vice versa --> the normals in the plane were messed up. After fixing the issue the normal mapping for my plane works like a charme. The issue with the cube still exists, but this seems to be another bug, the normals of the cube look fine and the lighting works on the cube. Commented Mar 12 at 19:04
• I have now also found the problem with the cube. In fact it was not a problem of the cube, the position of the light was not transferred correctly to the shader. Commented Mar 14 at 12:24