This article describes how to implement a simple and effective bump mapping effect using nVIDIA's Cg programming language and OpenGL.
Although the focus of the article is on Cg, a limited amount of bump mapping theory is necessary and will be presented first. Additional bump mapping references are listed at the end of the article.
Bump Mapping Background
The goal of bump mapping is to create the illusion of "ridges" and "valleys" on otherwise flat surfaces. Here is an example :
The color of a surface is determined by the angle (dot product) between the normal vector of that surface and the light vector:
The light source is assumed to be placed "at infinity", and all rays from it are parallel to each other by the time they reach the surface.
On a flat surface, the normal vector is the same everywhere on that surface, so the color of that surface will be the same everywhere.
If the normal vector could be perturbed at various points on that surface, it would yield areas that are darker or lighter, thereby creating the illusion that parts of the surface are raised or lowered:
Bump Mapping using a Height Map
In order to compute the normal vector at every point on the surface, we need some additional information about whether points on that surface are "high" or "low". We use a height map to store this information (dark areas are "low", light areas are "high") :
The normal vector at a point is defined as the cross product of two tangent vectors to the surface at that point. The points in the height map range from (0,0) to (width, height) in a two-dimensional system of coordinates:
We define two tangent vectors to the surface at (x, y) as follows (heightmap[x, y] represents the height at (x, y)):
t = (1, 0, heightmap[x+1, y] - heightmap[x-1, y])
The normal vector η at (x, y) is the normalized cross product of t and β. If (x, y) is a point in an area of the height map where the neighborhood is all the same color (all black or all white, i.e. all "low" or all "high"), then t is (1,0,0) and β is (0,1,0). This makes sense: if the neighborhood is "flat", then the tangent vectors are the X and Y axes and η points straight "up" along the Z axis (0,0,1). If (x, y) is a point in an area of the height map at the edge between a black and a white region, then the Z component of t and β will be non-zero, so η will be perturbed away from the Z axis.
Note that η has been computed relative to the surface that we wish to bump map. If the surface would be rotated or translated, η would not change. This implies that we cannot immediately use η in the lighting equation: the light vector is not defined relative to the surface but relative to all surfaces in our scene.
Texture Space and Object Space
For every surface (triangle) in our scene there are two frames of reference. One frame of reference, called texture space (blue in the figure below), is used for defining the triangle texture coordinates, the η vector, etc. Another frame of reference, called object space (red in the figure below), is used for defining all other triangles in our scene, the light vector, the eye position, etc.:
In order to compute the lighting equation, we wish to use the light vector (object space) and the η vector (texture space). This suggests that we must transform the light vector from object space into texture space.
Let object space use the basis [(1,0,0), (0,1,0), (0,0,1)] and let texture space use the basis [T, B, N] where N is the cross product of the two basis vectors T and B and all basis vectors are normalized. We want to compute T and B in order to fully define texture space.
Consider the following triangle whose vertices (V1, V2, V3) are defined in object space and whose texture coordinates (C1, C2, C3) are defined texture space :
Let V4 be some point (in object space) that lies inside the triangle and let C4 be its corresponding texture coordinate (in texture space). The vector (C4 - C1), in texture space, can be decomposed along T and B: let the T component be (C4 - C1)T and let the B component be (C4 - C1)B.
The vector (V4 - V1) is the sum of the T and B components of (C4 - C1):
V4 - V1 = (C4 - C1)T * T + (C4 - C1)B * B
It follows immediately that:
V2 - V1 = (C2 - C1)T * T + (C2 - C1)B * B
This is a system of two equations with two unknowns (T and B) that can be readily solved for T and B:
t1 = (C2 - C1)T, t2 = (C3 - C1)T
By definition, the matrix that transforms vectors from texture space to object space has as columns the vectors T, B, N. The inverse of this matrix transforms from object space to texture space. For example, this (inverse) matrix will take the triangle normal (from object space) and map it to the Z axis (in texture space); similarly, this (inverse) matrix will take the light vector (from object space) and transform it to texture space. At this point, we can use η and the newly transformed light vector to compute the lighting value at every point in the height map.