More on 3D Lighting Using Surface Normals
In order for the OpenGL rendering engine to figure out how much
light should be hitting a specific polygon region, it is necessary
to calculate the normal vector to the surface of that polygon.
This will be
a directional vector of unit size one (1) that is perpendicular to
the surface of the polygon. This is illustrated by the blue triangle
and the green surface normal shown in the diagram to the right.
Given a triangular polygon with three vertices in the form ( xn,
yn, zn ), it is possible to calculate the
normal to that triangle by first describing two directional vectors in
the same plane.
For instance, given three points:
( Shown in BLUE )
p1 = (x1, y1, z1)
p2 = (x2, y2, z2)
p3 = (x3, y3, z3)
Two possible directional vectors representing the plane of that surface are:
d1 = ( x2 - x1, y2 - y1, z2 - z1 )
d2 = ( x3 - x2, y3 - y2, z3 - z2 )
In order to find a line perpendicular to those two vectors, it is necessary
to find the cross product of these two vectors. Each component of
the cross product vector is described below.
cross[x] = d1[y] * d2[z] - d1[z] * d2[y]
cross[y] = d1[z] * d2[x] - d1[x] * d2[z]
cross[z] = d1[x] * d2[y] - d1[y] * d2[x]
Cross product vectors can vary in size
depending upon the components, so in order for OpenGL to make some
comparisons needed for lighing, it is necessary to "normalize"
the result by making them all uniform in some way. This is done by
creating a normal vector with distance of one (1) unit long.
To scale the normal, it is necessary to divide each component
part by the total length or distance of the cross product vector which
will scale each portion appropriately.
The length of the cross product vector or its distance is calculated
with the following formula:
The components of the normal would then become:
dist = SQRT( cross[x]2 + cross[y]2 + cross[z]2)
The normal is drawn in GREEN in the diagram above.
norm[x] = cross[x] / dist
norm[y] = cross[y] / dist
norm[z] = cross[z] / dist
How OpenGL Uses Normal Vectors for Polygon Lighting
When OpenGL is deciding how much light should be applied to a surface,
it takes the direction of the light source and compares it
to the normal of the surface of the polygon. If the light
strikes the surface from directly above, the object will be colored
with the brightest light, whereas if the light strikes the surface at
an angle, the surface will be less strongly lit. If the light hits the
surface at a 90 degree angle or if the normal is pointing away from
the light source, the polygon will receive no light.
Using the Dot Product for Light Calculations
The comparison described above
is actually achieved by using a mathematical process called
the dot product between two vectors, one that
represents the normal for the surface of the polygon
and the other that represents
the direction of the light source.
Mathematically, the dot product is defined by the following formula:
Since the magnitudes of the normal vectors are each of length one (1),
the magnitudes can be ignored and therefore dot product merely returns
the cosine of the angle between the light
source and the normal vector that represents the perpendicular to the
polygon region. OpenGL uses this value as a percentage of the amount of
light that strikes the polygon region and thus can be used to emulate
the effects of light and shadow.
- u . v = |u| |v| cos(theta)
where |u| and |v| are the magnitudes of the two vectors and
"theta" is the angle between them.
Some Example Lighting Scenarios
If the normal points toward the light source, the polygon will receive the full
light value and will be drawn in the brightest color. Mathematically, the
cosine of zero degrees is 1.0, and thus the polygon will receive 100% of
If the normal of the polygon is at some angle greater than 0 but less than
90 degrees, the polygon will receive less light and thus will be drawn a
darker shade. Some example angles are:
cos(30) = 0.866 or 87% of the light
cos(45) = 0.707 or 71% of the light
cos(60) = 0.500 or 50% of the light
cos(75) = 0.259 or 26% of the light
- If the polygon normal vector and the light source are at 90 degrees,
the polygon will be in shadow since the cosine of 90 degrees is zero (0).
- Angles between 90 degrees and 270 degrees will return negative
values. Thus, the dot product for vectors in this range will be
negative and the surface will be colored dark as being in the shadow.
- As the angle exceeds 270 degrees and approaches 360, the cosine
will again be positive and the light will once again be hitting the polygon.
Since normal vectors are directional, it is necessary to do all calculations
in the same manner. For instance, if a triangle's vertices are given to
the formula in a counter clockwise fashion rather than clockwise, the
resulting normal vector will point in the opposite direction. This can
cause problems if calculations are not done in a consistent manner between
adjacent triangles. Some very unrealistic effects happen if a surface
generated by polygon regions has some of the triangles brightly lit while
others nearby are in thought to be in the shadow because their normals
are pointing in opposite direction!
There are a number of other techniques for averaging light values for
adjacent polygons, but that will depend upon the characteristics of the
object being modeled, such as does the model have sharp edges or are the
polygons attempting to model a smooth curved ssurface. Readers are
encouraged to investigate advanced lighting
concepts on their own.