Monday, February 28, 2011

A shadow system in a modern game needs to be able to mimic a wide range of shadows. The following text describes a shadow system that was used in the RawK® demo that is tailored to Intel's Sandy Bridge chipset [RawK].
This demo prototypes the characteristics of an open world game, when it comes to indoor shadow rendering. In an open-world game where the viewer can go inside buildings and stay outside as well, there might be shadows for

Cloud shadows, most of the time clouds just projected down

Self-Shadowing for the main character or more characters: those are optional shadows with their own frustum that just cover characters bodies close to the camera

Sun shadows: Cascaded Shadow Maps

Shadows from point, spot and other light types

For the first three types of shadows one might consider a shadow collector that collects the shadow data of all three types in a screen-space texture, that is then filtered and applied to the scene.
Shadows from point, spot and other light types might be cached. Trading memory against the effort of updating shadow maps makes sense on some platforms. The following text will focus on shadows coming from ellipsoidal and point lights but similar thoughts apply for light types other than directional lights.
Developing a shadow system for those light types usually means facing the following challenges:

Shadow Rendering

Shadow Caching

Shadow Bias value

Softening the Penumbra

Shadow Rendering

For point light types and similar light types, the favorite storage method is a cube texture map. Compared to its main competitor the dual-paraboloid shadow map, it offers a more even error distribution. The hemispheric projection for dual-paraboloid shadow maps requires a high-level of tessellation that might not be common in a game where normal maps mimic the finer details.

Rendering into a cube shadow map can be done with a DirectX 10 and above capable graphics card in one draw call with the help of the geometry shader. The performance of the geometry shader on some graphics cards is not as good as one would expect. In those cases it helps to move some of the calculations from the geometry into the vertex shader. The inner loop of a typical geometry shader used to render into a cube map might look like this:

Cube shadow maps are not only useful to store point light shadows but shadows from other light types as well. For example shadows from ellipsoidal lights, where each of the directions has its own attenuation value, can be stored in cube maps as well.

Image 1: Ellipsoid Lighting

Image 2 -- 8 Ellipsoidal Light Shadow Maps

Image 3: Ellipsoid Lighting

Images 4: Many Shadows

Images 5: Level Shadows

Images 6: More Shadows

Shadow CachingDepending on the amount of memory that is available on the platform, caching 16-bit depth cube shadow maps might become an option. For example integrated graphics chips usually share memory with the CPU and might have a higher amount of - then usually slower- memory available. Storing for example 100 256x256x6 16-bit cube shadow maps is about 75 Mb.

To find a good caching algorithm, the following parameters might be useful:

Distance from shadow to camera

Size of shadow on screen

Is there a moving object in the area of the light / shadow ?

From those parameters and others, the question if anything moves in the area of influence of the light / shadow is certainly the most important one. As long as nothing moves or changes in the area of the light, an update of the shadow map is not necessary and the shadow data can stay unaltered in memory.

Even if something is moving in the area of influence of the light, an update of the shadow map might not be necessary if the shadow is not easily visible from the point of view of the player. If the shadow is far away and it is hard to spot that an object is moving through the shadow, it would make sense to not update the map and to keep it cached.
The question if a light with a shadow map with a very small visible area on screen needs to be updated, follows a similar logic.

If there is not enough memory available, caching might be restricted by distance and then maps are moved in and out into the cache.

Shadow Bias Value

The classical shadow mapping algorithm generates a binary value based on a comparison. Because this comparison relies on hardware precision, it is prone to generate slight errors in edge cases.
In case of a regular 2D shadow map, the usual solution is to introduce a shadow bias value. Commonly this value needs to be picked by the user, which makes it scene dependent. In case of cube shadow maps that are attached to a moving light, there is no sensible way to pick a working value.

Approximating the binary comparison with an exponential function will lead to better overall results [Salvi].

There are many approaches that cover the softening of the Penumbra. Certainly all the probability based shadow filtering techniques that can elevate hardware filtering have a very good quality / performance ratio.
Screen-space filtering to achieve perceptually correct cube shadow maps is an area where game developers just started to do research. An implementation is described in [Engel].

Image 8: 16 Screen-Space Soft Point Light Shadows

Image 9: 32 Screen-Space Soft Point Light Shadows

Future Development

Game developers try to move away from pre-calculated lighting and shadowing and any other pre-calculated data. The main reasons to do this are:

hard to mimic a 24 hour cycle

storing those light or radiosity maps on disk or even the DVD / Blu-ray required a lot of memory

streaming the light or radiosity maps from disk or hard-drive through hardware to the GPU consumes valuable memory bandwidth

geometry with light maps or radiosity maps is not destructible anymore (this is a reason to avoid any solution with pre-computed maps)

while the environment is lit nicely, it is hard to light characters in a consistent way with this environment

A shadow caching scheme might be one tool to remove pre-calculated data. Following the recent development in dynamic global illumination in the area of one-bounce lighting effects[Dachsbacher][DachsbacherSI][Kaplanyan], it is possible to store not only shadow data but also data for reflective shadow maps in cube maps. All the ideas mentioned above apply then to this approach. One question that remains them is if it is best to cache the data in cube shadow maps or use a memory area with higher density for this, like a Light Propagation Volume.
In any case temporal coherence can be used to improve shadows and global illumination data over time.

Acknowledgements

I want to thank my business partner Peter Santoki for the help, feedback and encouragement while implementing the ideas covered above. I also would like to thank Tim Martin for help in researching the general topic of cube shadow map rendering and Igor Lobanchikov for the cube map optimizations trick.

Google+ Followers

About Me

Wolfgang is the CEO of Confetti. Confetti is a think-tank for advanced real-time graphics research and a service provider for the video game and movie industry. Confetti worked in the last three years on many AAA IPs like Tomb Raider, Battlefield 4, Murdered Soul Suspect, Star Citizen, Dirt 4, Vainglory, Transistor, Call of Duty Black Ops 3, Battlefield 1, Mafia 3 and others.
Wolfgang is the founder and editor of the ShaderX and GPU Pro books series, a Microsoft MVP, the author of several books and articles on real-time rendering and a regular contributor to websites and the GDC. One of the books he edited -ShaderX4- won the Game developer Front line award in 2006. Wolfgang is in the advisory boards of several companies. He is an active contributor to several future standards that drive the Game Industry.
You can find him on twitter at