Ogre样例程序解析
关键词:Ogre,源码
作者:BIce 创建时间:2012-03-27 20:51:09
在这篇文档里主要对Ogre源码里给出的样例程序OgreSDK\include\OGRE\ExampleApplication.h进行解释,进而达到简单的对Ogre核心对象进行了解和介绍的目的。
一、Ogre核心类
Ogre是一个面向对象的图形引擎,其中的主体也就是一些对象,下面对Ogre的核心类做一个简单的列举和介绍。
1.1 Root类
Ogre的对象体系是一个树形结构,这个树形结构的根就是Root对象,它是Ogre中最基本的对象,并负责对Ogre的一切内容进行管理。
Root对象的构造函数如下所示:
Root * root = new Root(“plugins.cfg”, “ogre.cfg”, “ogre.log”);
其中:
1. plugins.cfg是Ogre的插件配置文件,其中的内容是指出Ogre可以用的插件,以及插件所在的位置
2. ogre.cfg是由启动Ogre时的配置对话框自动生成的文件,一般来说不用手动修改,仅仅保存配置的结果。(当然也可以手动修改来对项目进行配置,不过不常用)。
3. ogre.log是Ogre运行项目时的日志文件,记录一些信息来方便我们在程序出错时进行调试的。
1.2 SceneManager类
场景管理器(SceneManager),它是Ogre引擎中极其要的组成概念。所有场景图的具体执行过程都来自SceneManager类。
场景管理器可以帮助你创建具体的场景节点(Scene Node)。所谓的场景节点就是你在场景中实际移动变换的基本单元。
场景中具体的场景内容需要挂接到场景节点上才能显示。这里所说的内容在大多数情况下指的就是实体(Entity)。实体继承于活动对象(MovableObject),并通过场景管理器来进行创建。
Scene Manager, Scene Node, Movable Object, Entity的关系如下图:
SceneManager的创建代码如下:
SceneManager* sceneMgr = root->createSceneManager(ST_GENERIC, “MySceneManager”);
其中createSceneManager是一个工厂方法,ST_GENERIC指定构造一个最基本的场景管理器,其还有很多可以使用的场景管理器,不同的场景管理器适合不同的情景,具体的选择要看API和实际情况选定。
1.3 RenderSystem类
渲染系统是Ogre对底层硬件API(OpenGL或者Direct3D)的一层抽象。它可以创建一种叫做渲染目标(RenderTarget)的类,RenderTarget是两个重要概念的抽象:渲染到窗口和渲染到纹理。RenderSystem还可以创建RenderWindow对象,而RenderWindow(渲染窗口)对象是对RenderTarget(渲染目标)接口的一个实现
RenderSystem对象默认由Root对象从配置文件创建并设置好,一般不用手动创建。
1.4 RenderWindow类
Ogre渲染窗口的概念是唯一可以被系统用来渲染场景的地方。就如同现实世界中的画布一样,Ogre把你程序的内容画到它的表面。为了实现这个目的,Ogre至少需要一个摄影机(Camera)来“拍摄”你的场景,同时也需要一个或几个视口(Viewport),用于存放摄影机的拍摄下来的“胶片”(和渲染窗口的功能有些类似)。
RenderWindow对象可以由Root对象间接创建,也可以由RenderSystem手动创建,具体方式如下:
1. 由Root间接创建
root->initialise(true, ”My Render Window”);
RenderWindow * window =root->getAutoCreatedWindow ();
或
RenderWindow *window=root->initialize(true ,”My Render Window”)
Root的initialise函数的第一个参数代表是否由Root对象创建一个RenderWindow给用户使用,第二个参数代表RenderWindow的标题,此处我们需要Root给我们默认创建一个,使用true即可。
2. 使用RenderSystem手动创建
//root初始化的时候,我们可以传入一个false值来告知Root不用给我们创建渲染窗口。
root->initialise(false);
//在这里我们仍然使用默认的参数来创建渲染窗口
RenderWindow *window = rSys->createRenderWindow(
“Manual Ogre Window”, //窗口的名字
800, //窗口的宽度(像素)
600, //窗口的高度(像素)
false, //是否全屏显示
0); //其他参数,使用默认值
1.5 Camera类
在任何图型相关的程序中,Camera绝对是一个非常重要的概念,Camera控制着我们能看到的画面,相当于我们在3D世界的眼镜。如下图:
在Ogre中的Camera,的创建代码为:
Camera *camera = sceneMgr->createCamera(“MainCam”);
Camera也有如图中所示的各种属性,设置大多靠API来实现,下面简单加以介绍。
1) 坐标设置x,y,z
camera->setPosition(Vector3(x,y,z));
2) 视线
mCamera->lookAt(Vector3(a,b,c));
设置camera的视线为朝向点(a,b,c)
3) 设置近截面
camera->setNearClipDistance(5.0f);
4) 设置远截面
camera->setFarClipDistance(1000.0f);
5) 视线方向和视截体下截面(或者上截面)的夹角W
camera->setFOVy(30.0f);
//设置夹角为30度
6) 摄像机的纵宽比率(X/Y)
camera->setAspectRatio(1.333333f);
将摄像机的纵宽比例调整为4:3.
7) Render Mode渲染模式
摄像机支持3种不同的渲染模式:边框,实体,“点”(只渲染顶点)。
camera->setPolygonMode(PM_WIREFRAME);
camera->setPolygonMode(PM_POINTS);
camera->setPolygonMode(PM_SOLOD);
PolygonMode mode = camera->getPolygonMode();
系统默认的是PM_SOLOD
8) 像机移动
//假设摄像机还在我们之前设置的200, 10, 200空间位置上。
camera->move(10, 0, 0); //摄像机移动到210, 10, 200
camera->moveRelative(0, 0, 10); //摄像机移动到210, 10, 210
Ogre里操纵Camera的函数有很多,给了我们许多操纵Camera的方法,下面不一一列出。
1.6 Viewport类
Viewport即为在RenderWindow中显示的一个个小窗口,借助viewport我们可以实现画中画的功能。
Viewport对象由RenderWindow的addViewport()方法进行创建
1.7 FrameListener类
1.7.1渲染循环
Render Loop 渲染循环,是执行对于场景的渲染的地方,可以由Root对象的startRendering()方法启动。
root->startRendering();//此方法中仅简单循环调用root->renderOneFrame()方法
Render Loop也可以由我们手动进行启动:
bool keepRendering=true;
while(keepRendering){
//do something
…
root->renderOneFrame();
if(nextmessageInQueue()==QUIT){
keepRendering=false;
}
}
1.7.2 FrameListener
Render Loop与我们在Windows程序设计中的while(true)很类似,而在Windows程序设计中负责在循环内对用户输入进行响应的叫做WindowProcedure,那么在Ogre中也有一个类似的用来对用户输入进行处理的地方,即为FrameListener对象。
Ogre在每一帧的渲染开始和结束的时候都会调用FrameListener中的方法如:frameStarted和frameEnded。我们在使用的时候要从FrameListener中派生出一个子类,对FrameListener中的方法进行重写,以达到我们的实际目标
FrameListener由Root对象注册,注册后调用startRendering()即开始Render Loop。
root->addFrameListener(myListener);
root->startRendering();
1.8 ResourceGroupManager类
任何程序都需要各种各样资源,在Ogre中这些资源如Mesh、Skeleton、Material、Overlay、Script、Texture等等。加载资源可以使用手动编码和自动(resource.cfg配置)的两种方式。
而Ogre为了管理这些资源,使用了Group(组)的概念,并且它为每种资源都提供了自己的资源管理器如MaterialManager。而默认的,Ogre使用一个单件对象ResourceGroupManager来作为资源管理器。它负责定位资源的具体位置并完成资源的初始化工作。
1.9 Ogre系统简单类图
关于Ogre的核心类我们差不多已经简单介绍过了,下面给出Ogre的简单类图给大家参考:
接下来我们开始对OgreSrc自带的两个例子文件进行解析,进而加强对上述Ogre类的理解。
二、ExampleApplication.h解析
在ExampleApplication.h文件中,Ogre给我们定义了一个最简单的Ogre使用框架,ExampleApplication类,我们可以通过派生得到一个实例化的子类来对它进行使用。关于用户输入处理(OIS)部分,主要借助与另外一个样例类ExampleFrameListener,将在后面进行介绍。
2.1 使用ExampleApplication.h
使用ExampleApplication.h这个样例程序是比较简单的,只要
#include
引入本文件,然后定义一个继承与ExampleApplication的类即可,如:
class SampleApp : public ExampleApplication
{
public:
// Basic constructor
SampleApp()
{}
protected:
// Just override the mandatory create scene method
void createScene(void)
{
// Create the SkyBox
mSceneMgr->setSkyBox(true, "Examples/CloudyNoonSkyBox");
// Create a light
Light* myLight = mSceneMgr->createLight("Light0");
myLight->setType(Light::LT_POINT);
myLight->setPosition(0, 40, 0);
myLight->setDiffuseColour(1, 1, 1);
myLight->setSpecularColour(1, 1, 1);
}
};
另外,在WinMain函数里定义一个SamleApp的实例并调用其go()方法,就可以完成此样例程序的启动了,上述代码会启动一个Ogre程序,场景中有一个点光源模拟太阳,有一个天空盒,地表为海水,可以通过鼠标和键盘来控制Camera的朝向和动作。
说完了简单的使用,下面开始介绍ExampleApplication类
2.2 ExampleApplication类成员
protected:
Root *mRoot; //我们的Root对象
Camera* mCamera; //Camera对象
SceneManager* mSceneMgr; //SceneManager对象
ExampleFrameListener* mFrameListener; //FrameListener对象
RenderWindow* mWindow; //RenderWindow对象
//mResourcePath,mConfigPath指定Root对象初始化的配置文件路径
Ogre::String mResourcePath;
Ogre::String mConfigPath;
2.3 ExampleApplication类的构造、析构函数
ExampleApplication类的构造函数的主要工作是给成员变量赋以一个默认值,而没有进行实际的变量初始化操作。像mSceneMgr, mRoot都赋以0,默认为null.
ExampleApplication类的析构函数主要完成了在ExampleApplication的成员变量的析构,主要是mRoot对象以及mFrameListener对象(其他由mRoot管理的对象由于mRoot的析构会被销毁?)
2.4 ExampleApplication类的启动函数
/// Start the example
virtual void go(void)
{
if (!setup())
return;
//开始渲染循环
mRoot->startRendering();
// clean up 清理场景
destroyScene();
}
go函数是ExampleApplication的启动函数,负责所有Ogre流程的执行,它包括三部分:
1. 启动程序,并初始化各种对象及相关资源
2. 开始主Render Loop
3. 清理场景
2.5 Setup函数:初始化需要的Ogre对象以及资源
Setup函数的函数体如下:
virtual bool setup(void)
{
//执行实质mRoot的初始化,目录为生成的可执行文件的执行目录
String pluginsPath;
//读入plugins.cfg文件,或者plugins_d.cfg
#if OGRE_DEBUG_MODE
pluginsPath = mResourcePath + "plugins_d.cfg";
#else
pluginsPath = mResourcePath + "plugins.cfg";
#endif
//初始化mRoot对象
mRoot = OGRE_NEW Root(pluginsPath,
mConfigPath + "ogre.cfg", mResourcePath + "Ogre.log");
//调用函数,根据resource.cfg配置文件,并向ResourceGroupManager进行资源的注册
setupResources();
//启动Config对话框,获取Ogre配置
bool carryOn = configure();
if (!carryOn)
return false;
//完成SceneManager的初始化
chooseSceneManager();
//完成Camera的设置
createCamera();
//初始化Viewport
createViewports();
// Set default mipmap level (NB some APIs ignore this)
//设置默认的材质级别,由于距离不同,我们使用的材质清晰度是不同的~
TextureManager::getSingleton().setDefaultNumMipmaps(5);
// Create any resource listeners (for loading screens)
//默认空函数体,可以修改以加上某些动作:loading界面?
createResourceListener();
// Load resources
//调用ResourceGroupManager::getSingleton().initialiseAllResourceGroups();初始化相关资源
loadResources();
// Create the scene,此函数为纯虚函数,必须由子类给予实现,实现具体的场景的布置,如上面对SampleApp给出的实现。
createScene();
//初始化一个ExampleFrameListener的实例,并向Root对象进行注册,由此实例完成对用户输入的响应。具体实现在下部分中说明
createFrameListener();
return true;
}
由源代码可见,在Setup函数中,完成了几乎所有Ogre需要资源和对象的初始化,为Render Loop已经做完了所有准备,执行完Setup函数,执行mRoot->startRendering()即可渲染。
而在Setup中调用的函数,包括Setup函数,都是虚函数,也就是我们在自己使用的时候完全可以重写其中任意一个函数,来达到我们想要的效果。需要注意的是,其中createScene是纯虚函数,子类必须给予实现。
三、ExampleFrameListener.h解析
根据上面的介绍,我们大致也了解了ExampleFrameListener类的作用,那就是作为Ogre样例程序的WindowProcedure,对用户的输入进行响应,进而完成一些行为,比如Camera的旋转、移动等等。Ogre中的FrameListener和也window程序设计中的一样,必须对应一个RenderWindow才可以,只有属于RenderWindow的消息才会被传输到对应FrameListener进行处理。
ExampleFrameListener继承于
public FrameListener, public WindowEventListener
其中,FrameListener中定义了主要的frameStarted和frameEnded以及frameRenderingQueue接口供渲染循环使用,而WindowEventListener则定义了一些窗口实际的处理函数接口如resize,close,closing用于相关事件的调用。
在本例中,还要说明的是所有的输入只用来做一件事:对Camera的移动。
那么现在我们开始对ExampleFrameListener类的分析。
3.1 ExampleFrameListener类成员
根据在ExampleApplication.h中的解释,我们已经了解了如何使用ExampleFrameListener,那么我们就从介绍它的类成员开始。
protected:
Camera* mCamera;
RenderWindow* mWindow;
//以上两个对象是由构造函数直接传递过来的,是赋给FrameListener进行操作的指针。
Vector3 mTranslateVector;
Real mCurrentSpeed;
bool mStatsOn;
String mDebugText; //Debug模式下的输出文本
unsigned int mNumScreenShots;
float mMoveScale;
float mSpeedLimit;
Degree mRotScale;
// just to stop toggles flipping too fast
Real mTimeUntilNextToggle ;
Radian mRotX, mRotY;
TextureFilterOptions mFiltering;
int mAniso;
int mSceneDetailIndex ;
Real mMoveSpeed;
Degree mRotateSpeed;
Overlay* mDebugOverlay; //Debug模式使用的覆盖层
//OIS Input devices 用于对输入进行管理,可能的输入有Mouse,Keyboard,JoyStick三种
OIS::InputManager* mInputManager;
OIS::Mouse* mMouse;
OIS::Keyboard* mKeyboard;
OIS::JoyStick* mJoy;
3.2 ExampleFrameListener的构造函数
ExampleFrameListener的构造函数如下:
ExampleFrameListener(RenderWindow* win, Camera* cam, bool bufferedKeys = false, bool bufferedMouse = false, bool bufferedJoy = false )
前两个参数为RenderWindow和Camera,后面三个指定是否要使用带buffer的KeyBoard,Mouse,JoyStick。另外,在此构造函数中,还对上面列出的所有类成员都赋以了一个默认值,并完成了对mDebugOverlay, mInputManager , mKeyboard, mMouse ,mJoy的实质初始化工作。
另外它的析构函数完成了反向的工作,解除了对RenderWindow的注册,并销毁了以上对象。
3.3 windowResized函数
ExampleFrameListener重写WindowEventListener的windowResized函数,用于处理窗口大小变化,调整鼠标的裁剪区域
3.4 frameRenderingQueued函数
此函数是FrameListener类中定义的虚函数,它在所有的Render Target完成渲染,但是Window的Buffer没有进行切换(一般程序都有双缓冲)的时候进行调用。此函数在frameStarted之后,在frameEnded之前。
我们的对输入的处理主要在此函数中。函数代码如下:
// Override frameRenderingQueued event to process that (don't care about frameEnded)
bool frameRenderingQueued(const FrameEvent& evt)
{
if(mWindow->isClosed()) return false; //如果窗口关闭则返回false,这会直接导致Render Loop的结束
mSpeedLimit = mMoveScale * evt.timeSinceLastFrame;
//Need to capture/update each device
mKeyboard->capture();
mMouse->capture();
if( mJoy ) mJoy->capture();
bool buffJ = (mJoy) ? mJoy->buffered() : true;
Ogre::Vector3 lastMotion = mTranslateVector; //
//Check if one of the devices is not buffered
if( !mMouse->buffered() || !mKeyboard->buffered() || !buffJ )
{
// one of the input modes is immediate, so setup what is needed for immediate movement
if (mTimeUntilNextToggle >= 0)
mTimeUntilNextToggle -= evt.timeSinceLastFrame;
//计算需要移动的距离,和旋转的角度
// Move about 100 units per second
mMoveScale = mMoveSpeed * evt.timeSinceLastFrame;
// Take about 10 seconds for full rotation
mRotScale = mRotateSpeed * evt.timeSinceLastFrame;
mRotX = 0;
mRotY = 0;
mTranslateVector = Ogre::Vector3::ZERO;
}
//Check to see which device is not buffered, and handle it
#if OGRE_PLATFORM != OGRE_PLATFORM_IPHONE
if( !mKeyboard->buffered() )
if( processUnbufferedKeyInput(evt) == false )
return false; //处理非缓冲的Keyboard事件,在processUnbufferedKeyInput函数中进行
#endif
if( !mMouse->buffered() )
if( processUnbufferedMouseInput(evt) == false )
return false; //处理菲换成的Mouse事件,在processUnbufferedMouseInput函数中进行
// ramp up / ramp down speed
if (mTranslateVector == Ogre::Vector3::ZERO)
{
// decay (one third speed)
mCurrentSpeed -= evt.timeSinceLastFrame * 0.3;
mTranslateVector = lastMotion;
}
else
{
// ramp up
mCurrentSpeed += evt.timeSinceLastFrame;
}
// Limit motion speed
if (mCurrentSpeed > 1.0)
mCurrentSpeed = 1.0;
if (mCurrentSpeed < 0.0)
mCurrentSpeed = 0.0;
mTranslateVector *= mCurrentSpeed;
if( !mMouse->buffered() || !mKeyboard->buffered() || !buffJ )
moveCamera();//如果有一个是非缓冲的输入,则移动Camera
return true;
}
由上代码可见,frameRenderingQueued()函数完成了对输入的主要处理工作,并根据输入,对需要Camera的移动距离以及旋转角度进行了计算,最后调用moveCamera函数对Camera进行的移动。我们再看下对于鼠标和键盘的处理函数。
3.5 processUnbufferedKeyInput函数:键盘处理
在函数processUnbufferedKeyInput函数中,我们对键盘事件进行了处理,现少量截取代码如下:
virtual bool processUnbufferedKeyInput(const FrameEvent& evt)
{
Real moveScale = mMoveScale;
if(mKeyboard->isKeyDown(OIS::KC_LSHIFT))
moveScale *= 10;
if(mKeyboard->isKeyDown(OIS::KC_A)) //处理按键A
mTranslateVector.x = -moveScale; // Move camera left
if(mKeyboard->isKeyDown(OIS::KC_D)) //处理按键D
mTranslateVector.x = moveScale; // Move camera RIGHT
……………more…
}
从上面代码我们可以看出,这和WindowProcedure是很类似的,只不过是把所有的Keyboard事件放在一起进行处理,另外使用了OIS框架对输入进行处理而已。
3.6 processUnbufferedMouseInput函数:鼠标处理
函数processUnbufferedMouseInput函数主要完成了对鼠标的处理,其代码如下:
virtual bool processUnbufferedMouseInput(const FrameEvent& evt)
{
// Rotation factors, may not be used if the second mouse button is pressed
// 2nd mouse button - slide, otherwise rotate
const OIS::MouseState &ms = mMouse->getMouseState();
if( ms.buttonDown( OIS::MB_Right ) )
{
mTranslateVector.x += ms.X.rel * 0.13;
mTranslateVector.y -= ms.Y.rel * 0.13;
}
else
{
mRotX = Degree(-ms.X.rel * 0.13);
mRotY = Degree(-ms.Y.rel * 0.13);
}
return true;
}
由代码可见,在函数中,只对可能影响旋转参数的鼠标右键进行了处理,并根据它调整了旋转参数。
3.7 frameEnded函数
bool frameEnded(const FrameEvent& evt)
{
updateStats();
return true;
}
此函数仅仅调用了一个updateStatus函数完成了对当前状态的输出,没有其他作用。另外值得我们注意的是,在此ExampleFrameListener类中,我们没有看到关于frameStarted函数的重写,而是重写了frameRenderingQueued和frameEnded函数,这三个函数如何重写,需要我们根据具体情况判断。
3.8 moveCamera函数
根据上面frameRenderingQueue函数的一顿计算,我们得到了对Camera进行移动和旋转的参数,然后在frameRenderingQueue中调用moveCamera对Camera进行了实际的移动
virtual void moveCamera()
{
// Make all the changes to the camera
// Note that YAW direction is around a fixed axis (freelook style) rather than a natural YAW
//(e.g. airplane)
mCamera->yaw(mRotX);
mCamera->pitch(mRotY);
mCamera->moveRelative(mTranslateVector);
}
那么到此为止,简单的ExampleApplication.h和ExampleFrameListener.h这两个实例程序我们也就介绍完了,也对基本的Ogre对象有了了解,那么就可以进阶继续下一个Ogre之旅了,在下一篇文章写点啥呢?。