纹理阴影是通过以光源作为招眼点把投影体渲染到纹理上来得到,然后再把这个纹理投射到接受阴影的表面上来得到最终的结果。相对于7.1 模版阴影而言,纹理阴影的优势在于其极少的增加了集合体的细节,因此不用执行对每个三角形的计算。对渲染纹理阴影的大部分工作都放在图形硬件中执行,这就意味着在新的现实硬件上可以得到更好的性能,换句话说纹理阴影运算速度的进化超过了CPU的发展。另外的,纹理阴影的应用范围更加广泛——如果你喜欢可以把他们放入着色器中(你可以通过着色器的操作对整个阴影进行过滤而产生软阴影或者其他的效果)。基本上,绝大多数现代的图形引擎都会使用纹理阴影作为其首选阴影技术,这是因为其不仅非常强大,并且它的GPU执行代价也被分摊到不同的纹理通路中从而带来更高的效率。
不过纹理阴影也有其主要的不利因素,因为事实上所谓阴影的基础其事实就是一张纹理图,它拥有一个固定的像素数量,当纹理被拉长之后这些像素就变得很明显和突兀,这里有一些方法来改善这个缺点:
选择一个“投影基础” 最简单的投射方法就是简单的从光源点通过标准的摄像机设置来渲染阴影投射体。但是这种方法的结果却不尽人意,因此有很多种其他种类的投射方法用以改善主摄像机投射法的质量。Ogre支持通过ShadowCameraSetup类型来插入“投影基础”,并提供了一些已经存在的操作——Uniform(最简单的投射方法),Uniform Focussed(仍然采用了标准的摄像机投影,只不过是摄像机聚焦到主观察摄像机所观察的区域),LiSPSM (Light Space Perspective Shadow Mapping - 同时基于主观察摄像机聚焦并扭曲阴影的视截体)以及Plan Optimal (对一个独立的接收平面寻找最优的投影保真度)。
过滤 相对于一次采样的简单方法,我们也可以通过多次采样的方法来改善阴影的糅合度以及平滑边缘。尽管对于采样的策略有很多种不同的模式,不过基于百分比渐近滤波(PCF)是一种比较常用的过滤方法。在
我们提供的阴影演示程序中,使用了一种基于5-tap PCF混合的深度阴影贴图。
使用一个更大一点的纹理 随着GPU速度和显存容量的提高,你可以采用更加巨大的纹理来进行阴影处理。
如果你可以很好的混合运用上面三种处理技巧,你可以得到一个非常高质量的阴影解决方案。另外一个问题是关于点光源的,这是因为对于阴影纹理来说,需要从光源的方向对纹理进行渲染,而对于全方向的光(换句话说就是点光源)需要对六个不同的方向进行渲染来处理全方位的阴影投射。基于这个原因,Ogre内部支持对于有向光源和聚光灯光源的纹理阴影;点光源只有在距离摄像机很远的位置上才能正确的工作,这是因为这时候点光源对于你的摄像机视截体来产生纹理阴影已经蜕化成了普通的聚光灯光源。
有向光光源
从原理上来讲,有向光光源作为一个无穷远的光源,会把阴影投射满整个场景。不顾对于我们现在来说,只拥有一个有限大小的纹理,如果把平铺到整个无限大的空间中来,会让我们阴影的质量变得及其低下。Ogre使用了一种技巧来处理这种问题,它直接把有限的阴影纹理放置在摄像机正前方,并把阴影纹理随着摄像机的移动而移动(尽管会在小距离移动纹理的时候,会因为对于纹理的倍数缩放的近似值四舍五入产生类似“阴影抖动”的影响)。阴影具体的扩展范围,以及相对于摄像机的偏移量都是可以手动配置的(参照配置纹理阴影)。在阴影远处的边缘上,Ogre根据另外一个可以配置的参数把阴影渐渐淡化,因此远处的阴影看上去是柔和的样子。
聚光灯光源
显然聚光灯光源相对于有向光源更加容易被表现出来,这是因为一个有向光源本身就是一个视截体形状。Ogre对从聚光灯光源的位置朝向聚光灯方向渲染整个阴影作为聚光产生的阴影纹理;纹理摄像机的“可视范围”是根据聚光灯光源的衰减角度(falloff angles)来得到。另外,为了隐藏阴影纹理是一个矩形的样子,即让聚光灯产生阴影的边缘不至于太过明显,Ogre使用了另外一个纹理单元来表现阴影到外侧的过渡淡出,最终的结果是我们可以看到一个圆形的阴影范围被投射在聚光灯光源前面。
点光源
我们在上面谈及过,为了完全支持点光源的投影特性,我们需要提供多次渲染(比如6次序渲染的立方体渲染或者精度稍微低一些的两次渲染贴图)来进行相应的处理。不过我们采用了把点光源近似的作为聚光灯光源处理的策略,把点光源作为聚光灯从所在位置照亮整个视截体。不过这样做只有在点光源在视截体外面的时候才能正确的工作,所以并不是一个真正完美的解决方案,并且而外的这样做还会带来一些纹理的“抖动”。基于这些原因,我们建议尽量不要用点光源来投射纹理阴影。
阴影投射物体和阴影接收物体
为了打开纹理阴影,需要使用阴影技术包括SHADOWTYPE_TEXTURE_MODULATIVE或者SHADOWTYPE_TEXTURE_ADDITIVE;你可以在章节7.3 调制阴影与7.4 叠加光照掩码中找到相应的详细介绍。对于最廉价和简单的纹理阴影技术来说是没有使用深度信息的,而是仅仅渲染投射阴影的物体到一个纹理上作为普通的颜色数据应用到阴影接收物体上——这就意味着这种方法无法实现自投影的效果。如果你使用了自动的阴影效果,这就是纹理阴影默认的行为,一种和固定功能(没有使用着色语言的)相兼容(进而可以使用到较低等级的显示卡上面)的纹理阴影技术。你也可以使用着色语言作为技术的实现基础来处理阴影投射体和接收体的材质来实现更复杂的阴影算法,比如实现支持自投影的深度阴影贴图。OGRE在演示程序中给我们带来一个这样的例子,虽然这个实现只能保证在Shader Model 2图形硬件(或者更高)上面很好的运行。虽然对于使用了OpenGL接口的固定功能管线而言深度阴影贴图是可以直接使用的,不过对于Direct3D标准而言却不市内建的支持,所以使用着色与语言来实现的投射/接收材质更加通用一些。如果你决定使用了这个途径,调用SceneManager::setShadowTextureSelfShadow并把传入参数设置为“true”,来允许对自投影纹理阴影的支持。
如果你没有使用深度纹理阴影贴图,Ogre会把阴影投射体和接收体分成两个不同的组。通过简单的关掉物体的投射开关,你就能自动地把物体设置为阴影接收物体(你也可以通过在材质脚本中把receive_shadows设置为“false”来实现)。同样的,如果一个物体被设置成阴影投射体,那就意味着它无法接收其他物体投射的阴影。
配置纹理阴影 这里有一些对纹理阴影的设置,可以帮助你实现你需要的效果。
阴影纹理的最大数量
阴影纹理占用了纹理显存。为了避免阻塞渲染管线的效率,Ogre并没有在同一帧中对多个光源重复使用阴影纹理进行渲染。这就意味着每个光源都必须拥有自己的独立的阴影纹理。在具体的使用中,如果你使用了过多的光源,你应该需要避免带来过多的纹理消耗。
你可以通过手动关闭灯光的投影来限制产生纹理阴影的光源的数量。另外你也通过调用SceneManager::setShadowTextureCount方法来设置最大阴影纹理数量来限制。Ogre会在每一帧测定那些光源会影响视截体,并分配向应限制的纹理数量。对于光源采用先到先投影的策略,任何超过限定数量的光源会在当前Frame中被忽略。
注意,你可以通过调用SceneManager::setShadowTextureSettings方法来同时设定阴影纹理的尺寸和数量;这是一个很重要的特性,因为每次这类调用潜在的会销毁然后创造所有的纹理资源。
阴影纹理的尺寸
用来放置投射阴影的纹理的尺寸是可以改变的;使用巨大的纹理可以明显的提高你所使用的纹理阴影的质量,但是同时也带来巨大的耗费。我们可以通过调用SceneManager::setShadowTextureSize方法来改变纹理的尺寸——不过这里的限制是必须设置成为边长为2的n次方的正方形。需要知道每个阴影纹理都会消耗边长*边长*3个bytes的尺寸。
重要:如果你使用了GL渲染系统的话,只能在硬件支持了Frame Buffer Object (FBO)或者Pixel Buffer Object (PBO)扩展特性的情况下才能使用大于主窗口尺寸的阴影纹理(在任何维度上)。虽然大部分现代的硬件都已经能支持这些属性,不过在使用的时候仍然要注意有些比较老的硬件环境仍可能有这些问题——这时候你可以通过ogreRoot->getRenderSystem()->getCapabilities()->hasCapability(RSC_HWRENDER_TO_TEXTURE)方法来检查硬件环境是否有这个能力。如果硬件返回了false结果并使用了超过了主平面大小的阴影纹理尺寸,会导致产生一个空白的纹理阴影结果。
阴影的最大距离
决定阴影结束的距离;同时也是决定有向光投射纹理阴影距离的延展——你可以通过减少这个值或者增加纹理的尺寸来增加有向光产生纹理阴影的质量,所付出的代价分别是减少阴影终止的距离与耗费更多的显存。
阴影纹理的偏移量(针对有向光光源)
在有向光章节中我们提及过,对于有向光光源阴影的渲染允许我们使用一个单一的渲染来覆盖一大片阴影区域。这个偏移参数影响从摄像机位置到阴影中心的距离。增加这个值,更多的阴影就会被放置到摄像机前面,进而对你会变得“可用”,但是如果这个偏移量过大,也带来更大的机会让你在某些角度看到阴影纹理的边缘。你可以通过调用SceneManager::setShadowDirLightTextureOffset方法来设置这个参数,在默认的情况下这个值被设置成0.6。
阴影淡出设置
阴影在远距离进行了淡出的处理来保证不会让阴影边缘过于生硬。你可以通过调用SceneManager::setShadowTextureFadeStart与SceneManager::setShadowTextureFadeEnd方法来配置开始和结束的点来修正淡出的效果,作为距离的参数都是一个相对的比例值。因为我们使用了正方形纹理,但是淡出的边缘是一个圆形,这就导致我们尽量不要用1.0作为阴影的结束位置,否则会带来一个非常难看的生硬边缘。在默认的情况下这两个值分别被设置成为0.7和0.9,which serve most purposes but you can change them if you like.
纹理阴影与顶点/片断编程 当把一个阴影投射物体渲染到一个调制阴影纹理上的时候,Ogre会关闭所有的纹理,除了环境光之外所有的光源都会参照阴影颜色(阴影颜色)
对最终的结果产生影响。而对于加成阴影,改为把投射物体渲染到白色和黑色纹理代替。这些对于固定渲染流程的材质技术的投射阴影物体来说已经足够用了,然而对于一个顶点程序来说Ogre没有了足够的控制。如果你在第一个渲染通路中使用了顶点着色技术,你就必须告知Ogre在渲染阴影投射体的时候具体使用那个顶点程序;参照阴影和顶点程序。
定制摄像机
在我们之前提到过,对阴影纹理有很大限制的就是纹理本身的解析度是有限的,并且当阴影纹理像素大于屏幕的像素的时候,纹理的映射可能会导致产生混淆现象。为了解决这个问题,你可以通过制定另外一个继承于ShadowCameraSetup的类型作为新的映射基础。在默认的版本下我们使用DefaultShadowCameraSetup作为一个简单的视截体映射来处理点光源和聚光灯光源,对于有向光源采用正交式截锥体来处理。这里同时也提供了名为PlaneOptimalShadowCameraSetup的类型来专门映射到一个平面上,当你的阴影主要的投射到一个平面上的时候会给你带来更佳的效果。其他的设置类型(例如你可以创建一个透视图或者不等边四边形的阴影贴图版本)可以在运行的时候插入到系统中来,可以针对某一个特别的光源也可以对整个场景管理器。
集成纹理阴影(Integrated Texture Shadows)
相对于模板阴影来说,纹理阴影有一个主要的优势——这些数据可以被标准的着色器引用并处理。同时默认的阴影模型(SHADOWTYPE_TEXTURE_MODULATIVE以及SHADOWTYPE_TEXTURE_ADDITIVE)会自动的为你把阴影渲染出来,他的不利因素是会带来很多附加在我们材质上面的额外负担,更多的场景中渲染通路的使用。而外的,你不能对阴影的成分有很多的控制。
这里存在“集成”的纹理阴影设置。对于两种纹理阴影类型都存在另外一种被称为SHADOWTYPE_TEXTURE_MODULATIVE_INTEGRATED或者SHADOWTYPE_TEXTURE_ADDITIVE_INTEGRATED的版本,当采用了这两种类型之后,系统将不会直接为你渲染阴影,取而代之的是在
渲染场景阴影接收物体的时候建立纹理阴影并期待你按照自己的需求去使用。如果你使用了这种类型设置,你需要在每一个材质上计算阴影接收的过程,而相应带来的好处是你拥有对对阴影纹理使用的全部控制。更深层的优点是你可以在这种环境下执行更复杂的着色过程,得到阴影计算,这时就有可能使用一些更深入的方法;并且因为你了解你具体需要多少数据量而不用全部得到,进而你能使用更少的渲染通路去处理这些信息。当你使用了这些阴影处理手段的一种,对于加成阴影和调制阴影的唯一却别就是阴影纹理投射的颜色(对于调制阴影是阴影颜色值,而对于加成阴影是简单的黑色)—— 在实际的计算过程中,所有处理接收阴影的问题都交给了你自己。没有调制渲染通路的分离过程,也没有把你的材质分成环境光/每个光源/贴花等部分——所有需要处理的事情都需要在你最初提供的原始材质中处理(如果你喜欢可以把调制分成每个通路或者每个光源进行迭代,但这并不是必需的)。
如果你希望在材质脚本中处理这些阴影纹理相关的事情,可以在{texture_unit}标记范围中通过'content_type shadow'标签来驱动。通过这项标签暗示这是一个阴影纹理基础,会在相同的通路中被使用多次,从light_start设置或者light-based通路进行迭代,灯光的索引值需要大于0。