OGRE 浅析 耿卫东 陈为
网上资源 http://sourceforge.net/projects/ogre/ http://www.ogre3d.org/(官方网站)
教学中文资料 OGRE_01a.doc: 从使用者的角度将 OGRE引擎最基本的概念和使用方法做 一个较全面的介绍。隐藏了OGRE引擎 内部的底层内容,力求做到简单、易懂, 是OGRE引擎的入门教程。By Marg小组 Loneststarreading.doc OGRE浅析:by 邹林灿 OGRE中文系列分析:by 盛崇山
OGRE LOGO
本次课程的主要内容 OGRE简介 OGRE的安装 如何用OGRE来进行应用程序的开发
OGRE简介 OGRE: Object-Oriented Graphics Rendering Engine (面向对象的图形渲染引擎) C++开发面向对象且使用灵活的3D引擎。 目的:让开发者方便和直接开发基于3D硬件 设备的应用程序或游戏。 引擎中的类库对更底层的系统库(如: Direct3D和OpenGL)的细节进行了抽象,并 提供了基于现实世界对象的接口和其它类。
OGRE简介 这部分主要介绍: OGRE特点 OGRE中的模块
OGRE特点 效率特性 简单、易用的面向对象接口设计能更容易 渲染3D场景,并使实现产品独立于渲染API (如Direct3D/OpenGL/Glide等)。 可扩展的程序框架(framework) 自动处理常见的需求,如渲染状态管理, hierarchical culling,半透物体排序等。 清晰、整洁的设计加上全面的文档支持。
OGRE特点 平台和3D API支持 支持Direct3D和OpenGL 支持Windows平台,用Visual C++ 6(或Visual C++.Net)和STLport来 编译。 支持Linux平台,用gcc 3+(或gcc 2.9x)和STLport来编译。 材质/Shader支持 支持从PNG、JPEG或TGA这几种文件中加载纹理;自动产生MipMap; 自动调整纹理大小以满足硬件需求。 支持可程序控制的纹理坐标生成(如环境帖图)和转换(平移、扭曲、 旋转)。 材质可以拥有足够多的纹理层,每层纹理支持各种渲染特效,支持动 画纹理。 自动应用多通道渲染和多纹理,从而大幅度提高渲染质量。 支持透明物体和其它场景级别的渲染特效。 通过脚本语言可以不用重新编译就设置和更改高级的材质属性。
OGRE特点 网格Meshes 高效的网格数据格式 提供插件支持从Milkshape3D导出OGRE本 身的.mesh和.skeleton文件格式。 支持骨骼动画(可渲染多个动画的组合) 支持用Bezier样条实现的曲面
OGRE特点 场景特性 拥有高效率和高度可配置性的资源管理器,并支持多 种场景类型。使用系统默认的场景组织方法,或通过 亲自编写插件使用自己的场景组织方法。 通过包围体(如包围盒)实现视域裁剪。 提供的二叉树场景管理器插件可加速室内场景的渲染, 支持加载Quake3关卡和shader脚本分析。 优秀的场景组织体系;场景结点支持物体的附属 (attach),并带动附属物体一起运动,实现了类似于 关节的运动继承体系。
OGRE特点 特效 粒子系统包括可以通过编写插件来扩展的 粒子发射器(emitter)和粒子特效影响器 (affector)。通过脚本语言可以不用重新 编译就设置和更改粒子属性。支持并自动 管理粒子池,从而提升粒子系统的性能。 支持天空盒、天空面和天空圆顶,使用非 常简单。 支持Billboard,以实现特效。 自动管理透明物体(系统自动帮你设置渲 染顺序和深度缓冲)
OGRE特点 其它特性 资源管理和文档加载(ZIP、PK3)。 支持高效的插件体系结构,它允许你不重 新编译就扩展引擎的功能。 运用'Controllers'你可以方便地改变一个数 值。例如动态改变一个带防护罩的飞船的 颜色值。 调试用的内存管理器负责检查内存溢出。
OGRE中的模块 OGRE中由很多模块组成,每个模块互相配合, 共同实现OGRE的强大功能和优秀特性。OGRE 的模块大致可表现为如下结构,这也基本上是 OGRE工程文件的结构: OgreMain PlatformManagers Plugins RenderSystems Tools
OgreMain模块 场景组织体系 Material管理 插件动态加载系统 数学支持库 渲染器和几何管道 网格/几何实体管理 资源管理 天空/背景渲染 公告板系统和粒子系统 日志和异常处理 事件监听器 编解码器和图像加载器 自定义内存管理器 基本动画 骨骼动画 字体渲染/字体加载 覆盖(Overlay)表面,二维元素 相关类 Node, SceneNode, SceneManager, Camera, MovableObject MaterialManager, Material, Material::TextureLayer Root, DynLibManager, DynLib Math, Vector3, Matrix3, Matrix4, Quaternion RenderSystem, RenderQueue, Renderable MeshManager, Mesh, SubMesh, MeshSerializer, PatchSurface ResourceManager, Resource, ArchiveManager, ArchiveEx SceneManager BillboardSet, Billboard, ParticleSystemManager, ParticleSystem, ParticleEmitter, ParticleAffector Exception, LogManager, Log FrameListener, RenderTargetListener Codec, JPGCodec, TGACodec, PNGCodec MemoryManager Animation, AnimationTrack, KeyFrame Skeleton, Bone, Animation, AnimationTrack, KeyFrame FontManager, Font Overlaymanager, Overlay, GuiElement, GuiContainer
Win32平台管理模块 实现了Windows平台的基本平台服务。 特性 输入管理 配置系统 相关类 Win32Input Win32ConfigDialog
BSP场景管理 该插件用BSP树和clusters提供了室内场景 的管理。它可以导入Quake3的关卡。 特性 BSP树 关卡导入 Shader支持
其它模块 文件系统插件 GuiElement插件 OctreeSceneManager插件 提供在文件系统的文件夹中定位资源的能 力。 提供标准的二维表面元素,如文本输入区 和边框。 OctreeSceneManager插件 用八叉树管理标准场景。可用它渲染地形。
其它模块 ParticleFX插件 Direct3D7渲染系统插件 Direct3D9渲染系统插件 此插件提供了标准的粒子发射器和粒子特 效影响器。 Direct3D7渲染系统插件 此插件提供了基于Direct3D7的渲染系统。 Direct3D9渲染系统插件 此插件提供了基于Direct3D9的渲染系统。
其它模块 SDL渲染系统插件 3ds2oof工具 3Dstudio Max导出器 此插件提供了基于OpenGL和SDL的渲染系统。 这个工具可以将3D Studio的网络文件(.3ds)转换 成.oof格式(OGRE以前的网络文件格式)。 这个工具已经被抛弃了。 3Dstudio Max导出器 这是一个3D Studio MAX(版本4或5)的插件,可以将3D Studio的模型数据转换成OGRE的.mesh或.skeleton格式。
其它模块 位图字体创建工具 Milkshape3D导出器 Python接口 能过此工具你可以把二进制字体文件转换成OGRE 的.fontdef文件。 Milkshape3D导出器 这个工具是Milkshape3D(一个建模工具)的插件,它 允许你将模式导出成OGRE支持的.mesh和.skeleton文件 格式。 Python接口 设计这个子工程的目的是以dll的形式提供一个接口, 使Python(一种脚本语言)可以直接驱动OGRE,并且 允许OGRE直接调用Python脚本以实现游戏相关的脚本 语言。
安装OGRE 这部分主要介绍 获取OGRE 支撑环境 编译OGRE 运行DEMO OGRE运行期结构
获取OGRE OGRE是一个开放源码项目,该项目的 网址是ogre.sourceforge.net。在这里可 以获取到OGRE的最新版本和文档,此 外还可以在论坛上与其它开发者交流。
支撑环境 OGRE是一个比较大的项目,不可能每 个功能都独立完成。OGRE的编译和使 用需要一些其它库作为支撑环境。在 Windows环境下编译和安装OGRE需要 如下支撑环境: STLport4.5.3 DirectX 9 SDK 其它第三方库
编译OGRE 在VC6环境里打开OGREOGRE的最新版本的 ogrenew文件夹下的Ogre.dsw工作区文件,执行 Batch Build指令,该指令会自动处理OGRE中各 个工程的依赖关系,正确完成全部的编译构建。 在VC7环境里打开OGREOGRE的最新版本的 ogrenews文件夹下的Ogre.sln工作区文件,执行 Batch Build指令,该指令会自动处理OGRE中各 个工程的依赖关系,正确完成全部的编译构建。
运行DEMO 在ogrenew\Samples\Common\bin\ Debug下可以看到Debug方式编译的全 部DEMO。
OGRE运行期结构 运行完DEMO之后,注意查看 ogrenew\Samples\Common\bin\Debug文 件夹中的内容,从这里可以看到OGRE 程序的运行环境。 除了DEMO的可执行文件外,该文件夹 中还包括如下的动态链接库:
OGRE运行期结构 OgreMain.dll OgrePlatform.dll RenderSystem_Direct3D7.dll RenderSystem_SDL.dll Plugin_GuiElements.dll Plugin_BspSceneManager.dll Plugin_OctreeSceneManager.dll Plugin_FileSystem.dll Plugin_ParticleFX.dll Devil.dll SDL.dll 其它:机器上还必须包括DirectX 8.1和STLport的动态链接库, 一般系统会将它们自动安装到Windows系统文件夹下。
OGRE运行期结构 OGRE的运行还需要如下的配置文件: ogre.cfg:OGRE的显示模式配置文件 Plugins.cfg:插件配置文件,在这里指定插 件的路径和插件文件名。上一个表中以 Plugin_开头的dll文件都是插件,它们可以 放在其它文件夹里,但必须在本文件里指 定路径。 resources.cfg:资源配置文件,设置资源搜索 路径,Zip文件也作为搜索路径对待。 quake3settings.cfg:quake3地图配置文件。 terrain.cfg:室外地形场景配置文件。
OGRE运行期结构 OGRE程序的资源路径在resources.cfg里指定。 OGRE DEMO的资源都放在 \ogrenew\Samples\Media及其下的Zip文件里。资 源文件包括以下内容: .skeleton骨骼动画的骨骼定义文件 .particle粒子模板定义文件 .overlay二维及三维界面定义文件.mesh模型文件 .material材质定义文件 .fontdef字体定义文件 .jpg图片文件 .png图片文件
准备用OGRE开发 从FrameWork开始 第一个3D程序 FrameWork与实际应用程序的关系 ExampleApplication类 ExampleFrameListener类 第一个3D程序
FrameWork 假设以Demo_EnvMapping工程为例,观察一下OGRE 的Demo程序的代码结构,可以发现在本工程中其实只 有两个文件:EnvMapping.h和EnvMapping.cpp。 查看本工程的Settings,在C/C++选项卡中打开 PreProcessor分类,可以看到有三个附加包含路径,其 中..\include是本工程的头文件路 径,..\..\Common\include是Demo程序公共的头文件路 径,..\..\..\OgreMain\include是OGRE引擎的头文件路径。 在..\..\Common\include中可以发现两个头文件: ExampleApplication.h和ExampleFrameListener.h。这两 个文件定义了简单OGRE程序的应用框架,它们封装了 简单OGRE程序的基本要素和运行过程。
FrameWork与实际应用程序的关系 在创建我们自己的OGRE程序时,只需要继承OGRE FrameWork中的类并做少量改动就可以了。OGRE FrameWork与实际应用的关系如下图所示:
ExampleApplication类 该类定义了如下数据成员: // 指向Root对象的指针 Root *mRoot; // 指向程序中摄像机的指针 Camera* mCamera; // 指向场景管理器的指针 SceneManager* mSceneMgr; // 指向“帧监听器”的指针 FrameListener* mFrameListener; // 指向渲染窗口的指针 RenderWindow* mWindow;
Root:OGRE系统的入口点 Root对象在程序中必须最先创建和最后释放。 OGRE引擎是通过Root将其它部分“串”起来 的,通过Root对象可以调出配置对话框以配置 渲染系统(RenderSystem); 通过Root对象可以获取到引擎其它部分的指针, 如SceneManager、RenderSystem、Resource managers等; Root对象还提供一个startRendering方法来开始 一个连续渲染过程,对该方法的调用在 ExampleApplication类中就可以见到。
Camera:摄象机 渲染结果实际上就是摄象机最后“看” 到的结果。
SceneManager:场景管理器 普通场景、室外封闭场景、室外无限场景和室内场景: enum SceneType { ST_GENERIC, ST_EXTERIOR_CLOSE, ST_EXTERIOR_FAR, ST_INTERIOR }; 室内场景采用BSP场景管理方式,室外场景采用八叉树场 景管理方式。初学者使用ST_GENERIC普通场景类型。 对于复杂室内和室外场景,OGRE分两部分:基本上固定 不变的“世界”,对于这部分采用BSP或Octree等特殊算 法提高渲染效率;场景中的可移动物体,它们的创建和控 制需要开发人员自己来完成。
FrameListener:帧监听器 监听最终用户的控制信息(鼠标、键盘、 遥控杆等),对摄象机、场景物体等进 行控制.
RenderWindow:渲染窗口 渲染结果所在的窗口,其中包括渲染真 正的目的地:视口Viewport。
启动 OGRE //继承自 ExampleApplication 类 EnvMapApplication app; try { app.go(); } catch( Exception& e ) { …… 以Demo_EnvMapping 工程为例,工程中有两个文件,EnvMapping.h和EnvMapping.cpp。EnvMapping.h 中定义了一个派生自ExampleApplication 类的EnvMapApplication类。在后来的代码分析中可以看出,ExampleApplication 类封装了简单OGRE程序的基本要素和运行过程。而EnvMapApplication所要做的就是override ExampleApplication 类的createScene函数,然后调用go函数,整个程序就运行起来了。 从这个DEMO可以发现,利用OGRE写一个DEMO是很简单的一件事情,程序员无需接触底层的渲染系统,不用关心用OpenGL 还是 Direct3D。
启动 OGRE // 启动引擎 virtual void go(void) { if (!setup()) return; mRoot->startRendering(); } go函数很简单,调用setup函数来初始化OGRE内部的各个组件,如果函数调用成功,即OGRE初始化成功,则整个OGRE开始工作。 调用mRoot成员的startRendering方法。
go函数 virtual void go(void): 该函数是ExampleApplication类除构造和析构函数以外唯 一的public函数,它一调用,程序就正式开始运行了。代 码如下: virtual void go(void) { if (!setup()) return; mRoot->startRendering(); } 从代码可以看出,先调用setup( )函数完成渲染前的准备, 如果setup成功,就由mRoot调用startRendering()开始渲染, 如果不成功则退出。
setup函数 Setup函数完成一个OGRE应用程序的开始渲染前 的准备工作。实际步骤如下: 首先创建Root类的对象。 加载资源路径setupResources(void ) 弹出config对话框,配置RenderSystem 选择场景管理器类型 创建并初始化摄像机 创建窗口中的视口 创建场景 创建帧监听器
setup函数代码 步骤的代码如下: virtual bool setup(void) { mRoot = new Root(); //首先创建Root类的对象。 setupResources(); //加载资源路径 bool carryOn = configure(); //弹出config对话框,配置RenderSystem if (!carryOn) return false; chooseSceneManager(); //选择场景管理器类型 createCamera(); //创建并初始化摄像机 createViewports(); //创建窗口中的视口 // Set default mipmap level (NB some APIs ignore this) TextureManager::getSingleton().setDefaultNumMipMaps(5); createScene(); //创建场景 createFrameListener(); //创建帧监听器 return true; }
setup函数 至此,setup()函数结束,它已完成 渲染之前的全部准备工作,接下来就由 Root对象调用startRendering()函数指 挥渲染系统开始连续的渲染过程。
OGRE 消息处理机制 消息机制的设计一般总要设计到三个部分:消息的 产生、消息的传递和消息的处理。 OGRE中的消息处理者的抽象类主要是listener类,而 listener必须是对应特定的target的,所以可以认为是 由listener和target两个抽象类组成。而消息传递由 Dispatcher和Processor组成。
FrameListener FrameListener类中有两个很重要的虚函数。 frameStarted和frameEnded。 class _OgreExport FrameListener { public: // 帧渲染之前的事件处理方法 virtual bool frameStarted(const FrameEvent& evt) { return true; } // 帧渲染之后的事件处理方法 virtual bool frameEnded(const FrameEvent& evt) { return true; } virtual ~FrameListener() {} } 以FrameListener为例 OGRE初始化的最后一步就是创建一个FrameListener。 OGRE在渲染每帧前会自动调用frameStarted,每帧结束后自动调用frameEnded。mFrameListener就是一个继承自FrameListener的ExampleFrameListener的实例指针,这样,mFrameListener只要实现这两个虚函数,就可以在每帧前后加入需要的控制操作。 值得一提的是,frameStarted和frameEnded函数的参数是FrameEvent类型。 FrameEvent类中定义了两个变量 timeSinceLastEvent 和 timeSinceLastFrame。这两个变量记录了两个帧之间间隔的一些时间参数。
ExampleFrameListener ExampleFrameListener继承自FrameListener。 它包装实现了一个帧监听器常用的功能。 OGRE对事件的处理方法有两种模式,立即 模式和缓冲模式。 ExampleFrameListener中有一个 mEventProcessor变量,它是用来处理缓冲模 式的事件的。 而在默认的非缓冲模式中, ExampleFrameListener实现了基本输入操作。 如:键盘WASD控制视点前后左右移动,鼠 标控制视点的旋转等等。 一般来讲立即模式适合于3D场景漫游过程,当在每帧渲染之前,系统捕获输入设备状态,并根据这些状态对场景中的物体和摄象机进行控制。而缓冲模式适合于GUI界面的情况(如设置菜单),输入设备状态可以被发送到各GUI元素进行处理(如按钮被按下)。 在实际运用中,可以从ExampleFrameListener中派生自己的myFrameListener类,重新实现frameStarted函数,以实现更复杂的功能,如让3D物体运动等等。当然最后不要忘了调用基类ExampleFrameListener中的frameStarted函数。否则,基本的键盘鼠标控制就没了。 非缓冲模式下的按键处理,相当于查询操作,每次都查看一下有无相应的键按下,并没有利用到OGRE中的消息处理机制。 分析一下缓冲模式下的消息处理机制。
EventProcessor EventProcessor类继承了FrameListener类, OGRE引擎自动在每帧前调用EventProcessor 的frameStarted函数。 EventProcessor类还继承了KeyTarget, MouseTarget,MouseMotionTarget。作为消息 传送目标的EventProcessor ,因此才有可能处 理这些消息。 缓冲模式下,OGRE把缓冲模式下的消息处理交给了EventProcessor类。 KeyTarget又继承自EventTarget。
键盘消息处理机制 Event Listener isMulticaster KeyXX(KeyEvent)=0 InputEvent EventTarget* mSource KeyEvent int mKey Event EventTarget virtual processKeyEvent()=0 KeyTarget processKeyEvent() add/remove KeyListener KeyListener Target Listener OGRE中有关键盘消息处理的有以下三个主要的类,EventTarget, EventListener, InputEvent. 如图: 可以看到EventListener类能过调用EventTarget的add/remove KeyListener可以注册到EventTarget上,当有消息到达时,EventTarget负责调用相应EventListener的处理函数。同时,消息本身被封装成InputEvent类。
EventProcessor的设置 //创建一个EventProcessor mEventProcessor = new EventProcessor(); //初始化 mEventProcessor->initialise(win); //开始处理消息 mEventProcessor->startProcessingEvents(); //将ExampleFrameListener作为按键消息的监听者注册 mEventProcessor->addKeyListener(this); 现在回到ExampleFrameListener在缓冲模式中的初始化情况。 在initialise中做了以下几个事情: cleanup()。清除mEventQueue, mdispatchList, mInputDevice等。 创建新的mEventQueue 创建新的mInputDevice. mEventQuue = InputDevice中的mEventQueue。 在startProcessingEvents中做了以下的事情: addFrameListener(this)。这个this指的是EventProcessor。这就是在OGRE中将EventProcessor注册为一个FrameListener。 在addKeyListener(this)这个this指的是ExampleFrameListener。因为mEventProcessor继承了KeyTarget。所以可以调用addKeyListener。这时候ExampleFrameListener就被当成是一个KeyListener了。因此,ExampleFrameListener必须声明或实现KeyListener中的一些函数,如keyClicked, keyPressed, keyReleased。 这时候EventProcess就初始化结束了,OGRE会在每帧的开始的时候调用EventProcess的frameStarted(evt)函数。
EventProcessor的运行 bool EventProcessor::frameStarted(const FrameEvent& evt) { mInputDevice->capture(); while (mEventQueue->getSize() > 0) InputEvent* e = mEventQueue->pop(); processEvent(e); delete e; } return true; 看一下EventProcess中的frameStarted函数,可以了解到实际中EventProcess是如何捕捉到消息输入并作出处理的。 如果有键盘消息的话, capture函数会产生键盘事件,保存在事件队列 mEventQueue中。 processEvent(e) 先遍历event dispatcher list, 处理这个事件。 如果事件没有被处理,遍历event target list 来处理这个事件。 最后手工判别消息的类别,再调用processXXXEvent(e),这些函数是EventProcessor父类XXXTarget中的函数。它们会调用注册在其上面的XXXListener中相应的处理函数。 OGRE中的键盘处理的过程就是如此。其它的消息处理机制与此类似。
第一个3D程序 OGRE的应用框架中定义好了 ExampleApplication与 ExampleFrameListener类,已封装了3D 应用程序的全部要素,应用开发者所要 做的工作主要有2点: 派生出自己的应用程序类,重新实现 createScene函数以创建场景。 派生出自己的监听器类,如果需要的话, 重新实现frameStarted函数以进行特殊的输 入控制和动画控制。
第一个3D程序 打开Demo_EnvMapping工程,这是一 个非常简单易懂的程序实例。 这个程序只有两个文件EnvMapping.h和 EnvMapping.cpp。 EnvMapping.h文件定义了 ExampleApplication类的派生类 EnvMapApplication,并重新实现了 createScene函数创建出一个亮闪闪的具 有环境帖图的食人魔头像。
class EnvMapApplication : public ExampleApplication { public: EnvMapApplication() {} protected: // 重新实现createScene函数,创建实际场景 void createScene(void) // 设置环境光 mSceneMgr->setAmbientLight(ColourValue(0.5, 0.5, 0.5)); // 创建点光源l Light* l = mSceneMgr->createLight("MainLight"); // 设置点光源l的位置,缺省颜色为白色 l->setPosition(20,80,50); // 读入ogrehead.mesh模型文件,创建为一个Entity。 Entity *ent = mSceneMgr->createEntity("head", "ogrehead.mesh"); // 设置食人魔Entity的材质为指定材质(环境贴图) ent->setMaterialName("Examples/EnvMappedRustySteel"); // 将食人魔Entity连接到场景根节点上。 mSceneMgr->getRootSceneNode()->createChild()->attachObject(ent); } };
第一个3D程序 一个基本的场景包括光、摄象机和模型等。 ExampleApplication类中创建了缺省摄象机(见 FrameWork部分)。 每个模型文件载入后被创建成Entity。为了便于 对场景的管理,OGRE引入了场景节点的概念, 所有场景节点组合成一棵节点树,全部Entity挂 在这棵节点树中的不同节点上。场景管理器 mSceneMgr通过对节点树的操控来完成对场景物 体的操控。 场景建立好之后,接下来处理应用程序的入口问 题,这部分代码在EnvMapping.cpp里.
EnvMapping程序
课后工作 安装OGRE 熟悉OGRE的变换和光照部分 熟悉OGRE的文件格式和场景管理模式 预习OGRE中的纹理映射