Programming Guides

Shadows

The osgShadow nodekit is a powerful collection of classes for adding shadows to your scene. Thanks to this powerful nodekit, you don't need to learn the complex calculations, multi-texturing units and programmable shaders involved in creating shadows. Just add these nodes to your scene graph, and the work is done for you.

The class documentation for osgShadow shows the classes and methods. This document describes how to use them.

Simple Case

In the simplest case, there is a single light enabled in your scene. You create a ShadowedScene node (a subclass of osg::Group), and add children to it. A ShadowTechnique is chosen and assigned to the ShadowedScene. The children of ShadowedScene can have the CastsShadow bit set in their node mask, and/or the ReceivesShadow bit. As the scene is rendered, ShadowedScene calls the methods of its ShadowTechnique to compute the shadows and decorate the scene graph with StateSets to render them:

 

 

The Techniques

As of OSG 2.4, there are five different shadow techniques available. All of them have varying requirements on the capabilities of your 3D card. If your card's shader engine and driver does not support certain commands, then it will not be capable of using some of these techniques.

To summarize:

Method uses textures uses osg::Shader respects CastsShadow respects ReceivesShadow notes
ShadowMap yes yes yes no Can work fixed-function too without any changes, just call clearShaderList() after init() but before first frame
ShadowVolume no no no? no  
ShadowTexture yes no yes no Can't do self-shadowing, so setting a node's CastsShadow bit means that node won't receive shadows.
SoftShadowMap yes yes yes no  
ParallelSplitShadowMap yes yes yes no  

Filter 3x3 implemented in PSSM

Filter 3x3 Matrix:

1 0 1
0 2 0
1 0 1

Filter divisor: 6.0

original filtered

Ambient Bias

The AmbientBias option on ShadowMap and SoftShadowMap is used to define how much shadows darken the scene. Its usually used to set up lower bound for shadowing factor, in the range [0..1]. If such shadow factor was used directly, shadowed areas would be completely black. Sometimes we want to make them to be only a bit darker than lit areas. By setting AmbientBias.x to some value from range 0..1 one limits minimal shadow value (hence AmbientBias because shadowed areas are lit only by ambient component). AmbientBias.y is usually set up to 1 - AmbientBias.x but it may be also set bit larger or smaller values used to make shadow range more dynamic or flat.

Texture Resolution

Generally speaking, a projected texture is used to render the shadow. This means that if your shadow casting geometry is very large, then the texture resolution must be spread over a large area. This can produce blocky aliasing in the shadow. Therefore, it is advisable to keep the bounding sphere of your shadow casting geometry as small as possible. Just placing a ShadowedScene above a large scene graph, where everything casts a shadow, is not likely to produce good results. You should very deliberately choose which nodes will cast shadows.

You can also increase the resolution of the texture with the ShadowTechnique's setTextureResolution method, but this is not a long term solution but rather just hides the problem. More advanced shadow techniques like PSSM (Parallel-Split Shadow Maps) or LiSPSM (Light-Space Perspective Shadow Maps) can help in those cases. (note: for now only PSSM is integrated into osgShadow and it does not work in all cases).

Example Code

This code loads two cessnas, offset from each other. The first cessna casts a shadow on the second. In fact, because ShadowMap treats everything as shadowed, the first cessna also self-shadows.

  const int ReceivesShadowTraversalMask = 0x1;
  const int CastsShadowTraversalMask = 0x2;
  osg::ref_ptr shadowedScene = new osgShadow::ShadowedScene;

  shadowedScene->setReceivesShadowTraversalMask(ReceivesShadowTraversalMask);
  shadowedScene->setCastsShadowTraversalMask(CastsShadowTraversalMask);

  osg::ref_ptr sm = new osgShadow::ShadowMap;
  shadowedScene->setShadowTechnique(sm.get());

  int mapres = 1024;
  sm->setTextureSize(osg::Vec2s(mapres,mapres));

  osg::Group* cessna1 = (osg::Group*) osgDB::readNodeFile("cessna.osg");
  cessna1->getChild(0)->setNodeMask(CastsShadowTraversalMask);

  osg::Group* cessna2 = (osg::Group*) osgDB::readNodeFile("cessna.osg");
  cessna2->getChild(0)->setNodeMask(ReceivesShadowTraversalMask);

  osg::MatrixTransform* positioned = new osg::MatrixTransform;
  positioned->setMatrix(osg::Matrix::translate(40,0,0));
  positioned->addChild(cessna1);

  shadowedScene->addChild(positioned);
  shadowedScene->addChild(cessna2);

CastsShadow and ReceivesShadow

The two ShadowedScene masks are there to help the ShadowTechnique implementations differentiate where appropriate between different types of objects in the scene - with some techniques like ShadowTexture this is essential, with others it doesn't make so much sense and actually can be rather awkward to implement. osgShadow library itself is also still quite young with the various implementations still not fully ground out, so some areas that they don't currently implement fully will hopefully be filled out in the future.

For example, with OSG 2.4, ShadowMap respects the CastsShadow bit (only objects with that bit will casts a shadow) but ignores the ReceivesShadow bit (all objects in the shadow scene graph receive shadows - there are technical reasons for this).

Keep in mind that the default node mask in OSG has all bits set: 0xffffffff. This means that every node under a ShadowedScene is set to both case and receive shadows, by default, unless you tell it otherwise. You should be careful when inserting a ShadowedScene into an existing application's scenegraph, as you might have a lot more objects casting shadows than you need, which can spread the shadow resolution very roughly. To solve the problem, disable casting by turning off the bit:

node->setNodeMask(node->getNodeMask() & ~CastsShadowTraversalMask);

Common Questions

Example osgshadow

The OSG examples include an application called osgshadow, which gives a very simple demonstration of how to call the osgShadow nodekit.