Light Sourced Lambert Shading
By Wayne Coles
This document assumes these things...
- You are using convex polygons, if you`re not then do so now, it`s a lot easier (trust me :-)
- Your polygons vertices are defined in clockwise order.
- Your polygons are single sided.
- You have some programming knowledge of 3d graphics (Not a lot, but some).
Of all the effects available to a 3d programmer, light sourcing is one of the most striking. There are many different ways of rendering a light source, but most people use Lambert Shading. Lambert shading is currently used in two main ways in games programming. Flat shading and Gouraud shading. Surprisingly there isn`t too much of a difference between them.
This consists of finding the position of a light source in relation to a polygon, calculating the light intensity and modifying the polygon`s color by it. So how do we calculate the light intensity. Well (surprise, surprise), we use maths. First up, we need a normal for the polygon and to calculate the normal we need the CrossProduct of two of the polygon edges, given a tri-poly with its vertices numbered 1,2,3 then the normal of the face can be calculated with the following formula...
nx = ( ( y1 - y2 ) * ( z1 - z3 ) ) - ( ( z1 - z2 ) * ( y1 - y3 ) ); ny = ( ( z1 - z2 ) * ( x1 - x3 ) ) - ( ( x1 - x2 ) * ( z1 - z3 ) ); nz = ( ( x1 - x2 ) * ( y1 - y3 ) ) - ( ( y1 - y2 ) * ( x1 - x3 ) );
you can use that for quad-polys as well, just use the first three vertices in the polys description. This provides you with a normal which is basically another vector perendicular to the plane the polygon lies in. What use is this? Well, not much (yet). Before we can use it for our lighting, we need to make sure it is a certain length. If you are using floating point calculations the best length is 1. So, to make our normal of unit length 1 we do the following steps...
1. Find the current length of the calculated normal. This is a 3d version of Pythagoras, and is very usefull ....
nlength = sqrt( ( nx * nx ) + ( ny * ny ) + ( nz * nz ) );
2. Now we make the length of the normal our required size by ( normalising ) using the following calculations....
nx = nx / nlength; ny = ny / nlength; nz = nz / nlength;
If you`d prefer to use integer calculations modify step 2 as required by your target length.
The above steps will provide you with a normal of unit length. Rotate the normals as you rotate the objects vertices and in your polygon structure have the normal number for that polygon. DO NOT apply perspective, translation or shearing on them!
Before we step into light sourcing proper, a quick mention of ambient lighting. This is the base lighting for the entire 3d world. ie. if no light fell on a face, it would still be lit by the ambient lighting. So if you have 16 shades for lighting and your ambient light was 5. Then the lowest lighting value a face could have would be 5.
Simple, maybe, But we also need the calculation. The Dot product requires two vectors (for us this means the plane normal and the light source), and if each was labeled a and b we could calculate the dot product by using the following formula...
d = ( a.x * b.x ) + ( a.y * b.y ) + ( a.z * b.z );
The dot product is one of the 3d programmers biggest friends (along with a sine table), it has literally hundreds of uses from hidden plane removal to BSP trees. To make it even better it is soooooo simple to memorize its crazy (just remember xyz!).
The Light Source
This is just a point that you move through your world wherever you want. A single vertex with a world coordinate. See, easy.
Our first tentative step into the world of light source shading. Flat shading just changes the lighting over the entire polygon. It doesn`t take much processor time and its easy to do. Lets look at the steps ....
- Move the light source into object space. This is just subtracting the objects world position from the lights position.
- We now normalise the light source. This is the same as making the distance unit length (see making the normal unit length above).
Now, we need another mathmatical formula, the Dot Product. This calculates the Cosine of the angle between two vectors scaled by the magnitude (unit length) ie. If your unit length is 1 then the Dot product will return a value between -1 and 1, if your unit length is 4096 (using integer calculations) you will get a value between -4096 and 4096. Because we get a Cosine value then any negative value we get means the angle between the light source and normal is greater than 90 degrees, hence the polygon cannot possibly be facing the light source but if the value is between 0 and 1 it is!
Great, but don`t throw away that value yet! Here`s the cool part.... The closer our value comes to unit length the more acute our light source is to our polygon normal. This means the light is more direct so the polygon should be brighter. Which means if we have 16 shades of each color then for unit length of 1 multiply the dot product by 15 and add the ambient light, you now have the lighting value for that polygon. Simple or what?
A quick note about shading here, to actually calculate what (say) a yellow polygon would be with a light intensity of 5, layout your palette so for each color, the 15 values before it is the colors getting darker. Use the darkest color as your base and add the light intensity to it.
Gouraud allows for a more realistic lighting effect than flat shading. It consists of calculating the light intensity at each corner and interpolating between them. This gives a smooth shaded appearence to the polygons. So, how is it done? Our first problem we encounter is that we cannot generate a normal for a single point, we need a plane. But we need a normal for each vertex so we can get our hands on the dot product! What we do is this....
- Calculate the polygon normals for all polygons in your object. Now then, we need to generate point normals, ie a normal for a single point....
- For each vertex in a polygon, calculate the average x, y, z of all normals for polygons that use that vertex. That is the vertex normal for that vertex. Repeat until all normals have been calculated. Don`t forget to store the point normal id`s in your polygon structures!
Now to finish it off, calculate the light intensity for each point as described in the flat shading section using the dot product. When you come to your edge calculation interpolate the light intensity as well. Finally during your scan line filler interpolate across the line as well. There you go, one gourard shaded polygon.
And thats it! Le fin. If you want to contact me for any reason my e-mail address is firstname.lastname@example.org so, for now, cheerio...