Real-time shadows

Oh yeah, real-time shadows. I've been waiting for these so long and now I'm gonna use them for absolutely everything in my game. Each leaf of a tree is going to cast its shadow onto the terrain. That's going to look so cool! Absolutely everything will have its own shadow, just like in reality!

If that's what you are thinking right now I have to disappoint you. That's simply not doable. No PC would be able to run this smoothly. Nevertheless, the real-time stencil shadows of Ultimate 3D are a cool feature. Scenes without shadows seem unauthentic. Things need to be shadowed to look realistic. Shadows help the human eye to understand the three dimensional structure of the two dimensional pictures seen on the screen. In shocking games shadows can contribute to the dramatic effect of the happenings greatly. They can give you an idea of things you do not see directly. This way a shadow can scare the hell out of people playing a game, at least if it's done right.

It's not quite wrong to see the real-time shadow casting as a method to improve the dramatic potential of your game, but it needs to be used carefully to get a proper performance. In this context the most important lesson is that real-time shadows should be used only for non-static objects and light sources. For static shadows you can use techniques like light mapping, which can be realized through the multi texturing functions and are a lot more efficient.

Ultimate 3D uses hardware accelerated stencil shadows. You do not need to know how these shadows work, but you should know about the following advantages and disadvantages of this technique. The advantages are mainly the extremely high quality of the resulting shadows, which are absolutely precise for every pixel, the low required hardware capabilities (no pixel shader support is required), the reliability, the trouble-free combination with shader effects like per pixel lighting and the fact that they are fully hardware accelerated, so they do not stress the CPU that much. The disadvantages are that your models have to fulfill some conditions to be valid shadow casters; that they stress the GPU quite a lot; and that soft shadows aren't possible.

Using real-time shadows

The use of real-time shadows in Ultimate 3D is very easy. The first thing you have to do is make sure that they are supported. If so you can enable shadow casting for particular light sources, set up particular model objects as shadow casters and set up model objects, terrain objects or primitive objects as shadow receivers. The first thing you have to do to be able to use shadows, is setting global.u3d_z_buffer_format to 32 in the Ultimate3DOptions() script. For more information about the meaning of this variable, please refer to the chapter about Ultimate 3D options. The reason that there can not be shadows without a 32-bit z-buffer is that 8-bits of the z-buffer are used as stencil buffer which is needed to compute the shadows. If the PC you are using is current enough and if the z-buffer has the right format, the function GetShadowSupport() will return true, which means that you can use real-time shadows. Otherwise you can't. All shadow related functions will not do anything then.

Now that you've made sure that shadows are supported you can call the following function to enable shadows for a particular light source.

This function enables or disables shadow casting for the light source object it's being called by. It has to be called after LightDirectional(), LightPoint() or LightSpot().


If you pass true for this parameter, Ultimate 3D will enable shadow casting for this light source, otherwise it will disable it.

Next you can define which model objects cast shadows for which light sources.

This function enables or disables shadow casting for the model object it's being called by. If the model uses vertex skinning it must not use more than 28 bones, since all additional bones will get transformed incorrectly. The reason for this behavior is the limited number of vertex shader constants available in the used Direct3D version.


The Game Maker id of the light source object this model should cast shadows for.

Whether shadow casting is to be enabled (true) or disabled (false).

Finally you need to be able to set which objects can receive shadows. That's just as easy.

This function turns the receiving of shadows cast by a particular light source on or off. It applies to the terrain, model or primitive object it's called by.


The light source you want to enable or disable shadow receiving for.

If you pass true, the object will receive shadows from the given light source, otherwise it won't. Note that terrains will not receive any shadows unless they've called
CalculateTerrainLightMap(...) at least once after calling this function.

Now you can enable shadows for light sources, shadow casting for models, and shadow receiving for different object types. That's all you need to know to use shadows with Ultimate 3D. However, the results you'd get with this knowledge would be quite poor in the majority of all cases. The reason for this is that stencil shadows require special geometry to work properly. The geometry mustn't have any holes and it should have triangles representing the edge unless it has a high triangle count. Only if this geometry is available will the shadows look realistic. Luckily, Ultimate3D contains a function to alter a model's geometry so it meets these conditions. As usual you do not have a lot of work with this, because Ultimate 3D does everything automatically. You just have to ask it for this service.

This function creates geometry that's optimized for shadow casting for the model object it's called by. It should be called after LoadMesh() (and the call to CreateLODChain(...) if applicable).


Through this parameter you can define for which level of detail you want to create shadow optimized geometry. Often it makes sense to use lower levels of detail for the shadow casting since the shadows do not need to be that precise. The highest level of detail with shadow optimized geometry will be used for the shadow casting. For this parameter, 0 refers to the highest level of detail (the unchanged model), 1 refers to the next-lower level of detail and so on.

If you pass true for this parameter, this function will create geometry that leads to absolutely precise shadows, but the price is high. It requires the creation of two triangles for every edge of the model. If you pass false these connection patches will not get created, which leads to a higher performance but less detailed shadows.

Unfortunately even this function fails to optimize the geometry correctly in particular cases. In this case you'll get ugly results. The shadow will not stop behind the model. Instead it will go on to infinity in long dark stripes. So what's the condition the model has to fulfill to avoid this effect? It's quite easy to do but a bit hard to understand. All edges in the model mustn't be shared by more than two triangles. Here's a little illustration to clarify this.

Illustration not available

The geometry on the left could be optimized perfectly, because it's made up of single sided edges (blue) and two sided edges (green) only. But the piece of geometry on the right has two edges that are shared by three triangles (red). For this reason the mechanism Ultimate 3D uses to fill holes in model geometry can not find a proper solution and that's why the function will not result in valid shadow optimized geometry in this case. That's why no shadow casting model in your scene can have a triangle configuration like this. It's up to you to make that sure. Ultimate 3D can not do this for you, because a mathematical mechanism that leads to a unique solution can not be found in this situation. But that's a low price for absolutely realistic real-time shadows, is not it ;) ?

Shadow checklist

Recently problems with the shadow casting functions have been reported on the Ultimate 3D Community quite often. In the majority of all cases this was the result of wrong use. It is indeed quite hard to debug problems related to shadowing, because if only one component is wrong the whole shadowing will not work and there is no way to find out whether it is the light source, the shadow caster or the shadow receiver that's not functioning. For this reason here's a checklist you can work with, if your shadows aren't working. It explains all common mistakes and explains how to correct them. Read this before you post any questions related to non-functioning stencil shadows on the forum.

The most common mistake is that SwitchShadowCasting(...) and SwitchShadowReceiving(...) get called before the light source has been created and initialized. In this case the LightSourceID parameter will be invalid and as a result of this shadow casting or shadow receiving will not work. The safest way to avoid this problem is placing calls to these two functions in the step event of the objects. They do not do anything computing time intensive, so it will not be that bad for the performance.

Another mistake that has been done a couple of times is that CreateShadowOptimizedGeometry(...) hasn't been called for the shadow casting model objects. You should always call this function for shadow casters. Without it you will get wrong, imprecise or no shadows in most cases. The only exception from this are models with an extremely high triangle count. For those it may work without a call to this function, but if shadows aren't working and you aren't using CreateShadowOptimizedGeometry(...) you should change that immediately.

If you have problems with using terrain objects as shadow receivers you should check the return value of GetSimultaneousTextureCount(...). If it's smaller than three your system does not support terrain light mapping in combination with texture splatting. And without light mapping you can not get shadows. Another thing you should notice is that terrains with a custom light map can not receive shadows.

If you have functioning shadows, but get strange shadow stripes, which go on to infinity, read the explanation of three sided edges above. Those are usually the cause of this problem. Another possible cause of this problem is that you aren't calling CreateShadowOptimizedGeometry(...) correctly.

One last possible cause of non-functioning shadows is an inappropriate proportion between camera.min_range and camera.max_range. If the result of camera.max_range/camera.min_range is extremely high it can happen that rounding errors make the far-clipping plane go infinite far away. Since the far-clipping plane plays an important role for shadow casters shadow casting will not work in this case. To fix this you simply need to increase camera.min_range and/or decrease camera.max_range.

© Christoph Peters. Some rights reserved.

Creative Commons License XHTML 1.0 Transitional