Digital Light & Magic

Working on combat AI is all well and good, but Terror High is, at its heart, a horror game. After months of staring at bright, fully lit sprites, I’ve been ready for something a little different, something a little more atmospheric…something a little spookier.

I love working in 2D, but as we’ve discussed previously, it has its limitations. While lighting in a 3D game is relatively straightforward given the presence of 3D geometry, implementing a satisfactory lighting system in a 2D game involves a lot more trickery. As with perspective, it’s all about faking something that, in a 2D plane, essentially doesn’t exist.

The lighting system I’ve cooked up for Terror High is comprised of several relatively simple techniques:

  • Color grading, achieved by color look-up tables
  • A layer of “darkness” adjusted based on light sources and time of day
  • Sprites representing lights and shadows, dynamically scaled and adjusted depending on positioning

When all of these are combined, it provides for a pretty convincing overall effect.

The first two techniques are the simplest to implement. The “darkness” is literally just a simple black overlay with adjustable transparency. Light sources are represented by scaled and rotated sprites which essentially act as cutouts on the “darkness” layer, allowing “light” to show through. The number of light sources within a certain distance of the player are counted, their ambient light levels added up, and the “darkness” adjusted accordingly. This “darkness” can also be adjusted dynamically based on time of day, as the “sun” and “moon” are just another abstracted light source that moves according to the game’s internal clock.

Time of day, as well as specific weather patterns, are also represented to the player through the use of color look-up tables (LUTs). Color LUTs are, as their name implies, simply a table (represented by a 2D image) that the computer can reference when displaying colors, taking a base RGB value and displaying a different RGB value based on the information present in the LUT. A neutral LUT will produce no change in the image; each RGB value will be the same as the source. But a black & white LUT, for example, would take the source RGB and return a greyscale value, producing a B&W image:

Neutral LUT
Black & white LUT

This is an extraordinarily versatile and easy way to add color grading to an image, be you a photographer, videographer, or, in this case, game developer. To make LUTs for each time of day, I set up a simple “workshop” consisting of a scene with its neutral colors, a neutral LUT, and set about playing with the color balance, saturation, etc. for each time of day until I got something that I was satisfied with. I currently have LUTs for dawn, morning, noon, afternoon, evening, dusk, and night – but the beauty of this system is that all the LUTs are stored as external files, and thus existing LUTs can be updated and new LUTs can be created with ease.

All day and all of the night

This next part is where it starts to get slightly more complicated. Color grading is a great start, but horror is often about the contrast between light and dark (literally and metaphorically); dimly lit hallways, flickering bulbs, dancing shadows, and the meager beam of a flashlight standing between you and the darkness.

In this lighting engine, lights and shadows consist of both decorative sprites and cold, hard maths. As mentioned earlier, as part of calculating the darkness of a scene, all of the light sources within a certain radius of the player are counted. Additionally, each object which should cast a shadow within this radius is counted. This tells us how many shadows we need, and which objects they should be assigned to. For example, if there are two light sources and two shadowcasters in the area, we’ll need four shadows total (2 x 2 = 4).

Just two cool dudes being cool…with shadows!

Each shadow’s scale and angle is tied to its parent light source. As shadowcasters or lights move around the scene, shadows are updated dynamically to reflect their motion.

In addition to the position of the light source, a shadow’s visibility is affected by the strength of its parent light source. A flashlight at midnight produces a much stronger shadow than one at midday.

Lights can also be designated as “directional” lights, which limits their angle of influence:

As light sources are turned on and off, or leave and enter the designated radius of the player, shadows are added or culled as needed. To avoid unnecessary calculations, I will eventually instate an upper limit of shadows per object (most likely 4 – 1 for the sun/moon, 3 for remaining light sources). I have already planned for lights to be assigned a “priority” by which they can be ranked (e.g. shadows from bright sunlight would be a higher priority than a small table lamp).

A final element to the lighting engine which I would dearly love to implement, but am currently keeping on my “wishlist” due to the amount of work involved, is normal maps.

Normal maps, like color LUTS, are an image that allows for simplification of calculations – in this case, light bouncing off a surface. A normal map is essentially a picture with a range of colors, with each color instructing the computer as to what angle the light is striking and reflecting off an object. It’s often used in 3D games to simplify models (i.e. reduce polygon count) but still mimic much of the detail present in the original model, but it can also be used in 2D games to add dimension and depth where, in reality, there is none.

I’ve tinkered around with Sprite Lamp, which allows you to combine “lighting profiles” – examples of how the sprite would be illuminated from various angles – into a normal map.

Lighting profiles for Left, Bottom, Right, Top, and Front lighting
Unshaded sprite / Normal map / Unshaded sprite w/normal mapped lighting

The results are extremely impressive, but two things have kept me from shifting normal mapped lighting from my “would like to have” list for Terror High to my “must have” list:

1.) Each frame of animation for a normal mapped object would need at least a four direction lighting profile. While greyscale shading is relatively simple, that’s still a lot of art to create. If you remember from Mortal Combat, the one-handed primary and secondary attacks in the cardinal directions are 128 frames total. To create a lighting profile for those is at least an additional 512 frames. (Aren’t exponents fun?) Some of these assets could be flipped and used for multiple directions, but it’s still an undertaking.

2.) Clickteam Fusion doesn’t currently have great support for normal maps. There are a couple of community created shaders, but these are quite a few years old and don’t take advantage of Clickteam’s recent 2.5+ update, which adds support for DirectX 11.

Considering the still ridiculous amount of work ahead of me for Terror High, normal maps are pretty far down on my list of priorities.

Of course, making pretty lighting effects would more sensibly be lower on my list of priorities than this article shows it to be, but, hey, I’m making this up as I go.

UPDATE: I wanted to give a shout-out to this Gamasutra article from the lead programmer of Graveyard Keeper. It gave me a lot of great ideas, and it might quite possibly have introduced me to the concept of color LUTs (I can’t recall specifically). I haven’t played their game yet, but it certainly looks purdy.

One thought on “Digital Light & Magic

Add yours

Leave a comment

Powered by WordPress.com.

Up ↑