An OpenGL GLSL Normal and Specular Mapping Shader
This is a shader written in the OpenGL Shading Language (GLSL). It implements bump mapping using a normal map. In order to achieve more realistic lighting and more interesting material properties, this shader also does specular mapping. Below you will find the GLSL source code, as well as instructions on how it can be used.

General
Normal mapping is a technique that uses a texture in which the normals per fragment are encoded in the texture's colours. These normals replace vertex normals and are used for per-fragment lighting. Normal maps add a lot of detail to an otherwise flat surface, without increasing the vertex-count of a model.
Shader Inputs
Tangent, Binormal and Normal
Normals in a normal map are stored relative to the texture's coordinate system. This system is called tangent space. In order to translate eye space coordinates (e.g. light and eye positions) into tangent space, you need the correct tangent space matrix. This matrix (or its vectors) is calculated outside the shader; usually when first loading a model. Detailed articles on how to derive the tangent space matrix can be found here http://jerome.jouvie.free.fr/OpenGl/Lessons/Lesson8.php and here http://www.blacksmith-studios.dk/projects/downloads/tangent_matrix_derivation.php.
The tangent space matrix consists of a tangent, a binormal and a normal. All three have to be passed to the shader for each vertex. The normal is passed via glNormal(...). The other two are passed as shader attributes named tangent and binormal. Alternatively the binormal can be computed in the shader as the cross product of the normal and the tangent.
Textures
The fragment shader requires three textures. A diffuse textures that contains the traditional texture. A normal map that contains the normals and a specular map that determines specular highlights.
Diffuse Texture

A plain white diffuse texture, to see the effect of the shader more clearly.
Normal Map
Normal maps encode XYZ coordinates of normals as RGB colours. A colour ranging from 0 to 255 is mapped to a coordinate ranging from -1.0 to +1.0. This shader expects tangent-space normal maps (as opposed to object-space normal maps).

A tangent-space normal map with circular raised bumps.
Specular Map
The specular map is used to determine the strength and colour of specular reflection. The original specular colour of the light will be modulated by the colour in this texture.

A specular map with green, white and no specular reflection.
Shininess
Shininess is a uniform value used by the fragment shader to influence specular highlights. The higher this value the sharper and smaller specular reflections will be.
Light
Finally the light's parameters have to be passed to the shader. This shader uses a uniform variable named light of the included type lightDataType. In theory GLSL has built-in variables that contain the OpenGL light state (gl_LightSource). However some graphics cards' drivers do not set these values properly. To run on a broader range of hardware, this shader uses its own uniform variable for light. The shader expects the light's position to be in eye space coordinates, i.e. multiplied by a model view matrix.
The Vertex Program
struct lightDataType {
vec4 ambient;
vec4 diffuse;
vec4 specular;
vec4 position; // Position in eye coordinates
};
uniform lightDataType light;
attribute vec3 tangent;
attribute vec3 binormal;
varying vec3 tbnDirToLight; // Direction to light in tangent space
varying vec3 tbnHalfVector; // Half vector in tangent space
void main(void) {
// Vertex location
gl_Position = ftransform();
// Texture coordinates
// Multiplication with texture matrix can be omitted if default (identity matrix) is used. To
// use the texture matrix comment the first and uncomment the second line.
gl_TexCoord[0] = gl_MultiTexCoord0;
// gl_TexCoord[0] = gl_TextureMatrix[0] * gl_MultiTexCoord0;
// Tangent space vectors (TBN)
// The binormal can either be passed as an attribute or calculated as cross(normal, tangent).
vec3 mvTangent = gl_NormalMatrix * tangent;
vec3 mvBinormal = gl_NormalMatrix * binormal;
vec3 mvNormal = gl_NormalMatrix * gl_Normal;
// vec3 mvBinormal = cross(mvNormal, mvTangent);
// Vertex coords from eye position
vec3 mvVertex = vec3(gl_ModelViewMatrix * gl_Vertex);
// Eye direction from vertex, for half vector
// If eye position is at (0, 0, 0), -mvVertex points to eye position from vertex. Otherwise
// direction to eye is: eyePosition - mvVertex
vec3 mvDirToEye = -mvVertex;
vec3 tbnDirToEye;
tbnDirToEye.x = dot(mvDirToEye, mvTangent);
tbnDirToEye.y = dot(mvDirToEye, mvBinormal);
tbnDirToEye.z = dot(mvDirToEye, mvNormal);
// Light direction from vertex
vec3 mvDirToLight = vec3(light.position) - mvVertex; // For positional lights lights
tbnDirToLight.x = dot(mvDirToLight, mvTangent);
tbnDirToLight.y = dot(mvDirToLight, mvBinormal);
tbnDirToLight.z = dot(mvDirToLight, mvNormal);
// Half-vector for specular highlights
tbnHalfVector = normalize(tbnDirToEye + tbnDirToLight);
}
The Fragment Program
struct lightDataType {
vec4 ambient;
vec4 diffuse;
vec4 specular;
vec4 position;
};
uniform lightDataType light;
uniform float shininess; // Shininess exponent for specular highlights
uniform sampler2D diffuseTexture;
uniform sampler2D normalMap;
uniform sampler2D specularMap;
varying vec3 tbnDirToLight; // Direction to light in tangent space
varying vec3 tbnHalfVector; // Half vector in tangent space
void main(void) {
// Base colour from diffuse texture
vec4 baseColour = texture2D(diffuseTexture, gl_TexCoord[0].xy);
// Uncompress normal from normal map texture
vec3 normal = normalize(texture2D(normalMap, gl_TexCoord[0].xy).xyz * 2.0 - 1.0);
// Depending on the normal map's format, the normal's Y direction may have to be inverted to
// achieve the correct result. This depends - for example - on how the normal map has been
// created or how it was loaded by the engine. If the shader output seems wrong, uncomment
// this line:
// normal.y = -normal.y;
// Ambient
vec4 ambient = light.ambient * baseColour;
// Diffuse
// Normalize interpolated direction to light
vec3 tbnNormDirToLight = normalize(tbnDirToLight);
// Full strength if normal points directly at light
float diffuseIntensity = max(dot(tbnNormDirToLight, normal), 0.0);
vec4 diffuse = light.diffuse * baseColour * diffuseIntensity;
// Specular
vec4 specular = vec4(0.0, 0.0, 0.0, 0.0);
// Only calculate specular light if light reaches the fragment.
if (diffuseIntensity > 0.0) {
// Colour of specular reflection
vec4 specularColour = texture2D(specularMap, gl_TexCoord[0].xy);
// Specular strength, Blinn–Phong shading model
float specularModifier = max(dot(normal, normalize(tbnHalfVector)), 0.0);
specular = light.specular * specularColour * pow(specularModifier, shininess);
}
// Sum of all lights
gl_FragColor = clamp(ambient + diffuse + specular, 0.0, 1.0);
// Use the diffuse texture's alpha value.
gl_FragColor.a = baseColour.a;
}
Rendered Output
A quad rendered with the shader, the three textures above and a grey light source:

Downloads
Download the shader source and example textures (~60KB)
A ShaderDesigner project is included in the download. ShaderDesigner is a free GLSL IDE.
Additional Enhancements
Many additional enhancements are possible, in order to create more interesting effects. For example you could add multi-light support, simple self-shadowing and an emissive light map. This way you get better looking models without increasing the number of vertices.

The same shader with multiple lights, self-shadowing and an emissive light map.
Click here to see the enhanced version in action
(requires Java 5+ and an OpenGL 2.0-capable graphics card)
I just spent the night puzzling and fumbling through an HLSL/C# version of this with a tutorial no less. Great job getting this done by yourself! This is difficult material to learn on your own...
- reply
Submitted by Ivan (not verified) on 18. May 2009 - 18:26.Hah, just saw the date on your post. I guess we were both up late last night? :)
- reply
Submitted by Ivan (not verified) on 18. May 2009 - 18:27.ack nm, i read May instead of March.
- reply
Submitted by Ivan (not verified) on 18. May 2009 - 18:37.Just attribute it to bad design. If it's too easy to confuse different entities, they're not very well designed. Roman gods are to blame :D
I started with tutorials and examples, too. In hindsight that was a waste of time. At some point I just ignored them and worked through the math myself. Once you know the math, there are so many great shaders you can come up with. Fixed function mode suddenly becomes very boring.
- reply
Submitted by Ciardhubh on 22. May 2009 - 12:53.