Realtime Shadow Techniques Used By Grit

Depth Shadow Mapping

Grit has full dynamic shadows that are calculated in real time on the GPU. The basic technique is called depth shadow mapping http://en.wikipedia.org/wiki/Shadow_mapping(external link). It involves rendering the scene from the light (the sun or the moon) into a texture, called a 'shadow map'. The shadow map is updated every frame, because objects move and so does the light. However the colour of the scene is ignored, we are only interested in the depth of everything visible from the light, i.e. the distance from the light to its nearest occluder in all directions. When the scene is rendered from the player's point of view, this shadow map is used as a reference to help decide if a given pixel is the closest one to the light (in which case it is not in shadow) or whether there is something else that is closer (in which case it is rendered darker because it is in shadow).

Perspective Transform

There is a perspective transform applied in order to concentrate as many of the shadow map's texels as possible in the area nearest the player. There are many techniques but the one used in Grit is called LiSPSM (LIght Space Perspective Shadow Mapping) http://www.cg.tuwien.ac.at/research/vr/lispsm/(external link). The worst case is when the sun is directly behind you, in which case no perspective transform can be applied, and the shadow is very low detail and noisy. If you look 90 degrees to the sun direction however, the shadows will be a lot crisper due to the use of LiSPSM. Note that increasing the resolution of the shadow map texture will also make the shadows crisper, but will cost memory and performance.

The perspective transform changes every frame depending on the light direction and the chase cam's direction. Sometimes the changes can be quite severe. This causes an unavoidable 'crawling' effect in the shadows.

Covering Longer Distances

There are in fact 3 shadow maps used. One for the area closest to the player, one to cover the area further away, and the 3rd one for the furthest reach of the shadow (200 metres). They are all the same size textures, but the one nearest to the camera covers a much smaller area and thus the shadows are better defined. Another way of looking at this is that it allows shadows to appear much further from the player, without compromising the quality of shadows near the player. The exact technique used in Grit is called PSSM (Parallel Split Shadow Mapping) http://http.developer.nvidia.com/GPUGems3/gpugems3_ch10.html(external link). Sometimes you can see the transition from one shadow map to the next, as a sudden decrease in shadow quality.

Soft Shadows

If each screen pixel was merely tested for being in shadow or not, the shadows would be very hard-edged because of the sudden transition from 'in shadow' to 'not in shadow'. To avoid this, we soften the shadows using a technique called PCF (Percentage Closer Filtering) http://http.developer.nvidia.com/GPUGems/gpugems_ch11.html(external link). This boils down to testing the shadow map several times per screen pixel, and taking the average. The appearance is that several faint shadows have been overlaid in slightly different positions, to produce a blurred effect. It can get very slow but there is hardware support that we are currently not using that can help. This will be implemented eventually http://code.google.com/p/grit/issues/detail?id=125(external link).

Possible Artefacts

There are certain things that can go wrong with dynamic shadow implementations like the ones used in Grit. There are some things to avoid when modelling objects, in order to avoid problems.

Holes in shadows

Since the shadows are calculated by rendering the scene from the sun (or moon) you have to make sure that your geometry, when viewed from this direction, appears to be opaque. This means cliffs must have polys around the back facing the sun, in order to the sun shining through them to the front. Another alternative is to turn on the rendering of backfaces in the material.

If your map is an island that drops below sealevel in all directions, you don't have to worry about this.

Shadow Texture Stretch

Since the shadow texture is projected onto the scene from the light, surfaces that are perpendicular to the light (e.g. flat ground at sunset) will experience very bad texture stretch. This causes aliasing artifacts. Because of the LiSPSM perspective transformation, the artifacts have a very nasty sawtooth appearance.

To visualise the aliasing, we can use:

 lua> debug_cfg.falseColour = "SHADOWYNESS"

This shows the shadow mask projected onto the scene with equal intensity on all triangles.

Shadow texture stretch occurs where polys are not facing the light.

Luckily these areas should not be receiving very much light due to the diffuse lighting equation. E.g. if the light is incident at 15 degrees then the amount of lighting would only be 25% (i.e. sin(15)) of the amount of light that it would receive at 90 degrees. This means the shadow is much less distinct in these areas. This shows the actual shadow, i.e. incorporating the diffuse lighting as well:

 lua> debug_cfg.falseColour = "SHADOW_MASK"

Shadow texture stretch is not usually a problem because the areas where it occurs are dark anyway.

Normal Bending

If your mesh has sharp edges between polys (an angle of more than 20 degrees for example) and is smooth shaded, then the normals interpolated across that mesh will be considerably different to the 'true' normal of that face (i.e. the normal you would calculate using the positions of the 3 vertexes). For example, if you model a cube and use smooth shading then the normal of each face will be orthogonal, but the normals will be smoothly interpolated around the cube causing a huge amount of normal bending at the edges of each face.

Normal bending is usually OK, but causes a problem with shadows. This kind of artefact often occurs on sharp terrain like cliffs. It causes areas to be subject to lighting that would not ordinarily be lit, and therefore causes shadow artefacts to appear that would ordinarily be hidden in the darkness. If the face is in-line with the light, e.g. cliffs at noon, and there is significant normal bending, then the interpolated normal may point slightly towards the sun even though the polygon is at 90 degrees to the sun.

Shadow artefacts caused by normal bending. This is with shadowObliqueCutOff set to 0.

The best fix for this is to model in a way that does not have such extreme smoothing of the normals. However to avoid explosion of polycount, there is also an alternative — the shadowObliqueCutOff material property.

Here, shadowObliqueCutOff is set to 20 degrees.

Note, however, that the shadows are less distinct when using this feature — it is better to avoid the normal bending as much as possible when modelling the terrain in the first place.


Imprecision in the shadow map, which records the distance of each occluder from the light, causes the shadow to fluctuate, causing unpleasant high frequency transitions from 'in shadow' to 'not in shadow' on every surface. The engine will avoid shadow acne by adding a certain amount of bias to the shadow. Thus the shadow is pushed away from the light by enough in order to avoid the noise being an issue.

Shadow acne, caused by rendering shadows with no bias at all. Imprecision in the shadow map are causing the position of the occluder to fluctuate in the direction of the light, causing unpleasant high frequency transitions from 'in shadow' to 'not in shadow'.

Here is a diagram illustrating the problem:

You don't need to worry about this as a modeller, as the engine handles it internally. However, you should be aware that there is a bias applied to the shadow by the engine in order to avoid it.

Additional Bias

You can add additional bias yourself, in the material, in order to get rid of other artefacts. For example here there are unwanted shadows on the tank. There is simply not enough fidelity in the dynamic shadows to do this well enough. We would rather there were no shadows at all.

Unwanted self-shadowing.

One way to avoid this is to avoid these kind of nooks and crannies in the geometry of the object. However since these contribute greatly to the appearance of objects, we would prefer some other solution. We can add another 0.1m to the bias in order to push the shadow far enough away from the object to hide shadows on the high detail parts of the mesh.

Here we use a bias of 0.1 to push the shadow 0.1m further from the occluder.

Shadow Disconnection

Too much bias can cause a problem in itself though. If the bias is increased enough, the shadow will move so far from the object that there will be a 'gap' where the object meets the ground. This gives the unwelcome appearance that the object is 'floating' above the ground. This is also illustrated in the above diagram.

Too high a bias causes the shadow to disconnect from the base of the object.

The bias automatically used by the engine is carefully chosen to be as small as it can be. However as a modeller you must also make sure your additional bias is not too large as well.