Physical Queries for the Math Challenged: Real-time Shadows

Recently I’ve been working on a new game whose gameplay is centered around shadows. This meant that I needed to implement a shadow system which could:

  1. Generate “realistic” shadows for objects
  2. Allow for shadows to be cast at different angles
  3. Be efficient

Of course, I wanted to avoid all the complex math which tends to creep in with real-time shadows. In order to do this I needed to accept some constraints:

  1. Work with shadows, not light sources
  2. Have little control over intensities
  3. Objects simply extend in 3rd dimension

After implementing the system in my game I thought it might be useful for others. This post is going to cover the steps necessary to add simple real-time shadows to your game.

Casting a Shadow

What’s a shadow? For our purposes a shadow is the projection of a 3d object onto a 2d plane. That’s right, a 3d object. Most likely, your game objects are 2d, so you’ll need to add a new property to them. How about doing this formally like so:

interface ShadowCaster {
    /** Returns the object's "depth". */
    public int getZIndex();
    /** Calculate and return the object's shadow. */
    public Shape castShadow(float direction);
}

So, we have an interface which defines any object which can have a shadow. For convenience, I usually let objects with no shadow return null when castShadow is called so I can still easily leverage polymorphism. The z-index is important for two reasons. It is used in calculating the shadow and it is useful for sorting objects along the z-axis.

Next up let’s take a look at an implementation of castShadow. The following is for a square object. Underlying the object is a Shape derived from java.awt.Shape (specifically, it’s a Slick shape).

public Shape castShadow(float direction) {
    Vector2f v = Geom.calculateVector(getZIndex(), direction);

    Transform t = Transform.createTranslateTransform(v.x, v.y);
    Polygon extent = (Polygon) shape.transform(t);

    int index = findKeyPoint(v);

    Polygon shade = new Polygon();
    // adds points to the polygon except the key point
    for (int i = 1; i < 4; i++) {
        int c = (4 + index + i) % 4;
        float[] p = extent.getPoint(c);
        shade.addPoint(p[0], p[1]);
    }
    // as above, except this time with corresponding key point
    for (int i = 3; i > 0; i--) {
        int c = (4 + index + i) % 4;
        float[] p = shape.getPoint(c);
        shade.addPoint(p[0], p[1]);
    }

    return shade;
}

As you can see I project a certain distance based on its z-index and the direction of the light. After that I create a polygon using three points from the shape and from the projection. Let’s take a look at how I determine which points to use:

/**
 * Given two rectangles, a block and its shadow, the key point is the corner
 * on the shadow closest to the block. The second key point is the corner on
 * the block furthest from the shadow. One can be derived from the other.
 */
private int findKeyPoint(Vector2f v) {
    int index = 0;

    if (v.y > 0) { // bottom
        if (v.x > 0) { // right
            index = 0;
        } else { // left
            index = 1;
        }
    } else { // top
        if (v.x > 0) { // right
            index = 3;
        } else { // left
            index = 2;
        }
    }
    return index;
}

Simple, huh? At first I tried to find a mathematical relationship between two points to determine what the key point was but that ended in failure. So I did what any sane person would do and just mapped out what I knew intuitively with conditional statements. I know which points to leave out based on which direction the shadow was projected.

So there you have it. The greater the object’s z-index the longer the shadow it casts, and the direction passed to castShadow determines which way the shadow will be cast.

Creating a Shadowscape

Now that we’ve got objects and shadows we need to start thinking about how to render everything. In Shade most of the game objects were above the shadows. Therefore, I needed to render the shadowscape followed by the game objects. I also wanted to render game objects according to their z-index so that taller ones would be rendered over shorter ones. To accomplish these things I created two data objects.

First, there is the shadowscape. You probably noticed that castShadow returns a Shape. This is so I can collect all the shadows and render them in one pass. After that I make a second pass to render each of the game objects.

At it’s most basic the shadowscape has a single method:

public void render(Graphics g) {
    Color shade = Color.black;
    shade.a = SHADOW_ALPHA;
    g.setColor(shade);
    for (Shape s : shadows) {
        g.fill(s);
    }
    g.setColor(Color.white);
}

Where shadows is a collection containing the product of castShadow for each game object. In addition to a shadowscape I created a ZBuffer. This is a simple collection which orders objects by their z-index. Finally, I put everything together with a ShadowLevel which renders things like so:

public void render(StateBasedGame game, Graphics g) {
    shadowscape.render(g);
    for (ShadowCaster s : buffer) {
        s.render(game, g);
    }
}

This renders the shadowscape first then all of the game objects. This approach works well if your game is top-down like Shade. If you’re working on a platformer you would probalby want to take a different approach. Most likely you’d want to render the object and it’s shadow together instead of separating them into two passes.

That’s that! I hope you’ve found this helpful. If you have more questions or would like to see the source code behind Shade (and referenced in this tutorial) then please see:

As always feel free to use any of the code in this example, and be sure to let me know what comments or thoughts you’ve got!

Related Posts

Post a Comment

Your email is never published nor shared. Required fields are marked *

*
*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>