Computing Normal Vectors

Often you will have 3D polygonal data for an object, but need to generate normal vectors for it, in order to light it.

To compute normals from polygonal vertex data, use the cross product.

The cross product is a function of two vectors that will produce a new vector that is perpendicular to the other two. If the two vectors are edges of a polygon, then the cross product will give you a normal vector for the polygon (you will need to normalize its length to be 1.0, however).

The formula for the cross product of two vectors A & B is:

( Ay Bz - Az By ) x + ( Az Bx - Ax Bz ) y + ( Ax By - Ay Bx ) z

A note about notation

x, y, z are unit vectors, and refer to the X, Y, and Z coordinate axes.
A point in 3 dimensions (a,b,c) can also be written as the vector ax + by + cz.
e.g., (1, 4, 2) is 1 unit from the origin along the X axis, 4 units along the Y axis, and 2 units along the Z axis, which is the same as adding the 3 vectors 1x, 4y, & 2z.

Back to the cross product

The C code equivalent for the cross product is:

void crossProduct(float A[3], float B[3], float product[3])
    {
    product[0] = A[1] * B[2] - A[2] * B[1];
    product[1] = A[2] * B[0] - A[0] * B[2];
    product[2] = A[0] * B[1] - A[1] * B[0];
    }

The direction of the cross product is determined by the right-hand rule applied from vector A to vector B (so if you multiply them in the reverse order, the result will point the opposite direction).

Applying the cross product

Given the vertex data for a polygon, you can compute a normal to the polygon by taking two non-collinear edges (we usually just assume that no two adjacent edges will be collinear), compute the cross product, and normalize the result.

The following function can be used, passing it the first three vertices of your polygon:

/* computeNormal - takes 3 vertex positions as input (v0, v1, v2), and
    returns a unit length normal vector for the polygon (normal) */
void computeNormal(GLfloat v0[3], GLfloat v1[3], GLfloat v2[3],
                   GLfloat normal[3])
    {
    GLfloat A[3], B[3], product[3], length;
    A[0] = v2[0] - v1[0];
    A[1] = v2[1] - v1[1];
    A[2] = v2[2] - v1[2];
    B[0] = v0[0] - v1[0];
    B[1] = v0[1] - v1[1];
    B[2] = v0[2] - v1[2];
    product[0] = A[1] * B[2] - A[2] * B[1];
    product[1] = A[2] * B[0] - A[0] * B[2];
    product[2] = A[0] * B[1] - A[1] * B[0];
    length = sqrt(product[0]*product[0] + product[1]*product[1] +
                  product[2]*product[2]);
    if (length == 0)    /* If there's a problem, */
        {               /* return an arbitrary result */
        normal[0] = 0;
        normal[1] = 1;
        normal[2] = 0;
        }
    else
        {
        normal[0] = product[0] / length;
        normal[1] = product[1] / length;
        normal[2] = product[2] / length;
        }
    }


Example: topo-grey.c



next