0
\$\begingroup\$

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 (500x500 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

\$\endgroup\$
2
  • \$\begingroup\$ 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. \$\endgroup\$
    – tmctiger
    Commented Mar 12 at 19:04
  • \$\begingroup\$ 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. \$\endgroup\$
    – tmctiger
    Commented Mar 14 at 12:24

1 Answer 1

0
\$\begingroup\$

I have found all my problems in the code:

The first issue with normal mapping not working at all was that my vertex array was organized differently to what I told openGL. Therefore openGL interpreted my vertex normals as color and vice versa.

The second issue with the cube, was that the light position was not updated correctly resulting that the light was inside the cube and the cube was not lit correctly. I only noticed the bug when I started to try to move the light and nothing in the scene changed according to lighting.

\$\endgroup\$

You must log in to answer this question.

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