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.cfgOgre的插件配置文件,其中的内容是指出Ogre可以用的插件,以及插件所在的位置

2. ogre.cfg是由启动Ogre时的配置对话框自动生成的文件,一般来说不用手动修改,仅仅保存配置的结果。(当然也可以手动修改来对项目进行配置,不过不常用)

3. ogre.logOgre运行项目时的日志文件,记录一些信息来方便我们在程序出错时进行调试的。

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对底层硬件APIOpenGL或者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”)

Rootinitialise函数的第一个参数代表是否由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对象由RenderWindowaddViewport()方法进行创建

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中的方法如:frameStartedframeEnded。我们在使用的时候要从FrameListener中派生出一个子类,对FrameListener中的方法进行重写,以达到我们的实际目标

         FrameListenerRoot对象注册,注册后调用startRendering()即开始Render Loop

         root->addFrameListener(myListener);

         root->startRendering();

1.8 ResourceGroupManager

         任何程序都需要各种各样资源,在Ogre中这些资源如MeshSkeletonMaterialOverlayScriptTexture等等。加载资源可以使用手动编码和自动(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中定义了主要的frameStartedframeEnded以及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 用于对输入进行管理,可能的输入有MouseKeyboardJoyStick三种

    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 )

前两个参数为RenderWindowCamera,后面三个指定是否要使用带bufferKeyBoardMouseJoyStick。另外,在此构造函数中,还对上面列出的所有类成员都赋以了一个默认值,并完成了对mDebugOverlay, mInputManager , mKeyboard, mMouse ,mJoy的实质初始化工作。

另外它的析构函数完成了反向的工作,解除了对RenderWindow的注册,并销毁了以上对象。

3.3 windowResized函数

         ExampleFrameListener重写WindowEventListenerwindowResized函数,用于处理窗口大小变化,调整鼠标的裁剪区域

3.4 frameRenderingQueued函数

         此函数是FrameListener类中定义的虚函数,它在所有的Render Target完成渲染,但是WindowBuffer没有进行切换(一般程序都有双缓冲)的时候进行调用。此函数在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函数的重写,而是重写了frameRenderingQueuedframeEnded函数,这三个函数如何重写,需要我们根据具体情况判断。

3.8 moveCamera函数

根据上面frameRenderingQueue函数的一顿计算,我们得到了对Camera进行移动和旋转的参数,然后在frameRenderingQueue中调用moveCameraCamera进行了实际的移动

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.hExampleFrameListener.h这两个实例程序我们也就介绍完了,也对基本的Ogre对象有了了解,那么就可以进阶继续下一个Ogre之旅了,在下一篇文章写点啥呢?。


 

留言功能已取消,如需沟通,请邮件联系博主sunswk@sina.com,谢谢:)