Psykolambchopz:Kevin, i know its a fat chance youll read this but i would love to get that source code if you could.
I don't have the source code for all the demos anymore, but the code for the lighting engine is up on SourceForge. I must apologize because it's not particularly high quality; I wasn't very familiar with C++ at the time.
http://fury2.svn.sourceforge.net/viewvc/fury2/libraries/SoftFX/module/Fury2.cpp?revision=52&view=markup
The lighting engine starts around line 2272. Hopefully you can make some sense of it - it's very complicated, since it had to handle a lot of edge cases.
However, I'll give a general overview of the technique:
For any given scene there is a corresponding 'lighting environment'. The environment defines light sources, light obstructions, light 'planes', and light 'sprites'.
Light sources emit light in one of three shapes: Sphere, cone, or projected rectangle (the latter projects a bitmap as a light source, essentially).
Light obstructions are line segments that block light and cast shadows. They have no thickness or depth, since my environments are 2D.
Light planes are line segments with a 'height' value: Instead of blocking light, they passively receive incoming light and project it upwards a given amount, to create the appearance of light striking a flat surface like a wall.
Light sprites are stand-ins for normal 2D sprites within the lighting environment. They can optionally both cast shadows and passively receive incoming light. If they receive light, a silhouette of the sprite is drawn with the appropriate color to create the appearance of the sprite being illuminated by the lighting environment. If the sprite has an associated normal map, that normal map will be used to compute per-pixel lighting for the sprite based on its position instead of rendering a silhouette.
The game renders the scene as you normally would, creating a composited 2D backbuffer. After this, it takes the objects from the lighting environment and builds a lightmap for the scene, that corresponds to the backbuffer:
First, the lightmap is cleared with the ambient light color.
Then, for each light source in the scene:
- The light source's illumination is rendered to an empty temporary buffer. This is done using a fast approximation (a gradient triangle for cones, a radial gradient for spheres, a bitmap for projectors).
- A pair of lines is then projected from the light source to the endpoints of each light obstruction. This is used to compute a cast shadow, which is then masked out of the temporary buffer (making that section of the buffer pure black).
- Any sprites set to cast shadows are also masked out of the temporary buffer much like a light obstruction.
- The temporary buffer is copied to the lightmap using additive blending.
Then, for each light plane and sprite in the scene, ordered by Y:
- If the object is a plane:
- Light values are computed for each point along the bottom surface of the plane, via a raycasting technique. These values are stored into a 1-dimensional temporary texture representing the plane's illumination.
- The 1-dimensional texture containing the plane's light values is rendered to the lightmap as a textured quad, replacing the previous contents of the lightmap in the area occupied by the plane.
- If the object is a sprite:
- A light value is computed for the bottom centerpoint of the sprite via a raycasting technique.
- If the sprite is set to receive incoming light, A silhouette of the sprite is rendered to the lightmap, replacing the previous contents of the lightmap. The silhouette is colored using the light value for the sprite, and if a normal map is available it is used to compute per-pixel light values. If the sprite has an alpha channel it is used to mask the silhouette.
- If the sprite is set to be 'emissive', the optional emissive light map is rendered to the lightmap using additive blending.
Once this is done, you have a complete lightmap for the scene. You can then composite it onto the existing scene (I typically use a 2X multiply blend, but you can use other techniques), and you can also preprocess it, using a blur filter or such, to make it look more interesting.
In practice this is similar to a deferred renderer but has some different advantages/disadvantages. Note that this was designed and implemented before 3D hardware acceleration was prevalent so there are cases where it could be made more efficient and realistic by utilizing D3D9 features - for example, you could drop the temporary buffer for light sources entirely and use stenciling instead. You could also feasibly do per-pixel lighting computations for the entire surface of a plane, instead of just the bottom-most pixels, and get more realistic looking light for those surfaces.
Here are some images to demonstrate how it works. In this test scene, the barrels and boxes have normal maps and the boxes with lights inside them have both a normal map and an emissive map. The boxes also have a small projector attached to them.
Lightmap
Scene (unlit)
Composited Scene
Composited Scene w/ gaussian-blurred lightmap
Lighting environment in level editor
Hope this gives you an idea of how the technique works!