Real time lights and shadows can make a big impact on the presentation of your game if you know how to use them wisely; this is also expected in any modern game.
For 3D, two common methods for generating real-time shadows are Shadow Mapping and Shadow Volumes (check the documentation of your favorite engine), each with their respective advantages / disadvantages and higher processing if all what you want is a simple 2d game.
Real-time shadows do not have to be something exclusive to worlds with 3d geometry, there are several 2D games out there that make use of real time 2d shadows.
After reviewing several online articles that describe some techniques and some existing implementations (plugins for engines such as Game Maker) I decided to develop this tutorial I hope will be useful, in which I describe simple method for 2d shadows generation.
(Demo at the end of this post)
How to create 2d lights and shadows in real time?
For 2d we can use something similar to shadow volumes, either generate geometry for the light covered area or generate the geometry that is not covered by light, our lighting model is not going to be a real physics, but a fairly good approximation.
What is a shadow?
(Spanish lesson: light = luz, shadow = sombra)
The shadow is the area that is outside the scope of light, areas that are blocked by shadow casters. So to generate shadows we need a light source and the shadow casters.
We can start with a simple light source: an omnidirectional light that covers a finite NxN square area ,the foreground geometry that makes up the game level can be the shadow caster.
We have defined our source of light, now what remains is to generate the shadows.
How to generate the shadows?
For this we have 2 options:
- Calculate the region (2d geometry) that is not illuminated by light.
- Calculate the region (2d geometry) illuminated by light.
(Spanish lesson: option = opcion)
Any option is valid,but I decided to take option 2: calculate the geometry that represents the area illuminated by light.
How to calculate the geometry illuminated by light?
Our problem now is to detect the borders where shadow casters collide with the light rays emitted by our light source as seen in the image below:
How many rays to cast? Is that efficient?
Actually we don’t need to trace that too many rays to determine if they collide with the shadow casters.
What we can do is take the vertices of the shadow casters and select those that generate lines going straight to the center of the light without anything in between. We must also take into account the points of shadow casters that intersect the light region borders.
To make the process more efficient we can use AABB queries to detect intersections with the AABB of the light region and thus reduce the selection of vertices near the light.
Now that we have the points selected is only a matter of connecting dots , create the polygon and draw the area covered by light. The advantage is that this polygon can easily be rendered using a triangle strip and reduce the vertex information sent to the GPU.
We have light but it does not look too well, that is because we are rendering using a constant white color, instead we can use a texture with circular color gradient to create the light fading effect.
Lets add some color: for this simply paint a quad with a color gradient (the color of the light).
And the trick to make it look as seen in the screenshot is as follows: to paint this quad use a blending function that is equivalent to a multiplication RGB 1 x RGB2. What this does is multiply the value of light color by the value of the pixel beneath.
In opengl this is achieved using glBlendFunc (GL_ZERO, GL_SRC_COLOR).
You can experiment with various effects (I always use GIMP to design and model rendering by layers before doing any coding).
How to do multiple lights?
To do multiple lights, it’s just a matter of using FBOs (“Frame Buffer Object” or “Render to Texture” or “Post processing” or whatever you call it).
- We draw the colors of each light in a FBO1 and let the colors blend together (ADD blending).
- Draw the geometry of each light in a FBO2 and let intensities to accumulate (ADD blending)
- We proceed to draw the floor (or background).
- Draw over the FBO2 (containing the light intensities in gray scale/alpha).
- Draw the geometry of our 2d world (shadow casters).
- Finally paint the FBO1: Light colors. (Using blending MULTIPLY).
With this we have got a simple, low resource and Openg GL ES 2.0 compatible 2d lighting system.
3 simultaneous lights. (Demo screenshot)
This technique is useful if you are developing a 2d game and want to avoid 3D geometry for shadows. If you are using box2d you can actually reuse the body fixtures as shadow casters, and execute AABB queries to only process the geometry within the area of light.
What else is missing?
+ The shadows penumbra (soft shadows): we may need to generate more geometry for the shadow edges, it’s not that complicated, just some analytic geometry. The other option is to make a kind of blur shader when rendering the light geometry.
+ Per-pixel lighting: the floor is very flat would be nice to add bump mapping.
+ Modeling other types of light (directional lamp): for this we can reshape the region of light (instead of a rectangle use a triangle), make some adjustments to the calculations (origin and angle of light) and use the appropriate textures.
Link to win32 demo: Demo 2d Shadows
In future posts I will see another useful technique for 2d games (or perhaps continue a 2nd part of this article revisiting soft-shadows and per-pixel lighting 2d).