Open GL Super Bible

Previous Table of Contents Next


Surface Normals

A line from the vertex in this upward direction would then start in some imaginary plane (or your polygon) at a right angle. This line is called a normal vector. That word vector may sound like something the Star Trek crew members toss around, but it just means a line perpendicular to a real or imaginary surface. A vector is a line pointed in some direction, and the word normal is just another way for eggheads to say perpendicular (intersecting at a 90? angle). As if the word perpendicular weren’t bad enough! Therefore, a normal vector is a line pointed in a direction that is at a 90? angle to the surface of your polygon. Figure 9-10 presents examples of 2D and 3D normal vectors.


Figure 9-10  A 2D and a 3D normal vector

You may already be asking why we must specify a normal vector for each vertex. Why can’t we just specify a single normal for a polygon and use it for each vertex? We can—and for our first few examples, we will. However, there are times when you don’t want each normal to be exactly perpendicular to the surface of the polygon. You may have noticed that many surfaces are not flat! You can approximate these surfaces with flat, polygonal sections, but you will end up with a jagged or multifaceted surface. Later we’ll discuss a technique to produce the illusion of smooth curves with straight lines by “tweaking” your surface normals (more magic!). But first things first.

Specifying a Normal

To see how we specify a normal for a vertex, let’s take a look at Figure 9-11—a plane floating above the xz plane in 3D space. We’ve made this simple to demonstrate the concept. Notice the line through the vertex (1,1,0) that is perpendicular to the plane. If we select any point on this line, say (1,10,0), then the line from the first point (1,1,0) to the second point (1,10,0) is our normal vector. The second point specified actually indicates that the direction from the vertex is up in the y direction. This is also used to indicate the front and back sides of polygons, as the vector travels up and away from the front surface.


Figure 9-11  A normal vector traveling perpendicular from the surface

You can see that this second point is the number of units in the x, y, and z directions for some point on the normal vector away from the vertex. Rather than specifying two points for each normal vector, we can subtract the vertex from the second point on the normal, yielding a single coordinate triplet that indicates the x, y, and z steps away from the vertex. For our example this would be

(1,10,0) - (1,1,0) = (1-1, 10-1, 0) = (0, 9, 0)

Another way of looking at this is, if the vertex were translated to the origin, the point specified by subtracting the two original points would still specify the direction pointing away and at a 90? angle from the surface. Figure 9-12 shows the newly translated normal vector.


Figure 9-12  The newly translated normal vector

The vector is a directional quantity that tells OpenGL which direction the vertices (or polygon) face. This next code segment shows a normal vector being specified for one of the triangles in the JET example program:

glBegin(GL_TRIANGLES);
        glNormal3f(0.0f, -1.0f, 0.0f);
        glVertex3f(0.0f, 0.0f, 60.0f);
        glVertex3f(-15.0f, 0.0f, 30.0f);
        glVertex3f(15.0f,0.0f,30.0f);
glEnd();

The function glNormal3f takes the coordinate triplet that specifies a normal vector pointing in the direction perpendicular to the surface of this triangle. In this example, the normals for all three vertices have the same direction, which is down the negative y axis. This is a very simple example because the triangle is lying flat in the xz plane, and it actually represents a bottom section of the jet.

The prospect of specifying a normal for every vertex or polygon in your drawing may seem daunting, especially since very few surfaces will lie cleanly in one of the major planes. Never fear, we will shortly present a reusable function that you can call again and again to calculate your normals for you.


Polygon Winding:  
Take special note of the order of the vertices in the jet’s triangle. If you viewed this triangle being drawn from the direction in which the normal vector points, the corners would appear counterclockwise around the triangle. This is called polygon winding. By default, the front of a polygon is defined as the side from which the vertices appear to be wound in a counterclockwise fashion.

Unit Normals

As OpenGL does its magic, all surface normals must eventually be converted to unit normals. A unit normal is just a normal vector that has a length of 1. The normal in Figure 9-12 has a length of 9. You can find the length of any normal by squaring each component, adding them together, and taking the square root. Divide each component of the normal by the length and you get a vector pointed in exactly the same direction, but only 1 unit long. In this case, our new normal vector would be specified as (0,1,0). This is called normalization. Thus, for lighting calculations, all normal vectors must be normalized. Talk about jargon!

You can tell OpenGL to convert your normals to unit normals automatically, by enabling normalization with glEnable and a parameter of GL_NORMALIZE:

    glEnable(GL_NORMALIZE);

This does, however, have performance penalties. It’s far better to calculate your normals ahead of time as unit normals instead of relying on OpenGL to do this for you.

Given any normal vector specified by a coordinate triplet that indicates the direction from the origin, you can easily find the equivalent unit normal vector with the function in Listing 9-2.

Listing 9-2 A function that reduces any normal vector to a unit normal vector

// Reduces a normal vector specified as a set of three coordinates,
// to a unit normal vector of length 1.
void ReduceToUnit(float vector[3])
        {
        float length;

        // Calculate the length of the vector
        length = (float)sqrt((vector[0]*vector[0]) + 
                               (vector[1]*vector[1]) +
                               (vector[2]*vector[2]));

        // Keep the program from blowing up by providing an acceptable
        // value for vectors whose length may be calculated too close to
        zero.
        if(length == 0.0f)
                length = 1.0f;

        // Dividing each element by the length will result in a 
        // unit normal vector.
        vector[0] /= length;
        vector[1] /= length;
        vector[2] /= length;
        }


Previous Table of Contents Next