Making Code Happen

2D Dynamic Light Mapping

Last year I made called “Light The Way” for Ludum Dare 20. It featured a neat looking dynamic lighting system which I hacked together for the competition. It worked by firing 256 raycasts in a circle around the light, and using that to build a shadow mask. This is a very expensive way to do shadows, but I love the unique look it has. After several weeks of very hard work I’m excited to show the lighting system I’ve developed that takes it to the next level.

When I started working this problem I found a few articles that got thinking. This article by Catalin, another by Xot, and a video of a light engine demo by NeatWolf that is a good practical example. I had the idea that instead of going to polar space and back, we can just render the texture scaled on top of itself many times with a larger scale each time. Still the same basic principal but it saves us a step and eliminates graphical artifacts. Let’s take a look at how it works in a simple example.

I set up a little test area with a single point light to demonstrate the technique. For this example there are some terrain tiles I created in my engine as well as the player’s vehicle. I used GIMP to add a white dot where the light will eventually be located.

Shadow objects map

The first step in the process is to set up a camera centered around the light and render the shadow casters to another texture. In this scene the only shadow casters are the terrain and the player’s vehicle. As an optimization the rendering engine is culling everything outside the radius which explains the circular effect. This is the actual size texture. I am using 256×256 textures for lightmapping because I found that 512 textures were significantly slower without much visual improvement.

Extruded shadow map

After we have the shadow objects rendered we can extrude the shadows by rendering the texture on top of itself and scaling it slightly each time. Initially we scale it by just 2 pixels and continue to double that every time for about 8 passes. In practice the numbers are fudged a bit to eliminate artifacts. We could just stop here and render our scene on top of this shadow map, making it appear that shadows are behind the terrain and objects. My engine does basically the opposite of that because instead of creating a shadow map that goes behind stuff we are going to build up a light map and modulate it in as a mask over top of everything.

Softened shadow map

Before we can go the route of having a final light overlay we need to soften the shadows. Without soft shadows all of the objects in our scene would be in the darkness of their own shadows. My solution to shadow softening is to render a soft dot at a low alpha level every time we stretch the texture. This allows light to “bleed through” shadow casters somewhat and we can control how much by changing the alpha value. We also need to render a circular mask around the outside so that the light fades off gradually.

Final light map overlay

This image may look very similar to the previous one with one big difference. It is the final light map which lines up with our camera. Building up this final shadow map is just a matter of rendering each light’s shadow map to it with an additive blend. In this scene there is only a single point light so it looks very similar to the shadow map we created. It appears stretched because the light map is square but the game runs in widescreen. After creating the light map I apply a Gaussian blur to smooth things out. It may not look like much by itself but when we apply it on top of the scene something magical happens…

snapshot_0014a

Here’s a look at our test scene after the light map has been applied. To get to this step we just take the final shadow map and render it on top everything else, multiplying it’s color with the destination color. The process itself sounds simple but there are many technical details as well as subtle tweaking of values involved.

Complex scene with colored lights and an emissive pass

There are some other cool tricks we can do with this lighting system. Colored lights happen naturally by rendering the light’s shadow map as the lights color instead of white. Spot lights just have a cone map rendered on top of their shadow map. There is an emissive pass which is added into the light map used by lava tiles, glowing plants, and most importantly weapon effects. You can’t see it in this example but I also have a directional background light which casts light by sliding the texture instead of scaling it. The directional light works great for outdoor scenes and can provide some ambient lighting to interior areas.

With the current implementation my engine can do well over 10 lights full screen at a solid 60 fps and there’s plenty of room for optimization. The bulk of the expense is due to rendering the scene to each shadow map and in my case how many draw calls that requires. I was initially concerned that would not be enough lights but in practice it seems fine. There are normally only 2 or 3 stationary “world” lights on screen at a time, the rest are used for effects. Some small effects can use non-shadow casting lights which are essentially free. I retrofitted Faster Blaster with dynamic lighting to test out the new system, check it out and see for yourself how it looks in motion! Also my game engine is fully open source so if you want to take a look at the actual code or make a game with it you can get it here.

One response to “2D Dynamic Light Mapping”

  1. Garland Dupuy Avatar
    Garland Dupuy

    Hi! If you’re reading this then you’ve proved that ads posted through feedback forms like yours works! We can send your promotional message to people via their feedback form on their website. The advantage of this kind of advertising is that messages sent through feedback forms are automatically whitelisted. This dramatically improves the probability that your advertisement will be seen. Never any PPC costs! Pay a one time fee and reach millions of people. To get more info please send an email to: william4212sau@gmail.com

Leave A Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.