# stencil shadows - doom 3 engine - precision errors - shadow cracks - why?

I am testing the limits of the Doom 3 engine - in regards to maximum map size.

I noticed some stencil shadow precision errors that become more pronounced when objects get further and further away from the map origin.

at position: -10901 -18214 -11204

at position: -10802 -26483 -19383

at position: -10802 -34683 -27540

I believe these errors have been referred to as "shadow cracks" but I am not positive what these artifacts have been previously called.

Nearly all of the artifacts appear along the bounds of the lights/shadows - which can be seen here:

Has anyone seen this type of graphical artifact before with stencil shadows? What are they called? What is the cause?

More examples:

This is the vanilla Doom 3 Engine as found here: https://github.com/TTimo/doom3.gpl

I noticed while testing the r_useOptimizedShadows cvar (which handles the shadow volumes of the worldspawn geometry) that the artifact disappeared. Then I worked my way to this function:

R_LinkLightSurf( &vLight->globalShadows, tri, NULL, light, NULL, vLight->scissorRect, true /* FIXME? */ );


which I changed to this:

R_LinkLightSurf( &vLight->globalShadows, tri, NULL, light, NULL, vLight->scissorRect, false /* FIXME? */ );


That gets rid of the artifacts - but now it assumes that we are never inside shadow volumes of the worldspawn geometry. So whenever we do go inside a shadow volume, that shadow volume is not rendered properly.

• Not sure about this specific implementation but floating point loses precision the farther you go from zero. – concept3d Oct 2 '14 at 7:04
• Agreed. 7 digits is just about the maximum amount of precision you can expect from 32-bit floating point numbers (a.k.a. C/C++ float). You've easily used up 5 of those. en.wikipedia.org/wiki/… – snake5 Oct 2 '14 at 11:51
• @concept3d - I suppose converting the relevant floats to doubles would help? If someone had the technical know-how to do that. Even if it would result in longer render times. – Stepan1010 Oct 2 '14 at 15:49
• @glampert I updated the question. It is the vanilla Doom 3 engine. – Stepan1010 Oct 4 '14 at 18:52
• What happens when you change the shadow volume computations? (For example just scale all shadow volumes by a small value) Do the artifacts still appear/ change/ disappear? – Roy T. Oct 5 '14 at 16:16

## 2 Answers

I did finally figure out a solution - actually a few different solutions. I did not figure out the actual cause of the artifact from a graphics programming perspective - but I did find some solutions.

As I previously stated in my question, it appeared that the artifact was only occurring on the pre-computed shadow volumes of the worldspawn static geometry (which is basically geometry that the engine knows is never going to move so it pre-calculates ahead-of-time the shadow volumes and other things with a command entered in the console called "dmap"). I did not figure out why it was only on the shadows of the static worldspawn geometry and not on any of the ASE or LWO models.

Now, the thing that I noticed was that there is actually a plethora of parameters that can be used with the dmap command - one of these parameters is called "shadowOpt" - which must stand for shadow optimization level. This parameter sets an enum - there appear to be a few different shadow optimization levels:

typedef enum {
SO_NONE,            // 0 // NOTE: I haven't tried this one yet - should test this one.
SO_MERGE_SURFACES,  // 1 // NOTE: this was the original default one - it causes some artifacts - the ones I have been trying to fix.
SO_CULL_OCCLUDED,   // 2 // NOTE: this one works the best - takes a bit longer - but it has alot of unnecessary print statements that could probably be removed.
SO_CLIP_OCCLUDERS,  // 3 // NOTE: I haven't tried this one yet - but it is not used anywhere.
SO_CLIP_SILS,       // 4 // NOTE: I haven't tried this one yet - should test this one.
SO_SIL_OPTIMIZE     // 5 // NOTE: this one doesn't seem to work well at all - and it takes an extrememly long amount of time - was probably an expirimental version.
} shadowOptLevel_t;


I have had success with option 2 - "SO_CULL_OCCLUDED". It fixes all the artifacts - it takes a bit longer to run - but I believe a lot of this time is spent printing large amounts of information to the console - these prints could probably be reduced or done away with.

One the of the places that gave me some clues was the comment here in tr_stencilshadow.cpp:

// if we are running from dmap, perform the (very) expensive shadow optimizations
// to remove internal sil edges and optimize the caps
if ( callOptimizer ) {


Now, the problem with only doing this "extra" shadow optimization during "dmap" is that if any of these lights are ever moved (which is always possible depending on the type of project you are doing) - it will then default back to the "un-optimized" real time shadow volume creation process (for the moved light) and the artifacts will reappear for that light. So the only way to guarantee that these artifacts will not appear is to always run the very expensive optimization process for these static worldspawn shadows. It is in fact very expensive so this would be an absolute last resort if you can't figure out a proper graphics solution. (if you do, make sure to post your solution here.)

I would recommend for anyone creating large maps for the vanilla Doom 3 engine - and using worldspawn geometry - that they create a cvar that they can change depending on their needs for the real-time creation of the optimized shadow volumes. I called my cvar r_useExpensiveShadowOptimizations - which appears to be an oxymoron. For example:

// if we are running from dmap, perform the (very) expensive shadow optimizations
// to remove internal sil edges and optimize the caps
if ( callOptimizer || r_useExpensiveShadowOptimizations.GetBool() ) {


I also recommend that depending on how large your maps are(and assuming the lights will not move), that you increase the static shadow volume optimization level with the "shadowOpt" parameter for dmap.

So basically all of the things you need to have a large map and not have shadow artifacts are there for you, you just need to decide which ones you will need to use. Doing it in real time is extremely expensive and should only be done as a last resort if you can't find a proper graphics solution. Doing it in DMAP makes perfect sense as it solves the problem and only takes a few more seconds for the map to compile.

It could very well be a floating pointer precision error, since Doom used floats for the rendering (mainly an OpenGL limitation). However, fiddling around tr_stencilshadow.cpp, I've noticed this comment which might be related to the issue (inside PointsOrdered() function):

// vectors that wind up getting an equal hash value will
// potentially cause a misorder, which can show as a couple
// crack pixels in a shadow

....

// in the very rare case that these might be equal, all that would
// happen is an oportunity for a tiny rasterization shadow crack


So there it is. It might also be a known limitation of the way the shadow rendering was implemented. Quite frankly, the code is very messy and hard to read, so I can't tell you for sure. You might try mailing the devs to get more info.

• I also saw that comment. However, when I commented out the code of that function and just had it return true(or false - it doesn't matter) -- it doesn't seem to make any visual difference to anything at all. Do you think any of the devs would respond if I emailed them? I feel like they probably wouldn't respond. I guess I could try that if all else fails. – Stepan1010 Oct 4 '14 at 22:01
• @Stepan1010 Well, don't know, Carmack is a cool guy, I once mailed him about something not related to programming and he did reply. I guess it won't harm trying... But maybe you should mail the repo maintainer, as Carmack is no longer a member of idSoftware. If you show a legit problem and the work you did, they might be interested. – glampert Oct 4 '14 at 22:23