Vertex shader:

varying vec3 v; varying vec4 VaryingTexCoord0; varying vec3 vnormal; varying mat3 TBN; attribute vec3 tangent; void main(void) { vnormal = normalize(gl_NormalMatrix * gl_Normal); vec3 ntangent = normalize(gl_NormalMatrix * tangent); vec3 bitangent = cross(vnormal, ntangent); //get the bitangent perpendicular to the normal and tangent TBN = mat3(ntangent, bitangent, vnormal); //construct the TBN matrix v = vec3(gl_ModelViewMatrix * gl_Vertex); VaryingTexCoord0 = gl_MultiTexCoord0; gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; }

Important part of the fragment shader:

vec3 finalnormal; vec3 eyevec = normalize(-v); vec2 transformedTexcoord; vec4 ntexcolor; if(bump_on > 0) { vec3 view = normalize(eyevec * TBN); float height = (texture2D(height_texture, vec2(VaryingTexCoord0)).r * 0.04) - 0.03; //grab height from a heightmap transformedTexcoord = (height * view.xy) + vec2(VaryingTexCoord0); //transform the texcoord based on the height ntexcolor = texture2D(normal_texture, transformedTexcoord); //sample the normal map with the transformed texcoord vec3 normal = normalize(ntexcolor.rgb * 2.0 - vec3(1.0)); //turn that sample into a normal vector finalnormal = normalize(TBN * normal); //multiply that by the tbn matrix to get the final normal }