Version 16 (modified by osg, 6 years ago)

--

osgShadow

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:

http://www.openscenegraph.org/projects/osg/attachment/wiki/Support/ProgrammingGuide/osgShadow/shadows1.png?format=raw

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.

  • ShadowMap?.
    • This is the most frequently used option and most mature and stable implementation.
    • It uses a fragment shader (osg::Shader).
    • It basically does a render to texture from the light source's point of view (which is the so-called shadow map) and then, when rendering the final scene, checks the fragment's depth against the shadow map to decide whether it is in shadow or not. It works whether you have shader support or not, but the shadows will be opaque black when using the fixed pipeline (ATI cards have an extension to control this, but it is not standard). Also, some older cards have very poor render-to-texture performance (whether FBO or pbuffer) so you should turn off shadows completely in that case.
    • Option AmbientBias, see below.
  • ShadowVolume?.
    • As of OSG 2.4, this is still experimental and may not produce shadows on some graphics cards. (JSG - in fact, I have not seen it work correctly at all, there are always inverted shadows where two shadows overlap)
    • This technique does not use either texturing or shaders. It does make heavy use of stencils.
  • ShadowTexture?.
    • This is the simplest technique. It does not use a shader.
    • It's fixed-function only, so it should run on anything that supports RTT through either FBOs or pbuffers (so almost anything). One users reports: "On my hardware (ATI mobility X300), only ShadowTexture works".
  • SoftShadowMap?.
    • As of OSG 2.4, this is still experimental and may not produce shadows on some graphics cards.
    • It has quite complex shaders.
    • The original idea for this technique was published in GPU Gems 2 presumably the article "Efficient Soft-Edged Shadows Using Pixel Shader Branching".
    • Option AmbientBias, see below.
    • Options Bias, SoftnessWidth, and JitteringScale are documented in the API docs.
  • ParallelSplitShadowMap?.
    • The idea for this technique is based on Parallel-split shadow maps for large-scale virtual environments or GPU Gems 3 / Chapter 10 preview .
    • Instead of one single texture and it's discretisation bugs, this technique tries to reduce small shadow artifacts with upto 7 textures. Each shadow texture (depth map) has a camera distance range, in with the depth map will be active. In the active range, the shadow will be calculated based on the correct depth map. By default we assume that close points (near to the camera) should be more robust , less artefacts compared to far points. So we will split not linearly the viewing frustum. This technique should be quite similar to the Lispsm or view shadow map, if we use only one texture.
    • This is the most complex and potentially powerful algorithm, with many more options that can be set:
    • PolygonOffset
    • MaxFarDistance : to reduce the shadowing artefacts and increase the frame rate you could define the range from camera to the distance where the shadow should disappear
    • MoveVCamBehindRCamFactor : if you have need objects casting shadows behind the camera, may this option will solve some problems: it moves the camera virtually behind the real camera
    • MinNearDistanceForSplits : put the light camera closer to scene (frustum split)
    • forceFrontCullFace : activate front culling
    • useLinearSplit : by default the splits are located non-linear, if you set the flag you will get linear distances between the textures, starting at the camera position to the farest frustum point
    • It still has some problems and will need some work before being production-ready.
  • Light space perspective shadow maps (Lispsm).

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

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<osgShadow::ShadowedScene> shadowedScene = new osgShadow::ShadowedScene;

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

        osg::ref_ptr<osgShadow::ShadowMap> 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

  • Where in the scene graph does ShadowedScene go? Does it have to be at the root?
    • You can put your ShadowedScene node anywhere in the scene graph.
  • What if the shadower and shadowed nodes are far apart in the scene graph, should the ShadowedScene be inserted above their mutual parent?
    • Yes. The root node of the subgraph which you want to have shadows should be an osgShadow::ShadowedScene
  • Do the ReceivesShadow and CastsShadow mask bits need to be set to 0 for all the other nodes in the tree under ShadowedScene, to omit them from the shadow computation?
    • Yes. You will have to iterate through the entire scene graph to change the node mask for every node from its default (0xffffffff) to turn those bits off (use ~shadowedScene->getCastsShadowTraversalMask() and ~getReceivesShadowTraversalMask() to toggle the right bits off - the tilde means logical NOT). However, remember that some shadow techniques will ignore some bits in some cases, so you may not be able to omit nodes in all cases.
  • Does the Light need to be present in the graph under the ShadowedScene?
    • No, you don't need to add light to shadowed scene nor it does not need to be present in fixed location in viewer scene hierarchy. In case of many lights, it would be helpful if you point out the Light source that must be used to generate shadows. See !ShadowMap::setLight function.
  • Does ShadowedScene always rerender the shadow every frame?
    • Yes.
  • If I know that the light and geometry are not moving, can I avoid the shadows being constantly re-rendered?
  • What if my objects already have a shader applied to them?
    • That shader also needs to implement shadow mapping. See the top of src/osgShadow/ShadowMap.cpp for the basic shader, and use that in your shader (keep the same names for the variables too).
  • Can osgShadow be combined with osg::Fog?
    • Most of the ShadowTechniques use shaders. osg::Fog wraps up glFog, which is part of OpenGL's fixed pipeline. As soon as you enable shaders you lose the fixed pipeline functionality for that stage (vertex and/or fragment) and must implement the features you want yourself in the shader.

Example osgshadow

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

Attachments