Pythonstuff GLSL in English Pythonstuff GLSL auf Deutsch Pythonstuff GLSL Pythonstuff
PythonStuff Home


Example 4 - GLSL Dot3 Bump Mapping

Based on the previous example we add a Dot3 Bump Map (Normal map). You may be making Bumpmaps yourself soon!

The result looks like this:

Bumpy Texture Mapped Torus Bumpy Texture Mapped Sphere Bumpy Texture Mapped Sphere - other texture

The textures are in the Texture Pack 1 and Texture Pack 2 .

To run this demo, you need Pyglet and Tristam Macdonald's Library You find everything on the installation page.

Program description

To implement dot3 bumpmaps, we want to change the normal vector of a surface element according to a direction vector in a selfmade bumpmap (normal map) image.

A bumpmap image contains direction information encoded as RGB-Color values. A flat surface would be represented by normal vectors pointing straight up - a vector with the value 0/0/1. Generally the value of the vector components will vary between -1 and 1 (strictly speaking, the z-component just between 0 and 1 - it never points into the surface). To store it as an RGB value with 8 bit color components, you simply map

  1. 1 ⇒ 0, 0 ⇒ 127, 1 ⇒ 255

So a vector pointing straight up will be R/G/B = 127/127/255: this is a pastel blue tone. This is the reason why most bumpmaps look blueish. For illustration, here is a convex (pointing out) half-sphere:


We load this image:

texturecnt = 2          # Texturemap0.jpg = Colormap Texturemap1.jpg = Bumpmap

Then a button would be nice to turn the effect on and off (let's take “B”) and make its state known to the shader program:

global togglebump
def on_draw():
    shader.uniformi('togglebump', togglebump )
def on_key_press(...):
    elif symbol == key.B:
        togglebump = not togglebump
        print 'Toggle Bumpmap ', togglebump

This out of the way, we face a challenge: up to this point it was sufficient to know what point in the texture map we are dealing with. Now we need to know in which direction the map is applied, because the bump map can not be rotated arbitrarily.

This is called “uv-mapping”, because we need to know in which direction the texture coordinates (traditionally called u and v) point for every vertex. This direction vector is interpolated between the vertices. We can have our tiny little coordinate system for every point on the surface - this is called “tangent space”.

We hide the direction vector for “u” in the vertex color (gl_Color.rgb). “v” is not needed - we want an orthogonal coordinate system, so we can get “v” by calculating a vector that points 90 degrees off “u” and 90 degrees off the Normal vector (

Color components go from 0 to 255 in OpenGL, so we store the vector like this:

tangents.extend([ int(round(255 * (0.5 - 0.5 * tx))),
                  int(round(255 * (0.5 - 0.5 * ty))),
                  int(round(255 * (0.5 - 0.5 * tz))) ])

for every vertex in our polygon model.

Well, there is another change in the example program. I did away with the rather complicated code for a sphere (remember, you can toggle between “sphere” and “torus” with the “F” key) and thereby simplified the code and made it faster. The only drawback is the non-uniform size of the triangles, but that seems to be a small price to pay.

Look at the code for sphere generation, it is quite readable.

GLSL Dot3 Bumpmapping

Now that we have added the “u” vector to the polygon model, we can have a look at the shader.

In the vertex shader we build the “tiny little coordinate system” - 3 orthogonal vectors:

// Create the Texture Space Matrix
normal    = normalize(gl_NormalMatrix * gl_Normal);
      tangent   = normalize(gl_NormalMatrix * (gl_Color.rgb - 0.5));
      binormal  = cross(normal, tangent);
 mat3 TBNMatrix = mat3(tangent, binormal, normal);

The “T”angent/“B”inormal/“N”ormal Matrix code is ok, when you know that the “cross” (or “outer”) product of two vectors produces a new vector 90 degrees off as we needed.

If we want to do further calculations in “tangent space”, we need to transform the light- and eye-vectors to this coordinate system. Sorry that this sounds dreadful :-):

vVertex = vec3(gl_ModelViewMatrix * gl_Vertex);
lightDir0 = vec3(gl_LightSource[0] - vVertex) * TBNMatrix;
lightDir1 = vec3(gl_LightSource[1] - vVertex) * TBNMatrix;
eyeVec    = -vVertex * TBNMatrix;

Once all relevant vectors are transformed, you can do your calculations (in the pixel shader) just as before.

The “dot3” calculation is done in the line

vec3 norm = normalize( texture2D(my_color_texture[1], gl_TexCoord[0].st).rgb - 0.5);
vec3 N = (togglebump != 0) ? normalize(norm) : vec3(0.0, 0.0, 1.0 );

We sample the color from the Texturemap1 (0.0 to 1.0), shift it to -0.5..0.5 an normalize (-1.0 to 1.0).

If you have not activated bumpmapping, the normal vector will point “straight up” (x=0,y=0,z=1) - remember, this is the “tangent space” coordinate system!

With togglebump != 0 the normal will point in the direction given by the bumpmap.

Enjoy !

But there is more - let us look at Example 5 - Parallax Mapping with GLSL.


Deutschsprachige Version, Start
Impressum & Disclaimer