# How to create realistic 2d lighting using colour temperature

I'm looking for a lighting algorithm that produces realistic lights expressed in kelvins, from about 2500k to 6500k. What I'm confused about is how to make the lights properly interact with the colors of game objects.

If a whole level is fully lit (overcast daylight) then it would seem that I should use just the color of the object. But what if I'm in a closed room with no windows, and there is an incandescent bulb shining light in the room? How would that light properly light up the objects in the room?

There does not seem to be an obvious solution to the problem. And simply mixing the color of the light with the colors of the object, seems an inaccurate approach.

-

The "color of the object", i.e. the RGB value of the texture, formally represents its reflectance spectrum, discretized by averaging it over the response curves of the three color receptors in the human eye.

Thus, to get the apparent color of a point on an object's surface, you simply sum together the colors of the lights that fall on it and multiply the result with the color of the surface, doing this separately for all the three color channels.

Of course, this doesn't account for the fact that real lights and real surfaces may have complicated spectra that are not fully represented by a simple RGB triplet, but in practice it works pretty well — the spectral details that are not well represented by the RGB approximation tend to also be the ones that the human eye can't distinguish.

For example, let's say you have two lights A and B, with colors CA = (1.0, 1.0, 1.0) and CB = (1.2, 1.0, 0.8) and brightnesses LA = 1000 and LB = 5, which are at distances dA = 50 and dB = 4 from a point P with color CP = (0.3, 0.8, 0.2). Assuming the standard falloff of light intensity as the inverse of the squared distance, the total light intensity at the point P (assuming that both lights actually hit the point) is:

Ctotal = CA LA / dA2 + CB LB / dB2
= (1.0, 1.0, 1.0) · 1000 / 502 + (1.2, 1.0, 0.8) · 5 / 42
= (1.0, 1.0, 1.0) · 0.4 + (1.2, 1.0, 0.8) · 0.3125
= (0.4, 0.4, 0.4) + (0.375, 0.3125, 0.25)
= (0.775, 0.7125, 0.65)

Now multiply this componentwise with the surface reflectance CP to get the apparent color of the point:

Crefl = CtotalCP = (0.775 · 0.3, 0.7125 · 0.8, 0.65 · 0.2) = (0.2325, 0.57, 0.13)

(If you're doing indirect illumination, this is also the color of the light reflected from the point P.)

Note that, in the example above, I normalized the light colors CA and CB so that the average of their components is 1.0 and used the separate scaling factors LA and LB to represent their intensities. This is just a matter of personal preference, and you could just as well bake the intensities into the light colors if you wanted, but this way seems clearer to me. Also, for lights that are very distant and very bright, such as the sun, you'll probably want to leave out the 1/d2 term and just let the L represent the brightness of the light at the scene.

Also note that, while the light intensities can (and typically will) exceed 1.0, the reflectance values should not. If they did, that would mean that the object would reflect more light than falls on it, which is obviously absurd. The reflected light intensity can also exceed 1.0; that just means that the light exceeds the maximum brightness that can be displayed on the screen. You can just clip those color values to 1.0, although you'll probably get nicer results if you render them unchanged into a HDR buffer and apply a bloom shader to the result. If the average illumination level can change a lot (e.g. if the player can move between outdoors and indoors), you'll probably also want to include a variable scaling factor to account for adaptation of the eye to the varying light levels.

Also, if you're not doing true indirect illumination, you'll probably want to include an "ambient" light term (preferably with some approximate ambient occlusion factor) in your calculations to avoid your shadows being completely dark. One way is to simply let the ambient light have a fixed color (usually neutral gray) and brightness everywhere, but you could also e.g. try making the ambient light proportional to the total light level in the area, ignoring (most) shadows, or use more advanced techniques for estimating the ambient illumination. Another term you may want to include is light emission from glowing surfaces; that can simply be handled by adding it to the final result.

Finally, everything I've written above only deals with diffuse reflection; specular reflection and highlights are a whole 'nother kettle of fish entirely (although the same basic physical principles still apply).

-
When you say "simply sum together the colors of the lights that fall on it and multiply the result with the color of the surface," I'm assuming that the multiple needs to be somewhere between 0 and 1? The problem is that surface colours are stored in int RGB values, with values from 0 to 255. Should I just store colour values in linear float space all the time until displaying them? – Truncheon Dec 8 '12 at 23:13
@Truncheon: If your surface reflectance values run from 0 to 255 instead of from 0 to 1, just divide the result of the multiplication by 255. – Ilmari Karonen Dec 9 '12 at 4:32
Let's say I have three lights which light up a particular point with a value of 255 each. If I multiply that by anything from 1 to 255, I will end up with an absurdly large number. Something like 195075 (if multiplying by 255). This will clip to 255. Doesn't that seem a little extreme? There's must be a more realistic way to do this. – Truncheon Dec 9 '12 at 22:52
@Truncheon: The simple answer is, don't use clipping arithmetic for intermediate results. Do the intermediate calculations with floats (or sufficiently long integers) and only clip the final result. – Ilmari Karonen Dec 10 '12 at 9:27

A Common way to do nice lighting in 2d games are to blend the level with light shaped textures. not sure if thats what your after...

-