Shadows

作者: osgChina 发布于2018-10-22 09:29:00 分类 : 编程指南

osgShadow是一个非常强大的阴影工具箱,也正因为有了它,使得用户不用关心复杂的阴影算法、多重纹理单元以及可编程管线等,只要把这些结点加到场景中,阴影就有了。

本文会简单介绍其用法。

一个示例

构建一个单一光源的场景,需要一个ShadowedScene结点(它是从Group)派生而来,它下面需要加孩子,同样需要选择一个阴影技术(ShadowTechnique)不同的阴影实现技法特点不同。加的两个孩子分别是CastsShadow(阴影投射者)以及ReceivesShadow(阴影被投射者),通过nodeMask来识别哪些是投射者,哪些是被投者,如下图。当然有时候投射与被投也可以是同一个物体。比如一栋楼的阴影投给了自身。然后ShadowedScene会调用相关的方法,使用设定的阴影技术计算阴影。

8b6a18a5a6246ce475aaf2260e019528_shadows1.png

 

阴影渲染技术techniques

OSG2.4版本开始支持五种不同的渲染技术,它们都对显卡的配置有一定的需求,如果你的显卡不支持相关的扩展,则有些技术可能无法使用。

  • ShadowMap

    • 它属于经典算法了,非常稳定,实现纯熟。

    • 使用了片元Shader。

    • 基于光源点RTT生成一张贴图渲染到最终场景,通过片元的深度检测来判断这个贴图是否应该被贴在物体上。按理来说它的实现与是否支持shader无关,但是如果不使用shader,阴影的颜色会是不透明的黑色,非常难看(ATI显卡对此有个扩展可解决,但是是不标准的)。同样一些其它的旧卡对于RTT的支持(FBO或pbuffer)的支持相当不到位,以至于无法使用ShadowMap阴影。

    • Option AmbientBias, see below.

  • ShadowVolume

    • 在OSG2.4版本,这是个实验性质的特性,因为很多显卡无法支持。

    • 它即没有使用textureing也没有使用shader,它用了stencils。

  • ShadowTexture.

    • 这是个简单的实现方法,没有使用shader。

    • 它是完全的固定管线的技术,只需要对RTT支持,通过FBO或pBuffer或其它任何都可以,有一个测试者称这是他的显卡上唯一支持的阴影算法。(ATI X300)。

  • SoftShadowMap.

    • 同样在OSG2.4它是个实验性质的特性,在有些显卡无法工作。

    • 它使用了非常复杂的shader。

    • 最初这个技术被发表在 GPU Gems 2 在 "Efficient Soft-Edged Shadows Using Pixel Shader Branching"一节。

    • Option AmbientBias, see below.

    • Options Bias, SoftnessWidth, and JitteringScale are documented in the API docs.

  • ParallelSplitShadowMap(PSSM).

    • 这个技术的思想来源于(Parallel-split shadow maps for large-scale virtual environments or GPU Gems 3 / Chapter 10 preview )。

    • 较之以前的单纹理阴影,这种技术使用了七个纹理(按depth map来判断使用哪个)。在激活的范围后,shadow会跟据视点来计算应该使用哪层纹理,比如视点离的较近时,会让阴影粗壮精细,远一点时则粗糙淡一些。这个变化是线性的。这个技术感觉将来要成为主流。

    • 这是个相当复杂的阴影技术,因此有很多选项可以配置:

    • PolygonOffset

    • MaxFarDistance : 当视点超过这个距离时,阴影不显示。

    • MoveVCamBehindRCamFactor : 如果需要相机背后的物体也投射阴影过来,那么该选项有用,它会将相机虚拟的向后移动以来解决这具问题。

    • 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

    • If filtering is enabled (by default set) and GLSL is supported then the PSSM will filter the shadow with a 3x3 filter(Fliter是用来实现图像变换的效果时,不同的filter会有不同的效果,比如浮雕,虚化,锐化等,此处是指PSSM可以使用过滤器)

    • There is a method to activate a debug mode: It will display the active range of each texture (r=1,g=2,b=3,...)

    • It still has some problems and will need some work before being production-ready.

  • Light space perspective shadow maps (Lispsm).

总结:

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

使用上述Filter之后的过滤效果,关于过滤器可以参考橙宝书(OpenGL 着色语言)的与图像处理有关的相关章节。--FreeSouth注。

d0a40b18f211e3a3349823ffa57aab4d_test.png7f0b50946b5df7e7cb1dacc91da9bae2_test_3x3.png

Ambient Bias

这个选项被用来设置阴影的拉伸扁平和阴影的明暗程度。经常的阴影被投到地面后会是全黑色,往往我们只需要它相对物体有一点暗即可,此时可以通过设置AmbientBias.x的值来尝试。AmbientBias.y经常被设置为1-AmbientBias.x,它会影响阴影的形状。Ambient Bias.xy的范围为[0……1]。

Texture Resolution

总的来说,阴影技术大多是生成阴影纹理,然后贴到被投射的物体上。因此该阴影纹理就会有个分辩率,分辩率一来会影阴响效率,二来会影响效果,如果设置的太小,则无法表达投射体的轮廓,如果太大又效率太低。默认吧。

示例代码

下面的代码创建了两个飞机一个飞机是投射体,它自身和另一个飞机是被投射体。

  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(被投体)

通过指定nodemask来定义阴影的投与被投体有点土鳖,但是osgShadw本身是个很年轻的库,因此还在向前发展,当前的实现也并不是完全的像想像的那样完善,还在往前走。比如在OSG2.4中,只需要设置投射体,那么默认情况下所有的物体都是被投物体。需要明确的是,OSG场景结点的nodemask默认情况下是0xFFFFFFFF这样的话所有的物体即是投射体又是被投物体,如果场景非常复杂,那么这会给渲染带来很大的困难和问题。因此可以手动的指定无关紧要的物体为非投射体,通过如下的方式:

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

Common Questions

ShadowedScene应该挂在哪里呢,挂在root下合适吗?

  • 想挂哪挂哪.

如果说投射体与被投体在场景图中相距较远(是使逻辑结构,不是场景中的位置),是应该找他们共同的父或父父父来创建ShadowScene吗?

  • 是的,显然的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?

  • Possibly. See the mailing list thread osgShadow one shot shadow map

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

OSG中包含了一个osgShadow的例子,大家可以看一下。