Implementing Normal Mapping using OpenGL/GLSL

13,646

That normal map is in tangent-space, but you are treating it as object-space.

You need a bitangent and/or tangent vector per-vertex in addition to your normal in order to form the basis to perform transformation into and out of tangent-space. This matrix is often referred to as simply TBN.

You have two options here:

  1. Transform all of your lighting direction vectors into tangent-space

    • Useful for forward-shading, can be done in a vertex shader
  2. Transform your normal map from tangent-space back to view-space

    • Required by deferred-shading, must be done in fragment shader

Both options require the construction of a TBN matrix, and if your tangent-space basis is orthogonal (modeling software like Assimp can be configured to do this for you) you can transpose the TBN matrix to do either one.

You are implementing forward-shading, so solution #1 is the approach you should take.


Below is a rough overview of the necessary steps for solution #1. Ordinarily you would do the calculation of the lighting direction vector in the vertex shader for better performance.

Vertex Shader to Transform of Lighting Vectors into Tangent-space:

attribute vec3 tangent;
attribute vec3 bitangent;

varying vec3 N;
varying vec3 V;
varying vec3 E;

varying vec3 T;
varying vec3 B;

void main()
{
    N = normalize(gl_NormalMatrix*gl_Normal);
    V = vec3(gl_ModelViewMatrix*gl_Vertex);
    E = normalize(-V);

    T = normalize(gl_NormalMatrix*tangent);
    B = normalize(gl_NormalMatrix*bitangent);

    gl_TexCoord[0] = gl_MultiTexCoord0;
    gl_Position = gl_ModelViewProjectionMatrix*gl_Vertex;
}

Fragment Shader to Transform Lighting Vectors into Tangent-space:

varying vec3 N;
varying vec3 V;
varying vec3 E;

varying vec3 B;
varying vec3 T;

uniform sampler2D textureUnit;
uniform sampler2D normalTextureUnit;
uniform vec4 TexColor;

#define MAX_LIGHTS 1

void main()
{
    // Construct Tangent Space Basis
    mat3 TBN = mat3 (T, B, N);

    vec3 normal = normalize (texture2D(normalTextureUnit,gl_TexCoord[0].st).xyz*2.0 - 1.0);

    vec4 color = vec4(0,0,0,0);
    for(int i = 0; i < MAX_LIGHTS; i++)
    {
        vec4 lightPos = gl_LightSource[i].position;
        vec3 L = lightPos.w > 0 ? lightPos.xyz - V : lightPos;

        L *= TBN; // Transform into tangent-space

        float dist = length(L);
        L = normalize(L);

        float NdotL = max(dot(L,N),0.0);
        if(NdotL > 0)
        {
            float att = 1.0;
            if(lightPos.w > 0)
            {
                att = 1.0/ (gl_LightSource[i].constantAttenuation +
                gl_LightSource[i].linearAttenuation * dist +
                gl_LightSource[i].quadraticAttenuation * dist * dist);
            }

            vec4 diffuse =  clamp(att*NdotL*gl_FrontLightProduct[i].diffuse,0,1);
            color += att*gl_FrontLightProduct[i].ambient + diffuse;
        }
    }

    vec4 textureColor = texture2D(textureUnit, vec2(gl_TexCoord[0]));
    gl_FragColor = TexColor*textureColor + gl_FrontLightModelProduct.sceneColor + color;
}

There is a tutorial here that should fill in the gaps and explain how to compute tangent and bitangent.

Share:
13,646
haksist
Author by

haksist

Updated on June 04, 2022

Comments

  • haksist
    haksist almost 2 years

    I'm learning GLSL and trying to implement some lighting and mapping tricks. I'm working with ShaderDesigner tool. After coding normal mapping I recognized that my model illumination looks not real. Here is my code and some pictures. If it possible tell me what is my problem.

    Vertex Shader

    #define MAX_LIGHTS 1
    
    struct LightProps
    {
        vec3 direction[MAX_LIGHTS];
    };
    
    attribute vec3 tangent;
    attribute vec3 bitangent;
    
    varying LightProps lights;
    
    void main()
    {
        vec3 N = normalize(gl_NormalMatrix*gl_Normal);
        vec3 T = normalize(gl_NormalMatrix*tangent);
        vec3 B = normalize(gl_NormalMatrix*bitangent);
    
        mat3 TBNMatrix = mat3(T,B,N);
    
        vec4 vertex = gl_ModelViewMatrix*gl_Vertex;
        for(int i = 0; i < MAX_LIGHTS; i++)
        {
            vec4 lightPos = gl_LightSource[i].position;
            lights.direction[i] = vec3(lightPos.w > 0 ? lightPos-vertex : lightPos);
            lights.direction[i] *= TBNMatrix;
        }
    
        gl_TexCoord[0] = gl_MultiTexCoord0;
        gl_Position = gl_ModelViewProjectionMatrix*gl_Vertex;
    } 
    

    Fragment Shader

    #define MAX_LIGHTS 1
    struct LightProps
    {
        vec3 direction[MAX_LIGHTS];
    };
    
    uniform sampler2D textureUnit;
    uniform sampler2D normalTextureUnit;
    uniform vec4 TexColor;
    
    varying LightProps lights;
    
    void main()
    {
        vec3 N = normalize(texture2D(normalTextureUnit,gl_TexCoord[0].st).rgb*2.0-1.0);
    
        vec4 color = vec4(0,0,0,0);
        for(int i = 0; i < MAX_LIGHTS; i++)
        {
            vec3 L = lights.direction[i];
            float dist = length(L);
            L = normalize(L);
    
            float NdotL = max(dot(N,L),0.0);
    
            if(NdotL > 0)
            {
                float att = 1.0;
                if(gl_LightSource[i].position.w > 0)
                {
                    att = 1.0/ (gl_LightSource[i].constantAttenuation +
                    gl_LightSource[i].linearAttenuation * dist +
                    gl_LightSource[i].quadraticAttenuation * dist * dist);
                }
                
                vec4 ambient = gl_FrontLightProduct[i].ambient;
                vec4 diffuse = clamp(att*NdotL*gl_FrontLightProduct[i].diffuse,0,1);
            
                color += att*(ambient+diffuse);
            }
        }
    
        vec4 textureColor = texture2D(textureUnit, vec2(gl_TexCoord[0]));
        gl_FragColor = TexColor*textureColor + gl_FrontLightModelProduct.sceneColor + color;
    }
    

    I set TexColor to (0.3,0.3,0.3,1.0) and take screenshots:

    Screenshot

      Screenshot

    There is little bit lighting when I rotate camera and light to left, but when I rotate to right the plane got fully illuminated.I think there is something wrong because without normal mapping plane looks same from to sides. Here is normal texture. Thanks in advance.

    Normal Map:

      Normal Texture

  • Nolesh
    Nolesh almost 5 years
    What is lightPos.w and how can I obtain it?