I'm trying to do bump mapping, and I'm getting fairly close at achieving my goal. I miss one crucial piece of my puzzle to have bump mapped effect in my scene; I need to transform the normals from my bump map to match the direction my faces are pointing.

This is my scene rendered with my vertex normals: Scene rendered with normals

This is my scene rendered with the bump map: Scene rendered with bump map

The last image is wrong, because all samples from the bump map are rendered relative to as if they are pointing upforwards from the ground. What I need to do, is transform the samples from the bump map to be relative to the face normals.

Now, I have tried researching how to achieve this for a while - I've been told that if I create a TNB matrix, and multiply the sample with it - things should behave like I want. This is however not the behaviour I am getting.

This is my vertex shader:

uniform mat4 u_projectionMatrix;
uniform mat4 u_viewMatrix;
uniform mat4 u_modelMatrix;
attribute vec3 a_position;
attribute vec2 aTextureCoord;
attribute vec3 aNormalCoord;
attribute vec3 aTangentCoord;
attribute vec3 aBitangentCoord;
varying vec2 vTextureCoord;
varying vec3 vNormalCoord;
varying vec3 vVertexCoord;
varying mat3 vTNBMatrix;

void main()
    vec4 pos = vec4(a_position, 1.0);
    vTNBMatrix = mat3(

    gl_Position = u_projectionMatrix * u_viewMatrix * pos;
    vTextureCoord = aTextureCoord;
    vNormalCoord = aNormalCoord;
    vVertexCoord = a_position;

And this is my fragment shader:

varying vec2 vTextureCoord;
varying vec3 vNormalCoord;
varying vec3 vVertexCoord;
varying mat3 vTNBMatrix;

uniform sampler2D mytex;
uniform sampler2D mynormal;

void main()
    vec3 nt = normalize(texture2D(mynormal, vTextureCoord.st).rgb * 2.0 - 1.0);

    vec3 lightpos = vec3(15.0, 26.5, 0.5);
    vec3 L = normalize(lightpos - vVertexCoord);

    vec3 finalNormal = nt * vTNBMatrix;

    vec3 ld = vec3(1.0, 1.0, 1.0) * max(dot(finalNormal, L), 0.0);

    ld = clamp(ld, 0.0, 1.0);

    gl_FragColor = vec4(texture2D(mytex, vTextureCoord.st).rgb * ld, 1.0);

Which are giving me the following, undesireable result:

Scene rendered with the attached vertexes

Would anyone be so kind and give me some pointers at what I'm doing wrong?


These are the textures I'm using for this

Normal map:

My Normal map


My texture

  • \$\begingroup\$ The second result image doesn't look entirely wrong. Could you provide the bump map image used for this? \$\endgroup\$ – danijar Apr 6 '13 at 8:13
  • \$\begingroup\$ Added! But yes, you're right; The second image looks almost right - it renders all the fragments. But since the bump map is all set in tangent space, I need to transform the normals from the bump map to fit with the walls. I've had some success with using reflect(), but there are still something that's quite off - it doesn't take tangents into account so it only works on some faces. \$\endgroup\$ – Kvisle Apr 6 '13 at 10:39
  • \$\begingroup\$ If you want to implement normal mapping, your normal texture need to be in normal space. Makes sense, doesn't it? You should check that your normal map isn't in tangent space. The color indicating no change should be completely blue if you have Y up since it represents the vector 0, 0, 1. But I haven't implemented normal mapping myself, so this is only my assumption. But the are many tutorial on the net. \$\endgroup\$ – danijar Apr 6 '13 at 12:11

Your problem is in your Light vector calculation.

vec3 lightpos = vec3(15.0, 26.5, 0.5);
vec3 L = normalize(lightpos - vVertexCoord);

Here lightpos is in World space, whilst vVertexCoord in is Object space. You need all your operands in the same space.

Furthermore your TBN matrix(its not TNB) is wrong., it should be as follows:

mat3 TBNmatrix = transpose( mat3( Tangent, Bitangent, Normal ) );

This is the transformation from Tangent space to Eye(camera) space. but we need the inverse of this transform, because we want to transform from eye space to tangent space(because the textured normal is in tangent space). To do this we can transpose it to get the final matrix we want.

Additionally you need to transform the Light vector from Eye space to Tangent space too. Here is the final shader code to clarify what I mean(I took it from my working project, so it should be correct ). It moves most of the work to the Vertex shader.


uniform mat4 u_projectionMatrix;
uniform mat4 u_viewMatrix;
uniform mat4 u_modelMatrix;
uniform vec3 W_LightPos;

attribute vec3 in_Position;
attribute vec2 in_TexCoord;
attribute vec3 in_Normal;
attribute vec3 in_Tangent;
attribute vec3 in_Bitangent;

varying vec2 ex_TexCoord;
varying vec3 ex_LightVec;

void main()

    mat4 MVMatrix = u_viewMatrix * u_modelMatrix;
    mat4 MVPMatrix = u_projectionMatrix * MVMatrix;
    //Normalmatrix should be passed as uniform, calculated on clientside(in your program)
    mat3 NormalMatrix = mat3( inverse( MVMatrix ) );
    vec4 E_VertexPos = MVMatrix * vec4(in_Position, 1.0);
    vec4 E_LightPos  = u_viewMatrix * vec4(W_LightPos, 1.0);
    vec3 E_LightVec  = vec3( E_LightPos - E_VertexPos );

    vec3 E_Normal   = normalize( NormalMatrix * in_Normal );
    vec3 E_Tangent  = normalize( NormalMatrix * in_Tangent );
    vec3 E_BiTangent = normalize(cross( E_Normal, E_Tangent ));

    // Eye -> TBN matrix
    // no need to inverse, transpose will do as we wont do non-uniform scaling
    mat3 E_TBN = transpose( mat3( E_Tangent, E_BiTangent, E_Normal ) );

    vec3 T_LightVec = normalize( E_TBN * E_LightVec );
    vec3 T_Normal = normalize( E_TBN * E_Normal );

    ex_LightVec = T_LightVec;
    ex_TexCoord = in_TexCoord;

    gl_Position = MVPMatrix * vec4(in_Position, 1.0);


uniform sampler2D DiffuseMap;
uniform sampler2D NormalMap;

attribute vec2 ex_TexCoord;
attribute vec3 ex_LightVec;

void main()
    vec3 DiffuseColor = texture2D( DiffuseMap, ex_TexCoord ).rgb;

    //get normal from map, and rescale from [0,1] to [-1,1]
    vec3 Ntex = texture( NormalMap, ex_TexCoord ).rgb;
    N = Ntex*2.0 - 1.0;
    N = normalize( N );
    vec3 L = normalize( ex_LightVec );

    float LambertFactor = max( dot( N, L ), 0.0 );

    out_Color = vec4( DiffuseColor*LambertFactor, 1.0 );
  • 1
    \$\begingroup\$ Wow! First; Thanks for a very helpful reply. Not only have you shed a light on several things, but you've re-factored the shader code. I haven't reached the goal yet, though; To make the shaders compile, I made the following changes: gist.github.com/kvisle/22310f3553804f74bd79 -- and I've adapted my code to use the correct uniforms/attributes. I get an image if I leave the LambertFactor out in the fragment shader. Otherwise it's all black. I've created a NormalMatrix and passed it as a uniform with GLM: glm::inverse(glm::mat3(View * Model)); \$\endgroup\$ – Kvisle Apr 8 '13 at 20:38
  • \$\begingroup\$ It looks like E_LightVec turns completely black when I multiply it with E_TBN. Any idea what's broken? \$\endgroup\$ – Kvisle Apr 8 '13 at 20:39
  • \$\begingroup\$ Edited the answer, to include the calculation for E_LightVec(mistakenly left out when first posted). In the gist you sent, you calculate the u_normalmatrix on the clientside, but you use NormalMatrix = mat3( MVMatrix ) for the calculations. \$\endgroup\$ – akaltar Apr 15 '13 at 11:20

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.