天天看点

OGRE手册---核心对象  OGRE手册 v1.4.4('Eihort') 中文版 第二章

  OGRE手册 v1.4.4('Eihort') 中文版 第二章

#title OGRE手册 v1.4.4('Eihort') 中文版 第二章

翻译:jackie214

* 2. 核心对象

** 介绍

本教程带你快速了解将会在OGRE中用到的核心对象(core objects)以及它们的用途。

** 谈谈命名空间(namespace)

OGRE使用c++中命名空间(namespace)的特性。它可以把类(class),枚举(enum),结构(struceure)和其他东西都放在一个“命名空间”里来防止重名,也就是有两个东西同时叫做一个名字的情况。自从OGRE被设计成在其他程序内部使用,重名的问题就必须解决。有些人的解决方法是在类/类型(type)前面标上短前缀,因为一些编译器不支持命名空间。最后我还是选择了使用命名空间,因为我觉着这样比较正规。假如你有个不太标准的编译器的话那就对不住了,其实c++标准已经发布了有些年头,所以那些编译器的作者们就别找借口了。如果你的编译器还不支持命名空间的话,那只能说是实在太XXX了,换个好的吧。;) 这意味着每个类,类型等等前面都会被加上'Ogre::',例如:'Ogre::Camera', 'Ogre::Vector3'等,你在自己的程序中使用诸如Vector3这样的类型就不会重名了。为避免大量额外的打字,你可以在你的代码里加上'using namespace Ogre;'这条语句,这样就不用在前面打'Ogre::'了,除非碰到含糊定义(就是有相同命名的另外一处定义)的情况。

** 站得高,看得远

下面的图显示了一些核心对象及它们在大示意图中的“位子”。这绝对不是全部的类,只是在大量有效的类中用小小的一部分来说明它是如何联系在一起的。

OGRE手册---核心对象  OGRE手册 v1.4.4('Eihort') 中文版 第二章

图的最顶部是Root对象。这相当于OGRE系统的“入口”,也是用来建立待处理的高级对象(top-level objects)的地方,例如场景管理器(scene managers)、绘制系统(rendering systems)和绘制窗口(render windows)、插件加载系统(loading plugins)等基本元素。如果你实在摸不着门道,Root基本就是全部的东西了,虽然通常它只是给你另外一个做具体细节工作的对象,就好比Root本身是个组织者和服务商一样。OGRE剩下类的大部分属于下面的3种角色:

场景管理

这些关系到你场景中的内容如何被建立,如何通过摄像机观察等等。在这方面会提供一个自然描述接口来供你建造你的天地。也就是,你不用告诉OGRE“如此这般设置绘制状态,然后画3个三角形”,你可以这样说:“我要在这里放个东西,这里和这里,它们用这些材质,从这个视图开始画”,然后就是这样。

资源管理

所有的绘制都需要资源,不论是几何体,纹理,字体还是什么。小心仔细地载入,重用,卸载这些资源非常重要,所以这也就是这个方面的类要做的事情。

渲染

最后,这些画面被显示在屏幕上-这些都是有关于低端渲染流水线,供特定的绘制系统的API调用的对象,比如缓存,绘制状态这样的东西,然后送入流水线。场景管理子系统中的类用这来在屏幕上构造它们更高级别的场景信息。

你可能注意到四周角落散落着一些插件。OGRE的设计是可扩展的,插件是一种常用的扩展方式。OGRE中的很多类可以被继承以及扩展,比如可以更换定制的场景管理器来管理场景,添加新的绘制系统的实现(比如Direct3D或是OpenGL),或者提供一种方式从其他源上来获取资源(比如从网页或者是数据库)。这些只是插件能做的少数的这类活,但是你可以在系统的方方面面运用插件。这么说,OGRE不仅仅是个针对狭义问题的解决方案,它可以扩展到你想要做的任意事情。

** 2.1 Root对象

Root对象是OGRE系统的入口点。这个对象必须第一个创建,最后一个销毁。在示例程序中,我在程序对象中建立了一个Root的实例作为成员,保证它在程序对象建立的时候马上建立,在程序对象删除的时候马上删除。

root对象允许你配置系统,举例来说,通过showConfigDialog()方法,可以完全手动检测所有绘制系统的选项,并显示一个对话框供用户定制解析度,颜色深度,全屏选项等。它同时也会把用户的选择记录下来,这样以后就可以直接初始化系统了。

root对象也是你在系统中获取其他对象指针的好办法,如SceneManager, RenderSystem和许多其他的资源管理器。细节请阅读后面章节。

最后,如果你在连续绘制模式(continuous rendering mode)下,也就是说,你想尽可能快地刷新所有的绘制对象(游戏(game)和演示(demo)的标准行为,不包括不是窗口工具(windowed utilities))。root对象有个叫做startRendring的方法用来进入连续绘制模式,只有在所有的绘制窗口都被关闭或者任何一个帧监听器(FrameListener)对象提出需要结束的时候才会停止下来。(帧监听器对象的细节请阅读后面章节)

** 2.2 RenderSystem对象

RenderSystem对象实际上就是为后面的3D API定义的一个抽象类接口。它负责把渲染操作发送给API并设置所有的渲染选项。这个类是抽象类,因为所有的实现都由具体的绘制系统指定-每种绘制API对应一种指定的子类(例如D3DRenderSystem就是采用Dirext3D)。当系统通过Root::initialise函数初始化之后,可以通过Root::getRenderSystem()方法获取指定绘制API的RenderSystem对象。

不过,按理说一个典型的程序一般不需要直接操作RenderSystem对象,在渲染物体的时候你要做的就是指定场景管理器、材质和场景相关类的设置。只有你在创建多个绘制窗口(这种情况是指完全分离的窗口,不是多视口像分割屏幕特效那样可以通过一个RenderWindow类搞定)或者是要设置一些高级特性的时候你需要直接访问RenderSystem对象。

基于这个原因,在本教程中我不会讨论RenderSystem对象的特性。你可以假设场景管理器会在合适的时机调用RenderSystem。

** 2.3 SceneManager对象

除了Root对象,从程序的角度来看这应该是系统最主要的部分了。当然,它也是程序中被用得最多的对象。场景管理器负责管理被引擎渲染的场景内容。它用它认为最好的不论什么技术来组织这些内容,建立并管理摄像机(cameras),可移动对象(实体(entities)),灯光和材质(物体的表面性质)和通常用来表现场景中静止部分充满静态几何体的“世界几何体”。

对场景管理器来说必须通过它在场景中建立摄像机。同样在场景中打开或关闭灯光也要通过它。你的程序中没有必要保存对象的列表,场景管理器维护了一个场景中所有对象的命名集合来供你访问,你很需要它们吧。去主文档中翻翻getCamera,getLight,getEntity等方法。

场景管理器也在该渲染场景的时候把场景发送到RenderSystem对象中。你应该不要直接调用SceneManager::_renderScene方法,不论什么时候它都会在渲染对象需要更新的时候自动被调用。

所以你和场景管理器打的大部分交道是在场景设置阶段。你可能会调用非常多的方法(可能由于一些包含了场景数据的问题)来创建你的场景。你也用你自己创建的帧监听器(FrameListener)对象在渲染的时候动态改变场景(等下会说到)。

由于不同的场景类型需要不同的算法来决定哪些物体该被送到RenderSystem中去,以获得好的渲染性能,SceneManager类被设计为可以为了不同的场景类型派生子类。默认的SceneManager对象会渲染一个场景,但是它的只会做少量或者没有场景管理功能,在大场景的时候你别指望它的性能有多好。意思就是说应该建立每种场景的特殊操作,这样在幕后子类就可以靠给定的设置来优化这些场景类型,以获得最好的性能。一个例子就是BspSceneManager,它用二叉空间分割树(Binary Space Partition tree(BSP))来优化渲染大型室内关卡。

使用OGRE的程序不必知道那个子类是可用的。程序简单地调用Root::createSceneManager(..),通过传入一个参数来决定场景类型(例如ST_GENERIC, ST_INTERIOR等)。OGRE会自动该类型下有效的SceneManager子类,如果指定的那个无效就用默认的。OGRE开发者可以在以后加入新的特定场景管理器,这样可以不用修改任何代码来优化以前没有优化的场景类型。

** 2.4 ResourceGroupManager对象

ResourceGroupManager类就像一个“集线器(hub)”一样来加载可重用的资源,如纹理和模型。在这里你可以定义你的资源组,这样就可以在你需要的时候加载或卸载。属于它的是一批可以管理独立资源的ResourceManager,就像TextureManager或者是MeshManager。在这种情况下,资源就是必须从某处载入供OGRE使用的数据集合。

ResourceManager确保资源只被加载一次,并可以在OGRE引擎中通用。它们同时也管理资源需要的内存分配。它们也会通过很多位置来寻找资源,包括多个查询路径以及压缩文件(ZIP文件)。

大多数时候你不会和资源管理器直接打交道。资源管理器在需要的时候会由OGRE的其他部分调用,例如当你要求将一个纹理加到一个材质上的时候,TextureManager会被调用。如果你愿意,你可以让特定的资源管理器直接预加载(例如你在后面要防止磁盘访问),但大多数时候让OGRE自己决定就可以了。

有件事你要做的就是告诉资源管理器哪里可以找到资源。你通过Root::getSingleton().addResourceLocation来将资料传给ResourceGroupManager。

因为在引擎中每种类型的资源管理器只有一个实例,如果你要调用它的一个引用,可以用下面的语法:

<example>

TexureManager::getSingleton().someMethod();

MeshManager::getSingleton().someMethod();

</example>

** 2.5 Mesh对象

一个网格模型对象表现了一个离散模型,一套完备的几何体,在全局范围下通常比较小。网格模型对象一般是用来表现运动物体,并不是用来做一般作为建立背景幅员辽阔的形体。

网格模型对象是一种资源,由MeshManager资源管理器管理。它们一般由OGRE特定的物体格式'.mesh'格式中读取。网格模型文件一般由建模工具导出(见4.1 导出工具)并且可以由数种网格模型工具修改(见4. Mesh工具)。

你也可以调用MeshManager::createManual方法手动创建网格模型对象。这样你可以定义你自己的几何体,这部分内容已经超出了本手册的范围。

网格模型对象是游戏世界中独立运动物体的基础,它们也被叫做Entities(实体)。

网格模型对象还可以用骨骼动画(见8.1骨骼动画)来动作。

** 2.6 实体(Eneity)

Eneity(实体)是场景中的运动物体。它可以是一辆车,一个人,一条狗,一把手里剑(译注:日本忍者的一种暗器)这类随便什么东西。唯一的假设是它不需要在游戏世界中有个固定的位置。

Eneity基于离散网格模型(Mesh),就是说几何体包括本身的描述数据,且在通常在用网格模型对象表示的世界规模下都比较小。多个Eneity可以共用一个网格模型,经常你会想在场景中建立许多个同一种类型对象。

用SceneManager::createEntity方法创建实体,给它起个名字并指定它所代表的网格模型对象(例如'muscleboundhero.mesh')。场景管理器会确保用网格模型管理器载入网格模型对象。每个网格模型对象都只会被上载一次。

只有为实体指定了一个SceneNode该实体才会被当作场景的一部分。在指定SceneNode的时候,你可以为实体指定层次复杂的方位关系。然后你可以通过修改node的位置来间接改变实体的位置。

当一个网格模型被载入时,它一般自动的带有一些定义好的材质。很可能一个网格模型附带有多个材质-不同的部位可能是不同的材料做的。从该网格模型创建的实体自动使用默认的材质。但是,你可以根据需要在每个实体基础上更改,这样你可以基于相同的网格模型创建一系列不同纹理的实体。

想搞清楚这是怎么回事,你要知道所有的网格模型对象其实是由小网格模型(SubMesh)组成的。每种仅由一种材质组成该网格模型的一部分。如果一个网格模型只有一直材质,实际上它就是一个小网格模型。

当由这种网格模型构成一个实体的时候,它有尽可能多的小实体(SubEntity)对象,每种一一对应于原网格模型中的小网格模型对象。你可以通过Entity::getSubEntity方法访问小实体对象。通过setMaterialName方法还可以在你获得一个小实体引用的时候更换它的材质。这样你就能通过更改默认材质弄出个外表奇特的实体出来。

** 2.7 材质(Materials)

材质控制场景中的物体该如何被渲染。它指定物体的基本表面属性,如颜色反射率,光亮度之类,还有当前的纹理有几层,上面是什么图像,它们之间是如何混合的,有那些特效,比如环境贴图,使用那种剔除模式,纹理如何过滤等等。

材质也可以被编程,调用SceneManager::createMaterial并调整设置,或通过运行时载入'脚本'来指定。详细请参阅'3.1 材质脚本'

基本上和形状无关的物体外观都由材质类控制。

场景管理器类管理着场景中有效的材质主列表。这个列表可以在通过程序中调用SceneManager::createMaterial或是载入一个网格模型的方式增加(它会反过来加载材质属性)。不论何时把材质加入场景管理器,它们都会以一套默认属性开始,它们在OGRE中如下定义:

- 环境光反射率(ambient reflectance) = ColourValue::White (full)

- 漫反射率(diffuse reflectance) = ColourValue::White (full)

- 镜面光反射率(specular reflectance) = ColourValue::Black (none)

- 本体光(emissive) = ColourValue::Black (none)

- 发光度(shininess)= 0 (not shiny)

- 没有贴图层(这样也就没有贴图)

- 源混合因子(SourceBlendFactor) = SBF_ONE, 目标混合因子(DestBlendFactor) = SBF_ZERO (不透明)

- 深度缓冲检测 开

- 深度缓冲写 开

- 深度缓冲比较函数 = CMPF_LESS_EQUAL

- 剔除模式 = CULL_CLOCKWISE

- 场景环境光 = ColourValue(0.5, 0.5, 0.5) (中等灰)

- 动态光源 开

- Gourad阴影模式(Gourad shading mode)

- 立体多边形模式(Solid polygon mode)

- 双线性纹理过滤

你可以调用SceneManager::getDefaultMaterialSettings()并根据返回结果自己修改设置。如果使用了网格模型的话,实体就自动的拥有了附属的材质,在载入网格模型的时候就加上它要求的材质了。你也可以定制2.6节中描述的实体所使用的材质。只要创建一个新材质,按你喜欢的样子弄好,并用SubEntity::setMaterialName()来设置到子实体中去。

** 2.8 覆盖图(Overlays)

覆盖图可以在普通场景上绘制2D或3D元素,可以构成类似平视显示系统(HUD),菜单系统,状态条等等。刷新率的静态面板就是OGRE中覆盖图的一个例子。覆盖图可以包括2D和3D部件。2D部件用来构成平视显示系统,3D部件可以用来构建驾驶舱或其他任何需要在最顶层渲染的3D物体。

可以用SceneManager::createOverlay方法创建覆盖图,或者你可以在.overlay脚本中定义。实际上后一种方法更常用,因为它更好调试(不需要重新编译代码)。注意你爱定义多少覆盖图都可以:它们开始的时候都是关闭隐藏的,你可以调用'show()'方法来显示它们。你也可以一下子显示多个覆盖图,它们的Z轴顺序(译注:也就是深度)通过Overlay::setZOrder()方法设定。

*** 建立2D部件

OverlayElement类用于抽象可以被添加到覆盖图上的2D对象。所有想被添加到覆盖图上的物件都要从该类继承。OGRE用户可以(而且是推荐)自己定义OverlayElement的子类,这样他们可以自己控制。OverlayElement的常用关键特性就是像大小,位置,基本材质名等等。子类可以扩展这些方法来获得更复杂的属性和方法。

一个主要的内置OverlayElement子类是OverlayContainer。overlayContainer就是一个overlayElement,除此之外,它还可以容纳其他的overlayElement,对它们进行组合(例如可以把它们一起移动)并提供本地坐标以便于对齐。

另外一个重要的类是OverlayManager。当一个程序想要在一个覆盖图(或者是一个容器)上增加一个2D部件的时候,就必须调用OverlayManager::createOverlayElement。想要创建的部件类型是由字符串区分的,之所以这么做是因为可以用插件来增加一个新的OverlayElement类型,而又不必特殊依赖于这些库。举个例子:比如创建一个面板(一个可以包含其他部件的矩形区),你可以这么调用:OverlayManager::getSingleton().createOverlayElement("Panel", "myNewPanel");

*** 在覆盖图中增加2D部件

只有OverlayContainer可以被直接加到覆盖图上。原因是每层的容器都要确定它内部部件的z轴顺序(ZOrder),这样当你聚集了多个容器的时候,当中的容器拥有比较高的z轴顺序,而外围的容器拥有比较低的z轴顺序,这样看起来才对。要在覆盖图中增加一个容器(比如一个面板)只要简单的调用Overlay::add2D就可以了。

如果你想要在容器中增加子部件,就调用OverlayContainer::addChild。子部件本身只能是OverlayElement或OverlayContainer的实例。记住,子部件的位置是从其父对象的左上角开始算的。

*** 谈谈2D坐标

OGRE对放置和度量物品支持2种坐标系:相对坐标系和点坐标系。

点坐标系

当你想要为覆盖图中的物品指定个实际的大小时,这种模式非常有帮助,这样你就不必担心你在增加屏幕分辨率的时候这些物品会变小了(不过你可能要的就是这种效果)。这种模式下,让东西在任何分辨率的屏幕上居中,靠右或是靠底只有靠对齐选项。而相对坐标模式下只要使用右边的相对坐标就可以了。这种模式非常简单,屏幕的做上架是(0,0),而屏幕的右下角依靠分辨率而定。如上所示,你可以用对齐选项来确保水平和垂直的坐标对齐于右,下或是屏幕中心,而不必知道具体的分辨率就可以把像素级的东西放过去。

相对坐标系

如果你想要东西在不同的分辨率下保持一样的大小的话,这种模式就适合你。在这种模式下,左上坐标为(0,0),右下为(1,1)。你把一个东西放在(0.5,0.5)的位置上,它的左上角就在屏幕的中心,不论程序运行时的分辨率是多少。在大小上也是同样道理,如果你设置一个部件的宽是0.5,那么它就会覆盖屏幕宽度的一般。注意由于屏幕的宽高比一般为1.333:1(宽:高),故而一个(0.25,0.25)的东西可能不是个方的,但它确实会占掉屏幕面积的1/16。如果你需要一个看起来是方的东西,那你就要估摸着按照标准宽高比用(0.1875,0.25)了。

译注:现在的宽屏显示器宽高比和这个不一样了。

*** 覆盖图的几何变换

另一个关于覆盖图的精彩特性是它可以整体被旋转,扭曲,缩放。这可用于缩放菜单,把它们从屏幕外面丢进来这类美妙的特效。更多内容可以参考Overlay::scroll,Overlay::rotate和Overlay::scale。

*** 编写覆盖图脚本

覆盖图理所当然的可以在脚本里面体现,详情请参阅3.4 覆盖图脚本。

*** GUI系统

覆盖图只是被设计为非交互式的屏幕部件,当然,你也可以用它们做个简单的GUI(图形用户界面)。关于GUI解决方法的高深内容,我们建议采用CEGui(http://www.cegui.org.uk ),在示例Demo_Gui中有相关演示。

继续阅读