Advanced Lightmapping by (12 March 2001) | Return to The Archives

Introduction

You're a pretty good 3d coder, you have a nice environment running with static lightmaps. It looks great. In some ways though it's just missing something. What is that something? That something is a flickering light in a dark hallyway, or an orange pool or light around a rocket as it is fired. That something is dynamic lightmapping.
There are three major types of dynamic lightmapping. Fully dynamic lightmaps, animated lightmaps, and switchable lightmaps.
Fully dynamic lightmaps are for moving lights. Animated lightmaps are for things like strobe and flicker. Switchable lightmaps can be on or off.
I'm not going to be going into standard lightmapping techniques - There are plenty of tutorials out on the net for that.

Step 1: The creation of lightmaps

With standard static lightmaps each face has a lightmap. We are going to take it up a notch. For dynamic lightmapping each face can have a variable number of lightmaps. This makes creating lightmaps and storing them a little more complex. Since fully dynamic lightmaps are totally generated on the fly, here we are only concered with animated lightmaps and switchable lightmaps.
The first thing we need to define is what all the styles of lightmaps are. Just for example lets say you want to have flicker, pulse, and strobe. You assign numbers of 1, 2, 3 to the 3 styles, respectively. Any number assigned higher than the highest animated style will be considered switchable. Any light assigned a 0 is a normal static lightmap.
When you create your level data, a good way to assign switchable lights is to give lights a name. (whereas standard lights have no name.) When you go to process a light, if it has a name and you haven't come across that name before, mark that name as a new style above the animated styles.
You may be wondering where the animation of lightmaps comes in. Don't worry, it will be clear later. Each animation only needs one layer.
Now that you have style numbers assigned to each light in the scene you are lightmapping, you can go ahead and create the lightmaps. The basic idea is for each lumel (pixel on a lightmap) there is an array of color values with the array size being the number of styles.
You create lightmaps as you would regularly, but the incoming light value goes into the array position of the style of the light.
Once this is done for all faces in the scene, you still aren't quite ready to render the scene. A bit of optimization is needed to be done on the sets of lightmaps.
The problem is that many of the lightmaps that you just created are all black since there aren't styled lights in every place. You need to check for these and discard them. Otherwise your 3d engine is going to have a very bad framerate and you'll be storing information on disk and in memory that is useless and contributes nothing.
Any lightmap that has all lumels in a style less than, say a value of 16, can be discarded. The amount that a lightmap of brightness 16 contributes to a scene is close to nothing, and you can't really even see it unless you have your monitor brightness turned way up. The one thing you don't want to do is discard a lightmap with style 0, since this is the base static lightmap and it is ok for it to be totally dark.
When you are optimising your lightmap sets, you need to keep track of which lightmaps are still in use. For example a face may have styles 0 and 2 on it, and you remove 1 and 3. You need to store that the first lightmap for the face is style 0 and the second one is stlye 2. Otherwise your animations won't be correct.

Step 2: Dynamic lightmaps

There are two ways to deal with dynamic lightmaps. The first is to not use lightmaps. Since this is an article on lightmapping I won't go into to much detail. Basically you have a texture that represents a pool of light. As a first pass in rendering, you render all the other lightmaps, then additivly add the pool of light texture where lights fall. Then you render a pass with all your textures on top of that.
If you do use lightmaps things are a big different. For a dynamic light the first step is to find out which faces will be lighted by the dynamic light. A good way to do this is to push a bounding box around the light down some sort of spacial partition tree. All the faces in the leaves that the box is pushed into will be lighted by the light. You can further cull away a few more faces by checking to see if they are backfacing to the light.
For each face in the scene, you need to have preallocated an extra lightmap for each one. If a face is being dynamically lighted, this is where you put the resulting data. A dynamic map is created just like a static one. You can cast shadows at this point, but the slow down is generally unacceptable unless the scenes are very simple. It's better to just light everything around the light and cry silently that you can't do shadows this way.

Step 3: Rendering

This is the big step. Your renderer will apply one and only one lightmap to a face. 'What's that,' you ask? 'I just created tons of layers for each lightmap!' All the layers of the lightmaps get combined and then the resulting lightmap is what you render with.
The combine operation on lightmaps is a greater than function. If the incoming value is greater than the value that is there, then the incoming value replaces the current value. This is true for all red, green, and blue components of the color.
The first thing you need to do is to copy layer 0 of the lightmap info a buffer.
Then you need to add all the animated lightmaps for the face into the buffer. But they aren't animated! So we have to do that first. To animate a layer, you just scale the lightmap brightness with some function based on time and loop them. It's looks perfectly acceptable to animate at around 10 frames per second. This goes for dynamic lightmaps also. You really can't tell the difference. This can cause jaggy performance in the engine since every 10th of a second you spend more time doing lightmaps. To fix this you can do it every frame and just lerp between your brightness values.
The functions might look like this

    char strobe[] = {0, 255, 0, 255};
    char flicker[] = {255, 255, 0, 255, 0, 0, 128, 0, 255, 255};
    char pulse[] = {255, 245, 235, 225, 215, 225, 235, 245};

Then you add the switchable styles to the buffer. You only add a switchable styled lightmap if is toggled on. Very simple.
Then, if for that frame you generated a dynamic lightmap you add that to the buffer also.
Now you have your final lightmap, and you upload this to your 3d card and render like you always did with one lightmap per face.
If you're smart about how you arrange lightmaps, you can put a whole bunch of them onto one large texture and save some performance so you aren't doing hundreds of textures switches while uploading them all.
Also you'll have to take care of restore the origrinal lightmaps correctly. To save some performance, you may only upload version when the lightmap actually changes. Because of this, you have to save the previous state of the lightmaps so you know when to restore. As an example, lets say a dynamic light moves such that a face that it lighted last frame is no longer lighted. You have to restore the old lightmap otherwise the lightmap with the dynamic layer still on will be in texture memory.
So thats it. Now your scene looks great, and you're happy, and now you can read more tutorials and write more cool effects to make it look even better.

Copyright 1999-2008 (C) FLIPCODE.COM and/or the original content author(s). All rights reserved.
Please read our Terms, Conditions, and Privacy information.