第五章 Ogre场景管理器(2)

来源:互联网 发布:android 6.0系统源码 编辑:程序博客网 时间:2024/05/18 18:53

作者:赵光强 发表于 2010-8-2 16:58:49

场景中的活动物体(Movable Scene Object)

活动物体(Movable Object)由场景管理器创建,并且绑定到场景节点上,最后再由场景管理器销毁。因为这些内容独立于场景图,所以可以根据需要随时从场景节点上分离或者挂接,也可以在一个节点上挂接多个活动物体。不过需要注意的是同一时间内一个活动物体只能被挂接到一个场景节点上


其中可以被渲染的活动物体,比如只包含几何和纹理的模型,在它们里面并不包含或处理具体的场景图信息。而另外不可渲染的活动物体(比如进入场景中的灯光或者摄影机等),虽然并不包含任何几何体或者纹理数据,但是它们仍然有能力简单的绘制自己(例如你可以在需要的时候让一个摄影机在场景中可见)。


以资源为基础的物体(Resource-Based Objects)

在Ogre中有相当多的场景内容是根据磁盘的信息来创建的,在这里把它们称为以资源为基础(也可称为磁盘基础)的活动物体,其中最常见的莫过于模型本身以及相应的骨骼数据。虽然我们可以在场景管理器中直接通过磁盘路径来创建这些模型实体,不过事实上场景管理器并没有负责实际载入工作,它只是帮你调用相应的资源管理器来读入数据,因为这些资源仍然被Ogre的相应的资源管理器(将在第7章了解更多的细节)进行管理。


在一般的时候,你可以直接通过场景管理器来构建模型实体,场景管理器会帮助你把这个过程分解成为详细的步骤,比如通过资源管理器分别载入模型、骨骼以及材质等,并把它们组合起来交给你处理。不过如果有需要,你也可以通过手动的方式来分别载入比如骨骼这种模型相关的信息。(因为骨骼模型其实可以和不同的模型数据搭配,所以有时候可能需要手工载入骨骼来处理相关问题。)


在成功创建了模型实体之后,它并没有被挂接到任何场景节点之上。为了让它进入渲染队列,你还需要进行相应的挂接工作。另外还需要注意的是,当挂接之后,模型被创建到所挂接节点的本地空间,而不是世界空间。


提示:虽然可能这不是什么至关紧要的问题,但是我还是想要在这里顺便提及一下。当你的美工(也许就是你本人),制作3D模型的时候,通常会使用3D模型工具的场景来作为管理模型的基础,而这些场景数据在导出模型时都会被原封不动的导出到Ogre的模型文件中。但是因为很多3D模型工具所使用的坐标系统是Z轴向上,这就与Ogre空间中Y轴向上的坐标系统产生一些小茅盾。为了解决这个问题,大多数Ogre的导出插件都提供了“旋转90度”的设置。


以四边形为基础的物体(Quad-Based Objects)

在活动物体中有很多的以四边形(Quad)为基础的实体,其中包括了粒子系统(Particle System)、公告栏(Billboards)、跟踪轨迹(Ribbon Trail)、表层(Overlay)和天空盒(Skybox,以及相关的天空面Skyplane和穹顶Skydemo)。它们由四边形面片构造,并且没有相关的模型资源。而他们所拥有的资源是包括定义纹理本身和生命周期(粒子系统以及跟踪轨迹会用到)的脚本,通常而言这些四边形为基础的物体都会把带有纹理的一面一直朝向着摄影机方向。在这一章节中我们着重介绍天空盒、天空面以及穹顶;而关于粒子系统和跟踪轨迹的具体细节我们将在之后的第十章进行具体的讨论;表层相关的信息要到第十二章才会有所涉及。


虽然天空面、天空盒和穹顶之间的区别是很让人感兴趣的,不过在这之前我们首先要了解它们的共同之处。其中最大的共同点是它们总是和摄像机保持着固定距离;其次,它们要么是在所有场景物体渲染之前渲染(这也是默认的情况),要么是在所有场景物体渲染之后渲染;虽然他们高高在上,但仍然和所有场景中其他物体是用相同的材质进行渲染,因此天空的纹理和其他的活动纹理并没有本质区别(下一章会着重讲解这些材质相关细节);最后它们都可以通过场景管理器开放或者关闭,也可以重新设置它和摄像机的距离


天空面(Skyplane)

简单而言,天空面就是一个用来模拟现实世界中天空的一个平面。和Ogre中所有其他的平面一样,通过法线和距离来定在场景中的放置位置。通常而言,它只是一个平面;不过如果你需要,也可以把它当成“弯曲的”平面。场景管理器可以把天空面分解成多个片断,并能控制它的弧度,甚至你也可以亲自通过控制每个顶点来改变天空面的样子。当天空面弯曲的时候,可能很类似穹顶所产生的效果,不过还是需要运用一些云雾的效果来掩饰其中的不足。


穹顶(Skydome)

和使用了一个平面的“天空面”不同,穹顶技术使用了五个平面来构造天空,它是一个拥有一个顶和四个侧面构成的的半盒。换句话说它是一个没有底的盒子,意味着你必须要用世界地图(World geometry)来挡住盒子的底部,也就是类似地平线的效果。不过半盒下部分的纹理因为曲率过大而导致失真,所以地平线的高度应该调整到足够挡住这些问题的地方。系统通过调整纹理的坐标来产生弧度效果,这种弧度是可以被手动调整的。当这个值设置的较低的时候会让天空产生“平坦”的效果(这种状态适合在室外场景中看到“宽广的天空”的效果);较高的设置会产生“陡峭”的弧度(适合在室内场景的关卡中所透过窗口所看到的天空,虽然很小但是可以在任何时候看到全貌)。


天空半盒的每一个面都和摄像机总是保持固定的距离,这个距离可以交给具体应用来配置。不过这里需要注意的是:当你把这个距离设置得太近,可能会看到因天空渲染得太近,导致把场景地图中的一些区域给覆盖了。
与天空面类似,你可以任意的给天空半盒制定所需的纹理,比如你可以通过活动纹理来产生浮云一样的效果。如果想更好的控制这些就需要了解纹理坐标变换的一些高级特性(在本书的下一章节中会详细介绍这些)。


与天空面类似,你可以任意的给天空半盒制定所需的纹理,比如你可以通过活动纹理来产生浮云一样的效果。如果想更好的控制这些就需要了解纹理坐标变换的一些高级特性(在本书的下一章节中会详细介绍这些)。


天空盒(Skybox)

首先要注意的是,天空盒并不是简单的在穹顶技术上增加了一个底部平面。而是不同前面所介绍的技术,天空盒并没有“弯曲”纹理坐标的能力,而是使用了标准的UV坐标映射的方法来处理相应纹理,进而只有天空盒才能使用立方体纹理(Cubic Texture)技术来实现天空的细节。与普通的纹理不同,立方体纹理可以“平铺”到整个空间中。


使用天空盒的目的也与穹顶和天空面不同,经常被用于展示摄像机六个方向所有能看到的天空面,而不只是头顶的一部分。另外,如果可能的话,天空盒还能潜在的利用到硬件对立方体纹理的加速功能。

渲染对象(Rendering Objects)

因为英文翻译到中文的关系,有些语义会变得模糊,所以首先要说明一下,这里阐述的渲染对象(Rendering Objects)和之前看到的可渲染物体(Renderable Object)概念不同。对象指的是用来帮助场景图进行渲染功能的对象,而不是被渲染到屏幕的物体。在场景中两个最主要的渲染对象是摄象机和灯光。其中摄像机用来帮助你“拍摄”场景中的物体,而活动的灯光使你的场景显得更立体和真实。


摄象机(Camera)

对于场景中的摄像机来说,最主要的工作就是定义产生一个视截体用来处理渲染工作。更细致一点地说,它是一个包含“眼睛”所能看到所有内容的“盒子”(我们可以简单认为眼睛存在于视截体四条边所汇聚的点上面)。请参看下图。(原文的图显示不出来了,本文的所有图都是我找的应该是类似的图


视平截头体的图形描述

在上面的图中,你可以认为那个奇怪的眼睛就是“你”在场景中所拥有的。其中的近截面定义使用了两个参数:其与眼睛的距离和所拥有的视野(也就是你眼睛所能看到的范围);远截面不仅通过两个参数来定义了它的尺寸,同时还拥有和眼镜(摄像机)距离的参数。通过这两个“截面”进而产生了六个面组成的视截体。Ogre利用这六面来剔除场景中不可见的物体,换句话说,图形硬件使用它裁减掉“盒子外”一定粒度下的几何图形(多边形的级别)。


同时摄像机也是场景中的活动物体,所以它也可以有移动、旋转以及改变位置的操作(视截体也一同被操作)。另外,作为特殊属性,摄像机可以在没有挂接到场景节点的情况下可以直接放置在场景中,也可以不依赖节点进行自身的旋转移动等操作。这其实是一种比较常用的做法,当摄像机被创建之后就已经开始正常的工作了,场景把它配置在世界空间坐标的(0,0,0)位置上,你可以把它移动到任何你希望的位置上去。


提示:这里并没有说摄像机不能挂接到场景节点上进行工作。不过需要注意的是,在把摄像机放置在节点上操作的时候,需要关闭其“锁定偏移轴(use fixed yaw axis)”设置,否则摄像机将会维持固定的“向上”状态,导致无法进行滚动(Roll)操作。当你对挂载摄像机的节点进行场景操作的时候,如果觉得有些不对劲,比如有一个或者更多的轴向被锁定,那么八九不离十就是上面所说的问题了。


灯光(Light)

很多时候,灯光都会被绑定到某些场景节点上,用来产生特殊的效果,在这里我们可考虑下面的一些情况:

汽车模型的前灯

手掌中散发出来的火焰

黑暗走廊中飘荡的火球

上面每个例子里描述的灯光都需要绑定到一个场景中的对象,换句话说,它们都需要挂接在同一个场景节点上

可以认为任何能够照亮场景的东西都是灯光。灯光既可以是活动的也可以是固定的。在场景中灯光拥有其类型、位置以及强度的属性。不过,灯光只能被用于场景中物体的局部辐射光照算法(Local Object Illumination)使用。

信息:所谓全局辐射度模型(Global Illumination),就是在计算光照的时候考虑所有光源,其中不仅包括光源本身,也包括从其他物体上反射过来的光线。使用全局辐射度模型的算法包括光线跟踪(Raytracing)和光能传递(Radiosity)。由于这种算法都有很高的计算强度,在本书写作的时候,硬件还鲜有能力进行真正的实时渲染,所以目前版本的Ogre也没有支持任何真正的全局辐射度算法。不过相对而言,还有一些准全局辐射度算法模型的存在(更精确的说,是半影模型“Soft Shadow Model”),其中包括环境光吸收算法(Ambient Occlusion,简称AO)以及计算次表面散射算法(Percomputed Radiance Transfer,简称PTR)已经被“通过”为3D硬件的解决方案。Ogre提供了对其中AO渲染算法的支持。


在局部光照的模型中,对物体的光照计算只考虑光源和物体本身。详细一点地讲,就是通过物体表面法线以及材质属性来计算光照最终在摄像机中的表现,最终计算出的颜色值提供给显示器输出。虽然理论上Ogre本身可以支持任意数量的光源,不过在下一章节中会看到,在实际执行的过程中,还是会有一些具体的限制(比如硬件)。其中有一个限制是: 3D硬件在渲染的过程中,每个渲染通路(Pass)只能使用一定数目的灯光(通常情况下是八个),所以如果你希望使用更多的灯光,那么就要使用更多的渲染通路。最终导致渲染效率的下降。另外一个限制是:真的有必要计算所有灯光么?在实际的场景中,可能有一些灯光微弱且距离很远,这时候可能只会对物体产生微弱的影响。Ogre提供了对灯光由近及远的排序,所以在大多数情况下你只要处理一个渲染通路所提供的灯光数量就足够了。


Ogre中包含了三种灯光模型(与普通3D硬件相同):点光源(Point),聚光灯光源(Spot),有向光源(Directional)。虽然它们都是可移动的,不过移动对它们的意义却各不相同;其中对有向光源的位置改变并没有实际意义,事实上有向光模型并没有位置属性,你只能通过四元数来操纵它的方向,进而改变光线的方向。点光源和聚光灯光源都对会对距离的增加进行衰减,你可以应用不同类型的衰减系数(包括常量,线性和二次)来具体影响它们。这两种光源还有其影响范围属性,超过这个范围的物体不会得到光照。所有的光源都有自身的颜色属性,被用于最终的颜色计算。


点光源(Point Lights)

点光源是在3D场景中比较常用的一种光源。它可以从空间中的一个点向周围辐射光线,点光非常适合模拟一个辐射的灯光,例如,一个壁灯或者台灯,也可以是场景中任何可以移动的需要向周围辐射灯光的对象。


聚光灯光源(Spot Lights)

聚光灯光源与点光非常相似,但是带有了方向特性。一个聚光灯光源光有一个锥形作用范围,你可以指定一个附加的衰减率作外锥所特有的叠加衰减系数,聚光灯光源就像它的名字一样,非常适合模拟聚光灯,也可以模拟出摩托车前灯的照射效果。


有向光(Directional Lights)

有向光完全不同于前面提到的两种光。通过“有向”这个词,可以想象到这种光源有方向特性。因为在该概念中,它是从场景外非常遥远的地方发射过来的光线,所以它并没有位置属性。我们可以拿真实世界中的太阳光来说明:太阳从很远的地方以一定角度照射地球上的所有物体。跟太阳一样,因为太遥远了,进而没有明显的距离衰减。因此它也不需要衰减参数,任何与衰减有关的参数都被有向光忽略。当然,有向光最适合模拟场景中的太阳光。


世界地图(World Geometry)

你可以认为世界地图中包含除了活动物体(Movable Object)之外所有的东西,比如高度场地图,网格地图,以及在地图上面的建筑和自然景观(树木以及植被),室内的门和窗户。而比如怪物和场景中活动的门不属于地图本身,它们是活动物体。可以这么理解,世界地图是角色们表演的“舞台”,是演员们存在和表演的场所

通常来说,可以通过离线的制作工具来生成世界地图,比如一些3D建模工具Softimage|XSI、3D Studio Max、Maya以及Blender,或者高度场地图制作工具Terragen(http://www.planetside.co.uk/terragen/)。最终产生的地图通常是由网格模型为基础构成的,其中包括诸如一些简单的室内场景、建筑结构(比如桥梁)、植物(比如树木或者灌木丛)等等。虽然地形和整个室外场景也是可以用网格模型来构成,不过为了提高性能,它们被“分割”成一些较小的部分,当它们被系统载入的时候才会“缝合”在一起以供显示。


注意:当创建以网格模型为基础的场景或者关卡的时候,你有必要把它们拆分成许多小的部分。否则的话,就算整个网格模型只有一小部分需要显示,Ogre也会渲染整个模型的全部,这样对整个渲染效率的影响就不言自明了。一般来说,大部分的场景管理器(比如分页场景管理器“Paging Scene Manager”)都会提供自己的场景地图分割工具,虽然高度场地图本身并不支持在构建的时候进行分块,但是这些工作会交给系统在载入的时候自动进行。

Ogre的场景管理器提供了两种不同(却类似)的场景读取方式:直接从磁盘文件载入数据以及从任意的数据流载入数据。其中使用数据流的方式允许你在程序中把网格模型包装构造成通用或者私有的地图格式,并直接发送到场景管理器中。但是大多数情况还是使用从磁盘中读取地图的方法,在这种方法里世界地图文件通常是能直接打开和载入的磁盘上的独立文件。


通用的空间分割策略

场景管理器会根据设计和优化的需要,把场景中的几何体分解成一些集合来进行操纵和管理。这些几何体集合的划分方式被称空间分割(Spatial Partitioning),其分割的依据包括每个自空间有多少个几何体才能达到稳定、内含多少个可活动的物体、期待的密度和如何布置它们。对于Ogre来说,对场景地图的分割会建立在物体粒度上而不是多边形本身。这是因为Ogre 3D引擎是一个依靠硬件加速的工具,而当前的3D图形硬件中能更好的处理几何体批次,所以针对物体的分割方式可以得到比针对多边形的分割方式更好的效率。(分割多变形的技术更适合那些软件3D光栅化的工作,不过那是在没有图形硬件对3D变换和光照提供加速的时候)。


四叉树(Quadtree)和八叉树(Octree)的空间分割策略

四叉树是一种历史悠久的2D分割策略,不过在某些3D场景中也可以很好的使用,比如那些垂直高度变化很小的室外场景或者地形中。而八叉树是一种比较适合小型室外场景等分割方式,并且在允许摄像机随意位置的室内场景中工作的一样良好(比如摄像机可以在天棚上面或者地板之下)。


图 5-10 :八叉树(a)和四叉树(b)空间分割方试图


图 5-10(a)中你能够看到把空间逐层分割到八个(所以叫做八叉树)子空间的每一步。同样的,你也可以在图5-10(b)中看到四叉树算法把空间逐层分割成四个相同子空间的过程。这两种方式都是递归的分割独立的空间。

八叉树和四叉树更擅长处理在把多变形打包成几何体进行分割的工作,而这并不是二叉空间分割树(BSP)所擅长的。如果希望更多的了解四叉树和八叉树的信息,可以去网上找一些相关的资料来参阅。本书就不在这里多费口舌了。

二叉空间分割树(Binary-Space Partitioning)

BSP(binary-space partitioning)是一个非常有效的场景组织方式,但是对于现在的应用程序而言,更倾向于使用BSP做空间快速碰撞检测而不是几何体分割。虽然Ogre提供了一个简单得BSP支持(事实上也就是《雷神之锤III竞技场》所使用的地图格式),不过因为《雷神之锤III》因为历史的原因,更倾向于把几何数据划分成很多小的数据块而不是打包成大的数据批次,这使得现代的图形硬件并不能很好的进行处理。也便因此,已经没有开发人员继续维护这种BSP格式的简单实现。虽然现在仍然提供这种BSP地图场景管理器插件,但是它的作用也仅限于载入那些使用《雷神之锤III》关卡编辑器制作的地图所用。


静态几何体(Static Geometry)

看起来似乎静态几何体是活动物体(Moveable Object)的反义词,但事实上也不全是:通常来说静态几何体会由很多不再活动的活动物体来构成


在这里需要要再次提到这个问题,现代的GPU更适合渲染少量巨大物体,而不是很多小几何片断。


注意:因为这个原因,当在现在图形硬件上来测试3D应用程序性能时,光衡量渲染三角面数量的能力通常是没有意义的事情:比如当一百万个三角面捆绑成一个簇里能达到每秒300帧的渲染速度,在结构组织改变的时候,把这些三角形分布在一千个簇中(平均每簇一千个三角面),就可能会降低到30帧每秒这样的结果。所以在现代3D应用程序中可渲染三角面的数量已经不是衡量应用程序的唯一标准


所以通常而言,越多的三角面集中在一个簇中渲染,对提升你的应用程序效率越有利(当然,也不能盲目,很多时候还要考虑诸如图形硬件带宽吞吐量等诸多因素的影响。)


可能在这里你会觉得把复杂的场景作为静态物体来处理可能是一个不错的注意,但事实也并非如此,在下面列出静态物体的几个缺点:


·一般而言,巨大的静态物体需要在使用前被构建,通常这是一个缓慢的过程,所以不能在每一帧都执行。

· 静态几何体将按着放入物体的材质来进行分类,并且把材质相同的集合体放置到同一个渲染设置当中(换句话说,也就是放到同一个簇中)。但这并不表示只要把一组几何体打包成一个静态物体就能神奇的把它们捆绑到一个渲染设置中:不同材质的物体仍然会被拆分成不同的簇中被渲染。所以这时候你需要在两种需求中进行折衷:是在一次调用中渲染最多的三角形还是最少的调用的次数(也就意味着决定在静态物体中放置多少不同的材质)。

·在静态几何体中“静态”的含义是:一旦物体被放入静态几何体中,你就不能在单独移动它了。任何对静态几何体的世界变换都会应用到所有里面包含的物体上。这就好像放入咖啡中的方糖,你可以扔进去,但是再也捞不到它了。

·通常来说静态几何体会比同样大小的活动物体占用更多的内存;这是因为对于活动物体来说,可以多个实体共享一个网格模型。而静态几何体会为每一个实体创建一个网格模型数据的拷贝。不过在这本书写作的时候,GPU的几何处理也正在发生变革,可能在这本书出版之后,硬件会支持更加优化静态几何体的处理方式。

·就算在你的视野里(视截体)中看到了整个静态几何体的一小部分,甚至包括在你身后的整个数据都会传到图形硬件中渲染。假如你把场景中所有的植被都放到一个静态物体中,即使你只看到一颗小草,那么整个森林都会被渲染。

此外,在场景对静态几何体材质和模型的LoD(细节等级)处理中,会遵照整个静态场景最远的物体距离来设定整组物体的等级。这导致当距离改变的时候,整个静态物体的LoD都会有相同的改变。不过除了上面说的问题之外也有好的一面,场景管理器会通过空间分割算法把静态几何体分组,进而把那些没有在显示空间的静态几何体直接屏蔽到渲染队列之外。对于静态几何体的处理就和所有软件工程中所出现的问题一样,你必须为提高程序的性能而进行一系列设计上的折衷。


0 0