Monday, May 20, 2024
HomeGame Developmentopengl - Having a problem with tangent/bitangent calculation for my meshes

opengl – 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:

Vertex shader:

#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;
   float quadratic;
   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);
}

Fragment shader:

#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;
    float quadratic;
    
    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;  
    
    //specular blinn shading:
    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 (500×500 vertices) lit by a single point light and using this normal map, it looks quite off:

enter image description here

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):

enter image description here

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:

enter image description here

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

TangentViewPos – changes with the camera movement:

enter image description here

TangentFragPos – something seems off here:

enter image description here

TangentLightPos – something is definitly off here:

enter image description here

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular

Recent Comments