{"id":2524,"date":"2012-05-15T20:16:13","date_gmt":"2012-05-15T10:16:13","guid":{"rendered":"http:\/\/www.vectorstorm.org\/?p=2524"},"modified":"2012-05-16T12:56:23","modified_gmt":"2012-05-16T02:56:23","slug":"lets-talk-about-shadows","status":"publish","type":"post","link":"https:\/\/www.vectorstorm.com.au\/2012\/05\/15\/lets-talk-about-shadows\/","title":{"rendered":"Let’s talk about shadows"},"content":{"rendered":"

There are a lot of different ways to create shadows in video games.<\/p>\n

When I entered the game industry fifteen years ago, there were two standard ways to draw shadows:\u00a0 blob shadows and stencil shadows.\u00a0 (There was a third option as well:\u00a0 light maps, as popularised by Quake 2.\u00a0 But I’m not going to cover those since they were used for handling static lighting, not for real-time dynamic effects.)<\/p>\n

\"\"<\/a>Blob Shadows<\/h2>\n

Blob shadows are a very simple concept.\u00a0 If we have a moving object that we want to have appear to cast a shadow, all we have to do is to draw a sprite on the ground beneath it.\u00a0 This sprite was typically an indistinct, blurry oval, roughly matching the general size of the object it was “shadowing”.\u00a0 Its texture is where the term “blob” came from.\u00a0 In this shot, you can see a robot with a blob shadow.\u00a0 (That blob shadow is substantially larger than I would ever make it, personally.\u00a0 At this scale, it’s far too easy to tell that it’s just a simple sprite).<\/p>\n

A tricky point about implementing blob shadows is making the shadow appear correctly over a ground surface;\u00a0 making it draw on top of an undulating terrain is a surprisingly tricky problem.\u00a0 It’s very easy to end up with a shadow sprite that clips through the ground or floats above it, or is visible through other objects between the shadow and the camera.\u00a0 Since the shadow is basically just a big flat sprite, you can’t reliably cast the shadow onto an extremely bumpy surface, or onto another object.\u00a0 You’re basically stuck with just a flat circle on a (more or less) flat ground.<\/p>\n

Fifteen years ago, these were the usual way of putting dynamic shadows on movable objects in a game.\u00a0 As games became more sophisticated, we started finding ways to disguise how simplistic these shadows were.\u00a0 First by scaling the blob as the character animated (so as the character moved his leg forward, the circular shadow would stretch forward into an oval shape), and then by actually giving each character several different blob shadows, each projected down from a different part of the character’s body.\u00a0 This made the shadow seem to vaguely move along as the character animated in a much better way than the simple scaling approach.\u00a0 In Transformers<\/a>, we gave each enemy robot (I seem to recall) four or five blob shadows, each anchored to a different part of the robot’s body.\u00a0 When rendered, these separately circular blob shadows appeared to merge together into a single amorphous blob that moved in a way which didn’t draw the player’s attention as being circular and static.<\/p>\n

This technique is still around and being used today in new games.\u00a0 Just a few months ago I implemented it for use in an (unreleased) iPhone racing game, where we used vaguely car-shaped blob shadows under the cars.\u00a0 With some very sophisticated maths to try to make the shadow mimic the car’s rotation in mid-air, and projecting down onto the race track surface according to an angled light source and angled track.\u00a0 But fundamentally, if you ignore the fancy math for scaling and orienting it to the ground surface, it was still just a single blob shadow;\u00a0 just a flat sprite with a pre-drawn texture on it.<\/p>\n

I seriously considered using blob shadows for MMORPG Tycoon 2.\u00a0 It certainly would have been faster to implement than what I’ve actually done.\u00a0 But all the foliage in the game would have caused problems, since the shadows would have sorted underneath the foliage and been almost invisible in many situations.\u00a0 (Even in modern games you occasionally see this problem, blob shadows vanishing under ground clutter)<\/p>\n

Stencil Shadows\"\"<\/a><\/h2>\n

Also called “shadow volumes”, stencil shadows were the high-quality alternative to blob shadows.\u00a0 They’re not used as much today as they used to be, but it’s still an interesting technique.<\/p>\n

The basic idea of stencil shadows is to computationally find the silhouette of an object from the light’s point of view, and then extrude that silhouette through space.\u00a0 This extruded object is the shadow cast by the object.<\/p>\n

You then render this shadow object using a “stencil buffer”, and use the resulting stencil data to determine for each pixel in your screen image whether that pixel is inside or outside of the shadow volume.\u00a0 I worked on several games which used this sort of system.\u00a0 Usually this approach was only used for the shadow on the player character, or sometimes large boss enemies;\u00a0 it was really too CPU intensive for us to be able to afford to do it for all characters on-screen.\u00a0 And even when we did do it, we would use simplified versions of the visual models;\u00a0 usually just a series of boxes or elipses roughly approximating the character’s shape.\u00a0 Finding the silhouette and building renderable extruded geometry every frame was simply too expensive.<\/p>\n

Another interesting point is that because of the way that stencil buffers work, the shadow was always hard-edged;\u00a0 each pixel was either in-shadow or out-of-shadow, so shadows couldn’t feather or soften with distance.<\/p>\n

To do this, you had to render the shadow volume with a special shader, and you had to render any object which might receive a shadow with another special shader.\u00a0 This was always more expensive than normal rendering, so quite a lot of effort often went into determining precisely which triangles might receive a shadow, just for the purposes of figuring out which bits of world geometry needed to have the expensive shaders enabled.<\/p>\n

Fun note:\u00a0 The further your shadow geometry is extruded, the longer it takes to render, both because it touches more pixels, because it will more often have to go through the expensive geometry clipping process, and also because it potentially hits more world geometry, thus requiring more stuff to render using the expensive shadow-receiver shader.\u00a0 But if your renderable geometry didn’t actually hit a shadow receiver (the ground underneath the character, for example, because the character was high up in the air from a jump or something and the shadow geometry wasn’t extruded far enough downward to reach the ground), then no shadow would be visible at all;\u00a0 the shadow would appear to suddenly pop into being as soon as the shadow caster moved within range.\u00a0 Most games tried to hide this pop-in by making the extruded shadow geometry be built like a spike;\u00a0 smoothly scaling down into a single point — kind of the opposite of the shadow volume being extruded from the torus in the image above.\u00a0 If you look at games from the early 90s, you’ll occasionally see games which do this;\u00a0 you can tell because you’ll see that shadows get smaller as objects move away from their cast shadows, instead of getting larger.<\/p>\n

To my knowledge, no modern games use stencil shadows any longer.\u00a0 And that’s mostly because they’ve been completely replaced by the new favoured system(s): shadow maps, which are better in almost every way.<\/p>\n

Shadow Maps<\/h2>\n

<\/p>\n

As hardware has advanced, the power of graphic cards and rendering speeds have accelerated far faster than regular CPU processing.\u00a0 As a result, we’ve started to push all sorts of processing over to graphic cards whenever possible, since the graphic cards often have a lot more processing time available.\u00a0 Shadow maps are one of those cases.<\/p>\n

\"\"<\/a>The basic idea of shadow maps is to actually render our scene twice:\u00a0 once from the point of view of the light, and then again from the point of view of the camera.\u00a0 By comparing what’s visible in the two shots, we can tell what is in shadow and what is visible from the point of view of the light.<\/p>\n

In this screenshot I’ve put a copy of the light’s render of the scene in the top right corner.\u00a0 A few things to point out about it:\u00a0 the light doesn’t care about colors or textures;\u00a0 all it cares about is how far away an object is.\u00a0 So it’s drawing its view of the scene with darker colors representing close objects, and lighter ones being further away.\u00a0 It’s also worth mentioning that the light doesn’t turn with the player’s camera;\u00a0 it’s always oriented with north at the top of the image.\u00a0 In this image, the camera is facing approximately northwest.<\/p>\n

In the light’s view you can (roughly) see the column I’m standing beside, as well as the trees that are casting the large shadows on the left.\u00a0\u00a0\u00a0 (The more distant objects are not visible in the light’s rendering of the scene.\u00a0 I’ll talk about that in a moment)<\/p>\n

So after I’ve rendered the light’s view of the scene, I then draw the scene normally, but with a little extra logic running per pixel.\u00a0 The logic goes like this:\u00a0 For each pixel I draw on the screen, I figure out where that pixel is in the world, and then where that position would be in the light’s view.\u00a0 If the pixel is further away than what the light could see during its render, then I know that that pixel is blocked from the light, and so I should draw that pixel as being in shadow.\u00a0 If not, then it’s in the light.\u00a0 And that’s pretty much all there is to it!<\/p>\n

I’m a dirty liar<\/h2>\n

Actually, no, there’s a lot more to it than that.\u00a0 See, the chief problem with shadow maps is resolution.\u00a0 When the camera is close to shadows (as in this screenshot, and in almost all first-person perspective games), we really need a lot of pixels in our light’s view of the scene to keep our shadows from appearing blocky.\u00a0 But we only need that high resolution for the shadows that are close to the view;\u00a0 if we drew the light’s view of the scene at that sort of resolution everywhere, we’d have a terrible frame rate.<\/p>\n

So what almost every modern first-person-view game does is to use a system called cascaded shadow maps.<\/p>\n

\"\"<\/a>A simple overhead-view of the player’s first-person view of a scene looks something like this.\u00a0 The black trapezoid (“frustum”, in 3D) represents the part of the game world which the player will be able to see in a particular frame.\u00a0 Anything outside of that frustum won’t be visible to the player, unless the player turns or moves.\u00a0 The player is at the narrow end of the frustum, looking toward the wide end.<\/p>\n

When viewed from the player’s position (that is, not from an overhead view like this one), an object at the narrow end of the frustum is drawn using a lot more pixels than that same object being drawn at the wide end of the frustum.\u00a0 And this means that we need a lot more detail on things being rendered at the narrow end than at the wide end.\u00a0 This is true for everything, and a lot of computer graphic technology is built around dealing with this “how can we draw high quality stuff close to the player, and lower quality stuff further away” problem.\u00a0 For models, we have LODs;\u00a0 simpler versions of models which can be drawn with fewer triangles, for drawing when models are far away from the player.\u00a0 For textures, we have mipmaps, pre-filtered textures which are optimised for being displayed at smaller sizes on the screen.<\/p>\n

Similarly, for shadows, we use cascaded shadow maps.\u00a0 The idea is that rather than draw one big shadow map as we discussed above, we instead want to draw several, each covering a different part of the view frustum.\u00a0 Traditionally, each shadow map (called a “cascade” in this context) has the same number of pixels in it;\u00a0 often 512×512 or 1024×1024.\u00a0 But one will cover a very small box in front of the player (thus having a very high pixel density), another will cover a larger area past that (thus having a lower pixel density), and there may be several more, each covering larger areas located further away from the player, and having coarser and coarser pixels.\u00a0 In the screenshot here, I have roughly estimated the positions of three cascades to fit the frustum, and outlined them as grey boxes.<\/p>\n

Under this approach, when rendering a pixel, we first check how far forward it is from the camera, and then based on that distance forward, we pick which cascade to look in to decide whether that pixel is in light or in shadow.\u00a0 Right now, MMORPG Tycoon 2 has three cascades.\u00a0 One covers the first ten meters in front of the camera, the next covers the next 50 meters, and the last covers the next 800 meters.\u00a0 Each is rendering a 1024×1024 image from the light’s point of view.\u00a0 I’m not certain that these numbers are the best choices right now;\u00a0 It may well be that I’d be better off using more cascades with smaller images for each.\u00a0 Or maybe the cascades should be spaced differently.\u00a0 But fundamentally, the technology is in place and working;\u00a0 it’s only fiddling with numbers that’s left to do to tweak the behaviour.<\/p>\n

Almost every modern first-person game out there uses exactly this system for rendering their shadows, at least in outdoor scenes.\u00a0 It’s easy to spot, once you know what to look for.\u00a0 Because we’re rendering several different shadow maps at different resolutions, all you have to do is walk forward in a game, and look for a spot where shadows change their level of detail.\u00a0 In an FPS, this will ordinarily appear as a horizontal line across the ground, where shadows visibly become more defined as you move forward.<\/p>\n

So that’s cascades.\u00a0 There are a few other issues to be considered, but they’re probably too far down into fiddly points to be of much interest to most folks.\u00a0 I already talked about surface acne in a previous post.\u00a0 The opposite of that problem is called “Peter Panning”, where you’ve biased the shadows so much that they actually detach from the objects that are casting them, causing the objects to appear to be floating above the ground.<\/p>\n

Another common problem is shimmer at the edges of shadows, as the camera moves and rotates.\u00a0 The trick to solving this is to move the light-view cameras in increments that match the distance between pixels in their shadow map, not rotating the light-view cameras to try to better fill the frustum, and not changing the sizes of the cascades from frame to frame.\u00a0 Doing these things means that if I move the camera (say) three pixels to the right, it will generate exactly the same shadows as it did before, just three pixels away from where it was generating them before.\u00a0 By comparison, if I moved the camera by a non-integral number of pixels, the shadows would still look very similar, but the precise pixels at the edges of a shadow shape would change, just based on rounding differently.\u00a0 And this generates a very distracting shimmer around the edges of shadows.<\/p>\n

That’s really all I have to say on the subject.\u00a0 If I’ve forgotten something or if you’d like more detail on one bit or another, please ask in the comments!<\/p>\n","protected":false},"excerpt":{"rendered":"

There are a lot of different ways to create shadows in video games. When I entered the game industry fifteen years ago, there were two standard ways to draw shadows:\u00a0 blob shadows and stencil shadows.\u00a0 (There was a third option as well:\u00a0 light maps, as popularised by Quake 2.\u00a0 But I’m not going to cover…<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"spay_email":""},"categories":[33,24,25],"tags":[],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"jetpack_shortlink":"https:\/\/wp.me\/po9WK-EI","_links":{"self":[{"href":"https:\/\/www.vectorstorm.com.au\/wp-json\/wp\/v2\/posts\/2524"}],"collection":[{"href":"https:\/\/www.vectorstorm.com.au\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.vectorstorm.com.au\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.vectorstorm.com.au\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.vectorstorm.com.au\/wp-json\/wp\/v2\/comments?post=2524"}],"version-history":[{"count":0,"href":"https:\/\/www.vectorstorm.com.au\/wp-json\/wp\/v2\/posts\/2524\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.vectorstorm.com.au\/wp-json\/wp\/v2\/media?parent=2524"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.vectorstorm.com.au\/wp-json\/wp\/v2\/categories?post=2524"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.vectorstorm.com.au\/wp-json\/wp\/v2\/tags?post=2524"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}