Shadowmapping on iOS

One of things I like about both computer graphics and image processing is that things that we humans think as logical and easy, to a computer are anything but (but yet computers are able to spot patterns that we would miss). Drawing shadows is a case in point: we are so used to seeing the nuances of shadow all around us, that we don't really stop to think about why a surface is in shadow.

There a several methods of drawing shadows in computer graphics, but probably the best known is that of 'shadow mapping'. The basic principal is to store the distances of every point that the light can see in a 'shadow map'. Then when we come to actually draw the scene to the screen, we compare the current pixel (or, more accurately, fragment) with its equivalent in the shadowmap. And if the distance is greater, we paint that pixel in shadow.

My favourite resources for learning the basic shadowmap algorithm is here.

If you have ever written a shadow mapping algorithm, one thing you'll notice is that the basic algorithm creates 'hard edges' i.e. a pixel is either in shadow, or it isn't. To overcome the 'jagged' effect that this creates, we can implement Percentage Closer Filtering (PCF), which samples several pixels in a fixed region, and averages the result. This gives us a nice 'soft' shadow. A nice resources for PCF filtering is here.

While writing a 3D engine for iOS, I noticed that the basic PCF algorithm gave pathetic performaces - it reduced the framerate from ~60fps (with hard shadowing) down to single figures. So I decided to try to improve it

By adding an extra pass into the render algorithm, I ran a very simple edge-detection convolution filter on the shadow map. This gave me a mostly black image with a greyscale edge around any objects in the scene. By sampling this 'shadow-edge map' at higher mipmap levels (a technique called mipchain dilation) we can essentially create a mask, and sample it to only apply the PCF filtering within the mask, thus saving quite a lot of unnecessary calculation. This improves performance by a factor of 3 or 4!

The edge-map thing gave me an idea - the greyscale value of the edge indicates its strength i.e the magnitude of the distance between the object and background. This could be a very fast way to estimate the distance of the occluder to the shading surface, which may allow a way to draw a variable penumbra (i.e. make the shadow softer further away from the occluding object, and harder nearby).

By quantizing the edge strength, I managed to apply different levels of PCF (thus different width shadow) at different areas of the shadow, to give simple but quick variable penumbras. And it only costs a few more FPS - the scene is still very much interactive. Below you can see a screenshot of my test scene, and a github link to the code.