In this post, I thought I’d talk a little bit about our approach to shadows in Nome. Shadows don’t (currently!) have any direct effect on gameplay – so it might seem misguided to give them too much attention early on in the game’s development. However, they do serve some purpose: combined with other dynamic lighting effects, fog etc, they give the player information about the environment around them – they help place relative position of objects in the scene, the time of day, and current weather conditions, for example.
Consider the following image which shows the Nome sprite – a 2D billboard mesh – placed on a 3D terrain, which is how our scenes are constructed. So what are some of the different ways to approach adding shadows in this setup?
“Blob” shadows are perhaps the simplest form of shadows commonly used in games. They are typically fuzzy-edged dark patches drawn underneath the player’s feet, as if a directly overhead sun had shone down on some indiscriminately-shaped object. Although not having any correlation with lighting in the scene, they do serve to visually “ground” the player on the terrain. They have a particular usefulness in platform games, since the location of the shadow provides an instant indication of the surface directly below a player in the air – crucial when making pixel perfect landings. In some games, the blob shadow changes in size based on the character’s height above the ground, which can also be used as an indicator to place the character in the scene.
Aside from their obvious lack of realism, another problem with blob shadows is that they can end up “floating” in the air if the character stands near an edge. They are also unable to change shape – to depict long shadows you’d expect to see at early mornings or evenings, for example.
Rather than using a generic blob shape, this method creates a copy of the sprite casting the shadow, but with colours removed from the texture to leave only a silhouette of the object. This shadow sprite can then be distorted with a shear/scale matrix to simulate shadows caused from various light directions, and rendered using a multiplicative blend mode to cast shadows on other elements in the scene. This is the same approach used to create “drop shadows” in many 2D art/layout programs.
This is an improvement in realism compared to the blob approach, while still being relatively simple to calculate. By adjusting the projection matrix with which the sprite is rendered, it is possible to create long shadows that recede into the scene, or come towards the camera, based on the proximity and position of any lights in the scene. However, one limitation with this approach is that the shadow is itself still a 2D sprite, calculated in screenspace, and then simply overlaid onto the existing scene. Since it does not process any depth information, it will not correctly adapt to the contours of surfaces in the scene. To draw attention to this problem, consider what happens if we add another object to the background of the scene – notice how the shadow remains completely unchanged from the previous image:
Significantly more accurate than either of the previous methods, shadow mapping involves imagining a camera placed at a light source to determine which parts of the scene the light “sees” (i.e. are in light), and those surfaces that the light can’t see (i.e. are in shadow). This information is recorded to a depth texture, and is then used by the regular camera when rendering the scene.
Commonly used in 3D games, it can also be used in “2D” games if, as in Nome’s case, sprite meshes are billboards positioned at different depths in the scene (it would not work for an orthographic 2D scene in which all sprites are drawn at the same distance to camera).
This method requires GPU memory to record the depth information, which must be repeated for every light source in the scene. Also, the limited texture resolution can cause pixelated shadows.
However, unlike the screenspace sprite shadow method, it does correctly render shadows according to the surface on which they fall, as shown below:
This is the method we’re using currently, and here’s an image of how shadows appear in the game, with shadowmaps created by both suns. We’re hoping that the combination of the suns’ position in the sky, the colour of the atmosphere (created using physically-based rendering as described here), and the position and length of the shadows give the player enough information about the time of day in this scene without needing to add an explicit “clock” UI – as development continues we’ll see whether we’ve succeeded in this aim!: