3D C/C++ tutorials - OpenGL 2.1 - GLSL normal mapping
3D C/C++ tutorials -> OpenGL 2.1 -> GLSL normal mapping
Use for personal or educational purposes only. Commercial and other profit uses strictly prohibited. Exploitation of content on a website or in a publication prohibited.
To compile and run these tutorials some or all of these libraries are required: FreeImage 3.16.0, GLEW 1.11.0, GLUT 3.7.6 / GLUT for Dev-C++, GLM 0.9.5.4
GLSL normal mapping
Welcome to the GLSL normal mapping tutorial.
Before we show you our implementation of the normal mapping algorithm, we would like to inform you about an essential difference between a Direct3D and an OpenGL normal map.

Texture

Direct3D normal map

OpenGL normal map
Notice the greenish pixels in both normal maps. The normal map used in this tutorial is a Direct3D normal map and before it can be mapped on triangles, it must be converted from Direc3D space to OpenGL space.
In a forward renderer we have two options how to calculate lighting. Either we transform the light direction vector from world (or view) space to tangent space, or we transform normal from normal map from tangent space to world (or view) space.
In a deferred renderer we have no choice but to convert normal from normal map from tangent space to world (or view) space.
We decided to transform normal from normal map because it's easier to implement and to understand. And also because it's usable in a deferred renderer.
The first step is to calculate the tangent and bitangent vectors per triangle.
for(int t = 0; t < 12; t++)
{
    int ia = t * 3, ib = ia + 1, ic = ib + 1;

    vec3 vdab = VertexArray[ib].Vertex - VertexArray[ia].Vertex;
    vec3 vdac = VertexArray[ic].Vertex - VertexArray[ia].Vertex;

    vec2 tcdab = VertexArray[ib].TexCoord - VertexArray[ia].TexCoord;
    vec2 tcdac = VertexArray[ic].TexCoord - VertexArray[ia].TexCoord;

    float r = 1.0f / (tcdab.x * tcdac.y - tcdab.y * tcdac.x);

    ...

    vec3 Tangent = normalize((vdab * tcdac.y  - vdac * tcdab.y) * r);
    vec3 Bitangent = normalize((vdac * tcdab.x  - vdab * tcdac.x) * r);

    ...
}
At this point we have 2 vectors of unit length perpendicular to each other. We didn't explicitly define normals, so we need to calculate normals per triangle too.
for(int t = 0; t < 12; t++)
{
    ...

    vec3 Normal = normalize(cross(vdab, vdac));

    ...
}
Now we have 3 vectors of unit length perpendicular to each other. The common practice is to provide this data per vertex. In this tutorial we render a simple rectangular prism object, so the tangent, bitangent and normal vectors are identical for all 3 vertices of a triangle.
for(int t = 0; t < 12; t++)
{
    ...

    VertexArray[ia].Normal = Normal;
    VertexArray[ia].Tangent = Tangent;
    VertexArray[ia].Bitangent = Bitangent;

    VertexArray[ib].Normal = Normal;
    VertexArray[ib].Tangent = Tangent;
    VertexArray[ib].Bitangent = Bitangent;

    VertexArray[ic].Normal = Normal;
    VertexArray[ic].Tangent = Tangent;
    VertexArray[ic].Bitangent = Bitangent;
}
With this 3 vectors we can construct the TBN matrix in the fragment shader.
mat3x3 TBN = mat3x3(Tangent, Bitangent, Normal);
The TBN matrix is in it's substance an arbitrary axis rotation matrix and it defines the transformation from tangent space to object space.
If we wanted to render an object (for example loaded from a file and that object was) made of curved surfaces, it would probably include per vertex normals. In that case the per vertex normals of a triangle wouldn't necessarily have to be identical and they wouldn't necessarily have to be perpendicular to the per triangle tangent and bitangent vectors. After calculating the per triangle tangent and bitangent vectors, we would have to calculate the per vertex tangent vector perpendicular to the per vertex normal vector using the (first step of the) Gram-Schmidt orthogonalisation process and then we would have to calculate the per vertex bitangent vector perpendicular to the per vertex tangent and normal vectors using the cross product.
Tangent = normalize(Tangent - Normal * dot(Normal, Tangent));
Bitangent = cross(Normal, Tangent);
The tangent and bitangent vectors are passed on to vertex shader as vertex attributes.
attribute vec3 att_Tangent, att_Bitangent;
In GLSL 1.2 we don't have the option of using the "layout(location = ) in ..." convention, so we have to get the locations of the attributes using the glGetAttribLocation function in the C/C++ code first.
NormalMapping.AttribLocations = new GLuint[2];
NormalMapping.AttribLocations[0] = glGetAttribLocation(NormalMapping, "att_Tangent");
NormalMapping.AttribLocations[1] = glGetAttribLocation(NormalMapping, "att_Bitangent");
When rendering the object we enable the vertex attribute array using the glEnableVertexAttribArray function and we set the array using the glVertexAttribPointer function. You may know the glEnableVertexAttribArray and glVertexAttribPointer functions from the OpenGL 3.3 tutorials, but they are originally OpenGL 2.0 functions. This fact is one of the reasons, why you should learn the OpenGL 2.1 and older way of rendering before you jump to OpenGL 3.3.
glEnableVertexAttribArray(NormalMapping.AttribLocations[0]);
glVertexAttribPointer(NormalMapping.AttribLocations[0], 3, GL_FLOAT, GL_FALSE, 68, (void*)44);

glEnableVertexAttribArray(NormalMapping.AttribLocations[1]);
glVertexAttribPointer(NormalMapping.AttribLocations[1], 3, GL_FLOAT, GL_FALSE, 68, (void*)56);
If normal mapping is enabled, in the glsl_normal_mapping.vs vertex shader we only pass on the tangent, bitangent and normal vectors to the glsl_normal_mapping.fs fragment shader. The normal matrix is calculated in the C/C++ code as transpose of the inverse of the upper left 3x3 submatrix of the model matrix, so the lighting calculations in the glsl_normal_mapping.fs fragment shader are done in world space. The tangent, bitangent and normal vectors must be normalized in fragment shader because of two reasons. The normal matrix can contain inverse scaling transformation and the tangent, bitangent and normal vectors can be per vertex data. With the normalized tangent, bitangent and normal vectors we can construct the TBN matrix and use it to transform the normal from normal map from tangent space to object space. Then we have to use the normal matrix to transform normal from normal map from object space to world space.
opengl_21_tutorials_win32_framework.h
...

class CVertexArrayElement
{
public:
    vec3 Vertex;
    vec3 Color;
    vec2 TexCoord;
    vec3 Normal;
    vec3 Tangent;
    vec3 Bitangent;
};

...

class COpenGLRenderer
{
protected:
    int Width, Height;
    mat3x3 TangentMatrix, NormalMatrix;
    mat4x4 ModelMatrix, ViewMatrix, ProjectionMatrix;

protected:
    CTexture Texture, NormalMap;
    CShaderProgram NormalMapping;
    CVertexArrayElement *VertexArray;
    GLuint VertexBufferObject;

public:
    bool TexturingEnabled, NormalMappingEnabled, ShowEdges, ShowTBNs, ShowAxisGrid, Pause;

public:
    CString Text;

public:
    COpenGLRenderer();
    ~COpenGLRenderer();

    bool Init();
    void Render(float FrameTime);
    void Resize(int Width, int Height);
    void Destroy();
};

...
opengl_21_tutorials_win32_framework.cpp
...

COpenGLRenderer::COpenGLRenderer()
{
    TexturingEnabled = true;
    NormalMappingEnabled = true;
    ShowEdges = true;
    ShowTBNs = true;
    ShowAxisGrid = true;
    Pause = false;

    Camera.SetViewMatrixPointer(&ViewMatrix);
}

COpenGLRenderer::~COpenGLRenderer()
{
}

bool COpenGLRenderer::Init()
{
    CShaderProgram ConvertNormalMap;

    bool Error = false;

    Error |= !Texture.LoadTexture2D("texture.jpg");
    Error |= !NormalMap.LoadTexture2D("normal_map.jpg");

    Error |= !ConvertNormalMap.Load("convert_normal_map.vs", "convert_normal_map.fs");
    Error |= !NormalMapping.Load("normal_mapping.vs", "normal_mapping.fs");

    if(Error)
    {
        return false;
    }

    ConvertNormalMap.UniformLocations = new GLuint[1];
    ConvertNormalMap.UniformLocations[0] = glGetUniformLocation(ConvertNormalMap, "Conversion");

    NormalMapping.AttribLocations = new GLuint[2];
    NormalMapping.AttribLocations[0] = glGetAttribLocation(NormalMapping, "att_Tangent");
    NormalMapping.AttribLocations[1] = glGetAttribLocation(NormalMapping, "att_Bitangent");

    NormalMapping.UniformLocations = new GLuint[6];
    NormalMapping.UniformLocations[0] = glGetUniformLocation(NormalMapping, "ModelMatrix");
    NormalMapping.UniformLocations[1] = glGetUniformLocation(NormalMapping, "NormalMatrix");
    NormalMapping.UniformLocations[2] = glGetUniformLocation(NormalMapping, "TexturingEnabled");
    NormalMapping.UniformLocations[3] = glGetUniformLocation(NormalMapping, "NormalMappingEnabled");
    NormalMapping.UniformLocations[4] = glGetUniformLocation(NormalMapping, "LightPosition");
    NormalMapping.UniformLocations[5] = glGetUniformLocation(NormalMapping, "CameraPosition");

    glUseProgram(NormalMapping);
    glUniform1i(glGetUniformLocation(NormalMapping, "Texture"), 0);
    glUniform1i(glGetUniformLocation(NormalMapping, "NormalMap"), 1);
    glUseProgram(0);

    glViewport(0, 0, 512, 512);

    glUseProgram(ConvertNormalMap);

    glUniform3fv(ConvertNormalMap.UniformLocations[0], 1, &vec3(1.0f, -1.0f, 1.0f));

    glBindTexture(GL_TEXTURE_2D, NormalMap);

    glBegin(GL_QUADS);
        glVertex2f(0.0f, 0.0f);
        glVertex2f(1.0f, 0.0f);
        glVertex2f(1.0f, 1.0f);
        glVertex2f(0.0f, 1.0f);
    glEnd();

    glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, 512, 512);

    glBindTexture(GL_TEXTURE_2D, 0);

    glUseProgram(0);

    ConvertNormalMap.Destroy();

    VertexArray = new CVertexArrayElement[36];

    int i = 0;

    VertexArray[i].Vertex = vec3( 0.5f, -0.5f,  0.5f); VertexArray[i].Color = vec3(1.0f, 1.0f, 1.0f); VertexArray[i].TexCoord = vec2(0.0f, 0.0f); i++;
    VertexArray[i].Vertex = vec3( 0.5f, -0.5f, -0.5f); VertexArray[i].Color = vec3(1.0f, 1.0f, 1.0f); VertexArray[i].TexCoord = vec2(1.0f, 0.0f); i++;
    VertexArray[i].Vertex = vec3( 0.5f,  0.5f, -0.5f); VertexArray[i].Color = vec3(1.0f, 1.0f, 1.0f); VertexArray[i].TexCoord = vec2(1.0f, 1.0f); i++;

    VertexArray[i].Vertex = vec3( 0.5f,  0.5f, -0.5f); VertexArray[i].Color = vec3(1.0f, 1.0f, 1.0f); VertexArray[i].TexCoord = vec2(1.0f, 1.0f); i++;
    VertexArray[i].Vertex = vec3( 0.5f,  0.5f,  0.5f); VertexArray[i].Color = vec3(1.0f, 1.0f, 1.0f); VertexArray[i].TexCoord = vec2(0.0f, 1.0f); i++;
    VertexArray[i].Vertex = vec3( 0.5f, -0.5f,  0.5f); VertexArray[i].Color = vec3(1.0f, 1.0f, 1.0f); VertexArray[i].TexCoord = vec2(0.0f, 0.0f); i++;

    VertexArray[i].Vertex = vec3(-0.5f, -0.5f, -0.5f); VertexArray[i].Color = vec3(1.0f, 1.0f, 1.0f); VertexArray[i].TexCoord = vec2(0.0f, 0.0f); i++;
    VertexArray[i].Vertex = vec3(-0.5f, -0.5f,  0.5f); VertexArray[i].Color = vec3(1.0f, 1.0f, 1.0f); VertexArray[i].TexCoord = vec2(1.0f, 0.0f); i++;
    VertexArray[i].Vertex = vec3(-0.5f,  0.5f,  0.5f); VertexArray[i].Color = vec3(1.0f, 1.0f, 1.0f); VertexArray[i].TexCoord = vec2(1.0f, 1.0f); i++;

    VertexArray[i].Vertex = vec3(-0.5f,  0.5f,  0.5f); VertexArray[i].Color = vec3(1.0f, 1.0f, 1.0f); VertexArray[i].TexCoord = vec2(1.0f, 1.0f); i++;
    VertexArray[i].Vertex = vec3(-0.5f,  0.5f, -0.5f); VertexArray[i].Color = vec3(1.0f, 1.0f, 1.0f); VertexArray[i].TexCoord = vec2(0.0f, 1.0f); i++;
    VertexArray[i].Vertex = vec3(-0.5f, -0.5f, -0.5f); VertexArray[i].Color = vec3(1.0f, 1.0f, 1.0f); VertexArray[i].TexCoord = vec2(0.0f, 0.0f); i++;

    VertexArray[i].Vertex = vec3(-0.5f,  0.5f,  0.5f); VertexArray[i].Color = vec3(1.0f, 1.0f, 1.0f); VertexArray[i].TexCoord = vec2(0.0f, 0.0f); i++;
    VertexArray[i].Vertex = vec3( 0.5f,  0.5f,  0.5f); VertexArray[i].Color = vec3(1.0f, 1.0f, 1.0f); VertexArray[i].TexCoord = vec2(1.0f, 0.0f); i++;
    VertexArray[i].Vertex = vec3( 0.5f,  0.5f, -0.5f); VertexArray[i].Color = vec3(1.0f, 1.0f, 1.0f); VertexArray[i].TexCoord = vec2(1.0f, 1.0f); i++;

    VertexArray[i].Vertex = vec3( 0.5f,  0.5f, -0.5f); VertexArray[i].Color = vec3(1.0f, 1.0f, 1.0f); VertexArray[i].TexCoord = vec2(1.0f, 1.0f); i++;
    VertexArray[i].Vertex = vec3(-0.5f,  0.5f, -0.5f); VertexArray[i].Color = vec3(1.0f, 1.0f, 1.0f); VertexArray[i].TexCoord = vec2(0.0f, 1.0f); i++;
    VertexArray[i].Vertex = vec3(-0.5f,  0.5f,  0.5f); VertexArray[i].Color = vec3(1.0f, 1.0f, 1.0f); VertexArray[i].TexCoord = vec2(0.0f, 0.0f); i++;

    VertexArray[i].Vertex = vec3(-0.5f, -0.5f, -0.5f); VertexArray[i].Color = vec3(1.0f, 1.0f, 1.0f); VertexArray[i].TexCoord = vec2(0.0f, 0.0f); i++;
    VertexArray[i].Vertex = vec3( 0.5f, -0.5f, -0.5f); VertexArray[i].Color = vec3(1.0f, 1.0f, 1.0f); VertexArray[i].TexCoord = vec2(1.0f, 0.0f); i++;
    VertexArray[i].Vertex = vec3( 0.5f, -0.5f,  0.5f); VertexArray[i].Color = vec3(1.0f, 1.0f, 1.0f); VertexArray[i].TexCoord = vec2(1.0f, 1.0f); i++;

    VertexArray[i].Vertex = vec3( 0.5f, -0.5f,  0.5f); VertexArray[i].Color = vec3(1.0f, 1.0f, 1.0f); VertexArray[i].TexCoord = vec2(1.0f, 1.0f); i++;
    VertexArray[i].Vertex = vec3(-0.5f, -0.5f,  0.5f); VertexArray[i].Color = vec3(1.0f, 1.0f, 1.0f); VertexArray[i].TexCoord = vec2(0.0f, 1.0f); i++;
    VertexArray[i].Vertex = vec3(-0.5f, -0.5f, -0.5f); VertexArray[i].Color = vec3(1.0f, 1.0f, 1.0f); VertexArray[i].TexCoord = vec2(0.0f, 0.0f); i++;

    VertexArray[i].Vertex = vec3(-0.5f, -0.5f,  0.5f); VertexArray[i].Color = vec3(1.0f, 1.0f, 1.0f); VertexArray[i].TexCoord = vec2(0.0f, 0.0f); i++;
    VertexArray[i].Vertex = vec3( 0.5f, -0.5f,  0.5f); VertexArray[i].Color = vec3(1.0f, 1.0f, 1.0f); VertexArray[i].TexCoord = vec2(1.0f, 0.0f); i++;
    VertexArray[i].Vertex = vec3( 0.5f,  0.5f,  0.5f); VertexArray[i].Color = vec3(1.0f, 1.0f, 1.0f); VertexArray[i].TexCoord = vec2(1.0f, 1.0f); i++;

    VertexArray[i].Vertex = vec3( 0.5f,  0.5f,  0.5f); VertexArray[i].Color = vec3(1.0f, 1.0f, 1.0f); VertexArray[i].TexCoord = vec2(1.0f, 1.0f); i++;
    VertexArray[i].Vertex = vec3(-0.5f,  0.5f,  0.5f); VertexArray[i].Color = vec3(1.0f, 1.0f, 1.0f); VertexArray[i].TexCoord = vec2(0.0f, 1.0f); i++;
    VertexArray[i].Vertex = vec3(-0.5f, -0.5f,  0.5f); VertexArray[i].Color = vec3(1.0f, 1.0f, 1.0f); VertexArray[i].TexCoord = vec2(0.0f, 0.0f); i++;

    VertexArray[i].Vertex = vec3( 0.5f, -0.5f, -0.5f); VertexArray[i].Color = vec3(1.0f, 1.0f, 1.0f); VertexArray[i].TexCoord = vec2(0.0f, 0.0f); i++;
    VertexArray[i].Vertex = vec3(-0.5f, -0.5f, -0.5f); VertexArray[i].Color = vec3(1.0f, 1.0f, 1.0f); VertexArray[i].TexCoord = vec2(1.0f, 0.0f); i++;
    VertexArray[i].Vertex = vec3(-0.5f,  0.5f, -0.5f); VertexArray[i].Color = vec3(1.0f, 1.0f, 1.0f); VertexArray[i].TexCoord = vec2(1.0f, 1.0f); i++;

    VertexArray[i].Vertex = vec3(-0.5f,  0.5f, -0.5f); VertexArray[i].Color = vec3(1.0f, 1.0f, 1.0f); VertexArray[i].TexCoord = vec2(1.0f, 1.0f); i++;
    VertexArray[i].Vertex = vec3( 0.5f,  0.5f, -0.5f); VertexArray[i].Color = vec3(1.0f, 1.0f, 1.0f); VertexArray[i].TexCoord = vec2(0.0f, 1.0f); i++;
    VertexArray[i].Vertex = vec3( 0.5f, -0.5f, -0.5f); VertexArray[i].Color = vec3(1.0f, 1.0f, 1.0f); VertexArray[i].TexCoord = vec2(0.0f, 0.0f); i++;

    for(int t = 0; t < 12; t++)
    {
        int ia = t * 3, ib = ia + 1, ic = ib + 1;

        vec3 vdab = VertexArray[ib].Vertex - VertexArray[ia].Vertex;
        vec3 vdac = VertexArray[ic].Vertex - VertexArray[ia].Vertex;

        vec2 tcdab = VertexArray[ib].TexCoord - VertexArray[ia].TexCoord;
        vec2 tcdac = VertexArray[ic].TexCoord - VertexArray[ia].TexCoord;

        float r = 1.0f / (tcdab.x * tcdac.y - tcdab.y * tcdac.x);

        vec3 Normal = normalize(cross(vdab, vdac));
        vec3 Tangent = normalize((vdab * tcdac.y  - vdac * tcdab.y) * r);
        vec3 Bitangent = normalize((vdac * tcdab.x  - vdab * tcdac.x) * r);

        VertexArray[ia].Normal = Normal;
        VertexArray[ia].Tangent = Tangent;
        VertexArray[ia].Bitangent = Bitangent;

        VertexArray[ib].Normal = Normal;
        VertexArray[ib].Tangent = Tangent;
        VertexArray[ib].Bitangent = Bitangent;

        VertexArray[ic].Normal = Normal;
        VertexArray[ic].Tangent = Tangent;
        VertexArray[ic].Bitangent = Bitangent;
    }

    glGenBuffers(1, &VertexBufferObject);

    glBindBuffer(GL_ARRAY_BUFFER, VertexBufferObject);
    glBufferData(GL_ARRAY_BUFFER, 36 * 68, VertexArray, GL_STATIC_DRAW);
    glBindBuffer(GL_ARRAY_BUFFER, 0);

    Camera.Look(vec3(2.0f, 1.75f, 2.0f), vec3(0.5f, 0.625f, 0.5f));

    return true;
}

void COpenGLRenderer::Render(float FrameTime)
{
    static float a = 0.0f;

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glEnable(GL_DEPTH_TEST);
    glEnable(GL_CULL_FACE);

    glMatrixMode(GL_MODELVIEW);
    glLoadMatrixf(&ViewMatrix);

    if(ShowAxisGrid)
    {
        glLineWidth(2.0f);

        glBegin(GL_LINES);

        glColor3f(1.0f, 0.0f, 0.0f);

        glVertex3f(0.0f, 0.0f, 0.0f); glVertex3f(1.0f, 0.0f, 0.0f);
        glVertex3f(1.0f, 0.1f, 0.0f); glVertex3f(1.1f, -0.1f, 0.0f);
        glVertex3f(1.1f, 0.1f, 0.0f); glVertex3f(1.0f, -0.1f, 0.0f);

        glColor3f(0.0f, 1.0f, 0.0f);

        glVertex3f(0.0f, 0.0f, 0.0f); glVertex3f(0.0f, 1.0f, 0.0f);
        glVertex3f(-0.05f, 1.25f, 0.0f); glVertex3f(0.0f, 1.15f, 0.0f);
        glVertex3f(0.05f,1.25f, 0.0f); glVertex3f(0.0f, 1.15f, 0.0f);
        glVertex3f(0.0f,1.15f, 0.0f); glVertex3f(0.0f, 1.05f, 0.0f);

        glColor3f(0.0f, 0.0f, 1.0f);

        glVertex3f(0.0f,0.0f,0.0f); glVertex3f(0.0f, 0.0f, 1.0f);
        glVertex3f(-0.05f,0.1f,1.05f); glVertex3f(0.05f, 0.1f, 1.05f);
        glVertex3f(0.05f,0.1f,1.05f); glVertex3f(-0.05f, -0.1f, 1.05f);
        glVertex3f(-0.05f,-0.1f,1.05f); glVertex3f(0.05f, -0.1f, 1.05f);

        glEnd();

        glLineWidth(1.0f);

        glColor3f(1.0f, 1.0f, 1.0f);

        glBegin(GL_LINES);

        float d = 50.0f;

        for(float i = -d; i <= d; i += 1.0f)
        {
            glVertex3f(i, 0.0f, -d);
            glVertex3f(i, 0.0f, d);
            glVertex3f(-d, 0.0f, i);
            glVertex3f(d, 0.0f, i);
        }

        glEnd();
    }

    glMultMatrixf(&ModelMatrix);

    glUseProgram(NormalMapping);

    glUniformMatrix4fv(NormalMapping.UniformLocations[0], 1, GL_FALSE, &ModelMatrix);
    glUniformMatrix3fv(NormalMapping.UniformLocations[1], 1, GL_FALSE, &NormalMatrix);
    glUniform1i(NormalMapping.UniformLocations[2], TexturingEnabled);
    glUniform1i(NormalMapping.UniformLocations[3], NormalMappingEnabled);
    glUniform3fv(NormalMapping.UniformLocations[4], 1, &vec3(0.5f, 1.5f, 0.5f));
    glUniform3fv(NormalMapping.UniformLocations[5], 1, &Camera.Position);

    glBindBuffer(GL_ARRAY_BUFFER, VertexBufferObject);

    glEnableClientState(GL_VERTEX_ARRAY);
    glVertexPointer(3, GL_FLOAT, 68, (void*)0);

    glEnableClientState(GL_COLOR_ARRAY);
    glColorPointer(3, GL_FLOAT, 68, (void*)12);

    glEnableClientState(GL_TEXTURE_COORD_ARRAY);
    glTexCoordPointer(2, GL_FLOAT, 68, (void*)24);

    glEnableClientState(GL_NORMAL_ARRAY);
    glNormalPointer(GL_FLOAT, 68, (void*)32);

    glEnableVertexAttribArray(NormalMapping.AttribLocations[0]);
    glVertexAttribPointer(NormalMapping.AttribLocations[0], 3, GL_FLOAT, GL_FALSE, 68, (void*)44);

    glEnableVertexAttribArray(NormalMapping.AttribLocations[1]);
    glVertexAttribPointer(NormalMapping.AttribLocations[1], 3, GL_FLOAT, GL_FALSE, 68, (void*)56);

    glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, Texture);
    glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, NormalMap);

    glDrawArrays(GL_TRIANGLES, 0, 36);

    glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, 0);
    glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, 0);

    glDisableVertexAttribArray(NormalMapping.AttribLocations[1]);
    glDisableVertexAttribArray(NormalMapping.AttribLocations[0]);
    glDisableClientState(GL_NORMAL_ARRAY);
    glDisableClientState(GL_TEXTURE_COORD_ARRAY);
    glDisableClientState(GL_COLOR_ARRAY);
    glDisableClientState(GL_VERTEX_ARRAY);

    glBindBuffer(GL_ARRAY_BUFFER, 0);

    glUseProgram(0);

    if(ShowEdges)
    {
        glMatrixMode(GL_MODELVIEW);
        glLoadMatrixf(&ViewMatrix);
        glMultMatrixf(&ModelMatrix);
    
        for(int t = 0; t < 12; t++)
        {
            int ia = t * 3, ib = ia + 1, ic = ib + 1;

            glColor3f(0.0f, 0.0f, 0.0f);

            glBegin(GL_LINES);
                glVertex3fv(&VertexArray[ia].Vertex); glVertex3fv(&VertexArray[ib].Vertex);
                glVertex3fv(&VertexArray[ib].Vertex); glVertex3fv(&VertexArray[ic].Vertex);
                glVertex3fv(&VertexArray[ic].Vertex); glVertex3fv(&VertexArray[ia].Vertex);
            glEnd();
        }
    }

    if(ShowTBNs)
    {
        glMatrixMode(GL_MODELVIEW);
        glLoadMatrixf(&ViewMatrix);

        for(int t = 0; t < 12; t++)
        {
            int ia = t * 3, ib = ia + 1, ic = ib + 1;

            vec3 VertexA = *(vec3*)&(ModelMatrix * vec4(VertexArray[ia].Vertex, 1.0f));
            vec3 VertexB = *(vec3*)&(ModelMatrix * vec4(VertexArray[ib].Vertex, 1.0f));
            vec3 VertexC = *(vec3*)&(ModelMatrix * vec4(VertexArray[ic].Vertex, 1.0f));

            glLineWidth(1.0f);

            vec3 Middle = (VertexA + VertexB + VertexC) / 3.0f;

            glBegin(GL_LINES);
                glColor3f(1.0f, 0.0f, 0.0f); glVertex3fv(&Middle); glVertex3fv(&(Middle + normalize(TangentMatrix * VertexArray[ia].Tangent) * 0.25f));
                glColor3f(0.0f, 1.0f, 0.0f); glVertex3fv(&Middle); glVertex3fv(&(Middle + normalize(TangentMatrix * VertexArray[ia].Bitangent) * 0.25f));
                glColor3f(0.0f, 0.0f, 1.0f); glVertex3fv(&Middle); glVertex3fv(&(Middle + normalize(NormalMatrix * VertexArray[ia].Normal) * 0.25f));
            glEnd();
        }
    }

    glDisable(GL_CULL_FACE);
    glDisable(GL_DEPTH_TEST);

    if(!Pause)
    {
        ModelMatrix = translate(0.5f, 0.5f, 0.5f) * rotate(a, vec3(0.0f, 1.0f, 0.0f)) * rotate(a, vec3(1.0f, 0.0f, 0.0f)) * scale(1.0f, 0.75f, 0.5f);
        TangentMatrix = mat3x3(ModelMatrix);
        NormalMatrix = transpose(inverse(TangentMatrix));

        a += 11.25f * FrameTime;
    }
}

void COpenGLRenderer::Resize(int Width, int Height)
{
    this->Width = Width;
    this->Height = Height;

    glViewport(0, 0, Width, Height);

    ProjectionMatrix = perspective(45.0f, (float)Width / (float)Height, 0.125f, 512.0f);

    glMatrixMode(GL_PROJECTION);
    glLoadMatrixf(&ProjectionMatrix);
}

void COpenGLRenderer::Destroy()
{
    Texture.Destroy();
    NormalMap.Destroy();

    NormalMapping.Destroy();

    delete [] VertexArray;

    glDeleteBuffers(1, &VertexBufferObject);
}

...

void COpenGLView::OnKeyDown(UINT Key)
{
    switch(Key)
    {
        case VK_F1:
            OpenGLRenderer.TexturingEnabled = !OpenGLRenderer.TexturingEnabled;
            break;

        case VK_F2:
            OpenGLRenderer.NormalMappingEnabled = !OpenGLRenderer.NormalMappingEnabled;
            break;

        case VK_F3:
            OpenGLRenderer.ShowEdges = !OpenGLRenderer.ShowEdges;
            break;

        case VK_F4:
            OpenGLRenderer.ShowTBNs = !OpenGLRenderer.ShowTBNs;
            break;

        case VK_F5:
            OpenGLRenderer.ShowAxisGrid = !OpenGLRenderer.ShowAxisGrid;
            break;

        case VK_SPACE:
            OpenGLRenderer.Pause = !OpenGLRenderer.Pause;
            break;
    }
}

...
convert_normal_map.vs
#version 120

void main()
{
    gl_TexCoord[0] = gl_Vertex;
    gl_Position = gl_Vertex * 2.0 - 1.0;
}
convert_normal_map.fs
#version 120

uniform sampler2D NormalMap;

uniform vec3 Conversion;

void main()
{
    vec3 Normal = normalize(texture2D(NormalMap, gl_TexCoord[0].st).rgb * 2.0 - 1.0);

    Normal = Normal * Conversion;

    gl_FragColor = vec4(Normal * 0.5 + 0.5, 1.0);
}
normal_mapping.vs
#version 120

uniform mat4x4 ModelMatrix;
uniform mat3x3 NormalMatrix;
uniform bool TexturingEnabled, NormalMappingEnabled;

attribute vec3 att_Tangent, att_Bitangent;

varying vec3 var_Position, var_Normal, var_Tangent, var_Bitangent;

void main()
{
    gl_FrontColor = gl_Color;

    if(TexturingEnabled || NormalMappingEnabled)
    {
        gl_TexCoord[0] = gl_MultiTexCoord0;
    }

    var_Position = (ModelMatrix * gl_Vertex).xyz;

    if(NormalMappingEnabled)
    {
        var_Normal = gl_Normal;
        var_Tangent = att_Tangent;
        var_Bitangent = att_Bitangent;
    }
    else
    {
        var_Normal = NormalMatrix * gl_Normal;
    }

    gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
normal_mapping.fs
#version 120

uniform sampler2D Texture, NormalMap;
uniform mat3x3 NormalMatrix;
uniform vec3 LightPosition, CameraPosition;
uniform bool TexturingEnabled, NormalMappingEnabled;

varying vec3 var_Position, var_Normal, var_Tangent, var_Bitangent;

void main()
{
    gl_FragColor = gl_Color;

    if(TexturingEnabled)
    {
        gl_FragColor.rgb *= texture2D(Texture, gl_TexCoord[0].st).rgb;
    }

    vec3 Normal = normalize(var_Normal);

    if(NormalMappingEnabled)
    {
        vec3 Tangent = normalize(var_Tangent);
        vec3 Bitangent = normalize(var_Bitangent);

        mat3x3 TBN = mat3x3(Tangent, Bitangent, Normal);

        Normal = normalize(NormalMatrix * (TBN * (texture2D(NormalMap, gl_TexCoord[0].st).rgb * 2.0 - 1.0)));
    }

    vec3 LightDirection = normalize(LightPosition - var_Position);

    float NdotLD = max(dot(Normal, LightDirection), 0.0);

    gl_FragColor.rgb *= 0.25 + 0.75 * NdotLD;

    vec3 CameraDirection = normalize(CameraPosition - var_Position);
    vec3 LightDirectionReflected = reflect(-LightDirection, Normal);

    float CDdotLDR = max(dot(CameraDirection, LightDirectionReflected), 0.0);

    gl_FragColor.rgb += pow(CDdotLDR, 128.0);
}
Download
glsl_normal_mapping.zip (Visual Studio 2008 Professional)
glsl_normal_mapping_devcpp4992.zip (Dev-C++ 4.9.9.2)

© 2010 - 2016 Bc. Michal Belanec, michalbelanec@centrum.sk
Last update June 25, 2016
OpenGL® is a registered trademark of Silicon Graphics Inc.