九天雁翎的博客
如果你想在软件业获得成功,就使用你知道的最强大的语言,用它解决你知道的最难的问题,并且等待竞争对手的经理做出自甘平庸的选择。 -- Paul Graham

OGRE On iPhone ----Ogre的iPhone基础模版框架源代码分析

write by 九天雁翎(JTianLing) -- www.jtianling.com

讨论新闻组及文件

    当想要在iPhone上使用某个3D引擎的时候,感觉我这水平,自己写好像还不现实,学到自己能写都不知道要到何年何月了,于是折腾过没有官方支持但是比较简单而且我比较熟悉的Irrlicht。虽然的确成功了(见我原来的文章

),
但是弄2D游戏的时候,(用其他引擎)经历过一些痛苦的事情后,我发现强大,成熟的引擎,以及官方的支持是多么重要。。。。。于是,虽然Ogre的效率在
iPhone上还有些难以承受,但是,我还是希望学习学习,并将Ogre作为优先使用的3D引擎。谁叫Ogre那么流行呢?将来要是弄老本行,去做网络游
戏,估计也不错^^

 

本文写作目的:

1.Ogre的基础模版程序

是Ogre在iPhone平台的模版程序的基础,但是此模版程序没有太多的说明和注释,作者仅仅是说程序具有自解释性。。。。。我这里代为简单解释一下。。。。

2.Ogre虽然是跨平台引擎,但是既然跨平台,在各平台就会有些差异存在,而从桌面程序到iPhone这样的移动平台,差异就更大了,我这里不从源码的角度,仅仅从示例程序对Ogre的使用角度指出这些差异。

而怎么在iPhone上编译运行Ogre,怎么样安装Ogre为iPhone做的项目模版,因为有官方支持嘛,下个SDK

,非常容易,不像Irrlicht那样需要调整很多东西,所以,这些都不是本文的重点,大家自己摸索一下吧。本文也不是Ogre的入门教程,这个请去看Ogre的WIKI

,也有中文版

。(但是WIKI的中级教程有些老,代码一般不能直接在新版本的Ogre和CEGU中工作,但是理解后,稍微进行点改动就行了,中文版可能更老,我没有看,推荐还是去看原版的好,毕竟都是很简单的东西)

 

基础框架了解

在弄清楚一切之前,利用Ogre的iPhone的基础框架,可以得到以下的运行效果:

一个癞蛤蟆一样的Ogre标识模型。。。。。我见过最难看的一个。。。。。。

先在机器上运行一下,了解这个基础框架实现了哪些功能,主要有下列三个:

1.3D场景中显示了天空盒和一个癞蛤蟆皮Ogre头。。。。

2.2D UI中显示了一系列的Debug信息以及OGRE的标识

3.支持触摸进行Camara的旋转。

然后,下面对此框架的了解除了大致框架外,了解3个功能是在哪里实现的。

 

基础框架代码分析

主要部分是OgreFramework.h和OgreFramework.cpp两个文件包含的OgreFramework类,这个类虽然内容简单,但是
基本功能齐全,并且,在此简单的框架的基础上,尽量简单的实现了跨平台,这样的话,可以尝试开发Mac,Win32程序,然后移植到iPhone中,以加
快开发速度,缩短开发周期。

如下所示

#if OGRE_PLATFORM == OGRE_PLATFORM_IPHONE

class OgreFramework : public Ogre::Singleton<OgreFramework>, OIS::KeyListener, OIS::MultiTouchListener

#else

class OgreFramework : public Ogre::Singleton<OgreFramework>, OIS::KeyListener, OIS::MouseListener

#endif

OgreFramework是一个C++类,并通过Ogre::Singleton做成单件,并且,这里可以看出来,通过OIS的MultiTouchListener来支持多点触摸。

所有的接口如下:

public:

    OgreFramework();

    ~OgreFramework();

#if OGRE_PLATFORM == OGRE_PLATFORM_IPHONE

    bool initOgre(Ogre::String wndTitle, OIS::KeyListener *pKeyListener = 0, OIS::MultiTouchListener *pMouseListener = 0);

#else

    bool initOgre(Ogre::String wndTitle, OIS::KeyListener *pKeyListener = 0, OIS::MouseListener *pMouseListener = 0);

#endif

    void updateOgre(double timeSinceLastFrame);

    void updateStats();

    void moveCamera();

    void getInput();

    bool isOgreToBeShutDown()const{return m_bShutDownOgre;}  

    bool keyPressed(const OIS::KeyEvent &keyEventRef);

    bool keyReleased(const OIS::KeyEvent &keyEventRef);

#if OGRE_PLATFORM == OGRE_PLATFORM_IPHONE

    bool touchMoved(const OIS::MultiTouchEvent &evt);

    bool touchPressed(const OIS::MultiTouchEvent &evt);

    bool touchReleased(const OIS::MultiTouchEvent &evt);

    bool touchCancelled(const OIS::MultiTouchEvent &evt);

#else

    bool mouseMoved(const OIS::MouseEvent &evt);

    bool mousePressed(const OIS::MouseEvent &evt, OIS::MouseButtonID id);

    bool mouseReleased(const OIS::MouseEvent &evt, OIS::MouseButtonID id);

#endif

可以从接口看出,此框架设计的使用方式是直接作为变量/成员变量使用,不是希望使用者继承此类。下面分别看各个部分。

初始化

其他部分都属于进一步的操作和更新了,主要部分是Ogre的初始化部分,在initOgre,先看看这个函数:(感觉必要的地方我添加了注释)

 

/|||||||||||||||||||||||||||||||||||||||||||||||

#if OGRE_PLATFORM == OGRE_PLATFORM_IPHONE

bool OgreFramework::initOgre(Ogre::String wndTitle, OIS::KeyListener *pKeyListener, OIS::MultiTouchListener *pMouseListener)

#else

bool OgreFramework::initOgre(Ogre::String wndTitle, OIS::KeyListener *pKeyListener, OIS::MouseListener *pMouseListener)

#endif

{
// 日志管理的初始化

    new Ogre::LogManager();

    

    m_pLog = Ogre::LogManager::getSingleton().createLog("OgreLogfile.log", true, true, false);

    m_pLog->setDebugOutputEnabled(true);

   
// 不是静态链接的时候使用plugins.cfg配置文件,因为iPhone只能使用静态链接方式,没戏了

    String pluginsPath;

    // only use plugins.cfg if not static

#ifndef OGRE_STATIC_LIB

    pluginsPath = m_ResourcePath + "plugins.cfg";

#endif

    
// Root的创建(Ogre::Root* m_pRoot;)

    m_pRoot = new Ogre::Root(pluginsPath, Ogre::macBundlePath() + "/ogre.cfg");

    

#ifdef OGRE_STATIC_LIB

    m_StaticPluginLoader.load();

#endif

    
// 代码虽然是显示配置对话框的代码,但是示例中不会显示配置对话框,而是直接restore了原来的配置

    if(!m_pRoot->showConfigDialog())

        return false;

   
// 渲染窗口的创建及初始化(Ogre::RenderWindow* m_pRenderWnd;)

    m_pRenderWnd = m_pRoot->initialise(true, wndTitle);

    
// iPhone平台特定操作,设定屏幕位置及大小(这些都是随着设备就固定了的),这里的(0,0)还是2维坐标,即屏幕的左上角
// 有意思的是一开始的时候,Ogre就认为iPhone设备是横的。。。。即RenderWnd height: 320     width: 480
// 即状态OR_LANDSCAPELEFT  = OR_DEGREE_270

#if OGRE_PLATFORM == OGRE_PLATFORM_IPHONE

  m_pRenderWnd->reposition(0, 0);

  m_pRenderWnd->resize(m_pRenderWnd->getHeight(), m_pRenderWnd->getWidth());

#endif

    
// 以下是SceneMgr,Camera,Viewport的创建及初始化,与一般的过程一样

    m_pSceneMgr = m_pRoot->createSceneManager(ST_GENERIC, "SceneManager");

    m_pSceneMgr->setAmbientLight(Ogre::ColourValue(0.7, 0.7, 0.7));

    

    m_pCamera = m_pSceneMgr->createCamera("Camera");

    m_pCamera->setPosition(Vector3(0, 60, 60));

    m_pCamera->lookAt(Vector3(0,0,0));

    m_pCamera->setNearClipDistance(1);

    

    m_pViewport = m_pRenderWnd->addViewport(m_pCamera);

    m_pViewport->setBackgroundColour(ColourValue(0.8, 0.7, 0.6, 1.0));

    

    m_pCamera->setAspectRatio(Real(m_pViewport->getActualWidth()) / Real(m_pViewport->getActualHeight()));

    

    m_pViewport->setCamera(m_pCamera);

    
// OIS的部分,构造的方式为了跨平台,所以有些独特^^通过字符串的方式来索引参数创建,
// 传递的参数是窗口的句柄,但是也转换成string了

    unsigned long hWnd = 0;

    OIS::ParamList paramList;

    m_pRenderWnd->getCustomAttribute("WINDOW", &hWnd);

    

    paramList.insert(OIS::ParamList::value_type("WINDOW", Ogre::StringConverter::toString(hWnd)));

    

    m_pInputMgr = OIS::InputManager::createInputSystem(paramList);

    
// OIS有MultiTouch的支持,但是在这个框架中还是直接赋值给m_pMouse(这个变量已经根据宏分别创建了)

#if OGRE_PLATFORM != OGRE_PLATFORM_IPHONE

    m_pKeyboard = static_cast<OIS::Keyboard*>(m_pInputMgr->createInputObject(OIS::OISKeyboard, true));

    m_pMouse = static_cast<OIS::Mouse*>(m_pInputMgr->createInputObject(OIS::OISMouse, true));

    

    m_pMouse->getMouseState().height = m_pRenderWnd->getHeight();

    m_pMouse->getMouseState().width     = m_pRenderWnd->getWidth();

#else

    m_pMouse = static_cast<OIS::MultiTouch*>(m_pInputMgr->createInputObject(OIS::OISMultiTouch, true));

#endif

    
// 这里可以参考此类的构造函数,即允许构造此类的时候,传递外来的输入响应对象。

#if OGRE_PLATFORM != OGRE_PLATFORM_IPHONE

    if(pKeyListener == 0)

        m_pKeyboard->setEventCallback(this);

    else

        m_pKeyboard->setEventCallback(pKeyListener);

#endif

    

    if(pMouseListener == 0)

        m_pMouse->setEventCallback(this);

    else

        m_pMouse->setEventCallback(pMouseListener);

    
// 读取配置,与一般的情况一样,只是多了个m_ResourcePath作为基础目录,为Mac和iPhone准备的。
// 这两个平台因为用了Bundle,所以与PC有些不一样

    Ogre::String secName, typeName, archName;

    Ogre::ConfigFile cf;

    cf.load(m_ResourcePath + "resources.cfg");

    

    Ogre::ConfigFile::SectionIterator seci = cf.getSectionIterator();

    while (seci.hasMoreElements())

    {

        secName = seci.peekNextKey();

        Ogre::ConfigFile::SettingsMultiMap *settings = seci.getNext();

        Ogre::ConfigFile::SettingsMultiMap::iterator i;

        for (i = settings->begin(); i != settings->end(); ++i)

        {

            typeName = i->first;

            archName = i->second;

// 还是为Mac和iPhone进行了一些特殊处理,英文注释很详细了

#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE || OGRE_PLATFORM == OGRE_PLATFORM_IPHONE

            // OS X does not set the working directory relative to the app,

            // In order to make things portable on OS X we need to provide

            // the loading with it's own bundle path location

            if (!Ogre::StringUtil::startsWith(archName, "/", false)) // only adjust relative dirs

                archName = Ogre::String(Ogre::macBundlePath() + "/" + archName);

#endif

            Ogre::ResourceGroupManager::getSingleton().addResourceLocation(archName, typeName, secName);

        }

    }

    Ogre::TextureManager::getSingleton().setDefaultNumMipmaps(5);

    Ogre::ResourceGroupManager::getSingleton().initialiseAllResourceGroups();

    
// 创建计时器

    m_pTimer = OGRE_NEW Ogre::Timer();

    m_pTimer->reset();

    // 获取Debug的Overlay层,用于输出一些调试信息

    m_pDebugOverlay = OverlayManager::getSingleton().getByName("Core/DebugOverlay");

    m_pDebugOverlay->show();

    

    m_pRenderWnd->setActive(true);

    

    return true;

}

 

这个函数的目的是很单纯的,作为框架性代码,没有像Ogre的基础教程createScene函数一样,添加场景创建的代码,仅仅是初始化了一些相关的对象。而这个函数已经完成了此基础框架希望完成的大部分功能了。

 

输入

keyPressed,mouseMoved在iPhone中是完全没有什么用了,touch的系列接口虽然与iOS SDK基本一致,但是参数上OIS进行了进一步的分装,估计是为了将来方便移植到其他触摸移动平台(比如Android)。

此参数定义如下:

    //! Touch Event type

    enum MultiTypeEventTypeID

    {

        MT_None = 0, MT_Pressed, MT_Released, MT_Moved, MT_Cancelled

    };

    class _OISExport MultiTouchState

    {

    public:

        MultiTouchState() : width(50), height(50), touchType(MT_None) {};

        /** Represents the height/width of your display area.. used if touch clipping

        or touch grabbed in case of X11 - defaults to 50.. Make sure to set this

        and change when your size changes.. */

        mutable int width, height;

        //! X Axis component

        Axis X;

        //! Y Axis Component

        Axis Y;

        //! Z Axis Component

        Axis Z;

        int touchType;

        inline bool touchIsType( MultiTypeEventTypeID touch ) const

        {

            return ((touchType & ( 1L << touch )) == 0) ? false : true;

        }

       

        //! Clear all the values

        void clear()

        {

            X.clear();

            Y.clear();

            Z.clear();

            touchType = MT_None;

        }

    };

    /** Specialised for multi-touch events */

    class _OISExport MultiTouchEvent : public EventArg

    {

    public:

        MultiTouchEvent( Object *obj, const MultiTouchState &ms ) : EventArg(obj), state(ms) {}

        virtual ~MultiTouchEvent() {}

        //! The state of the touch - including axes

        const MultiTouchState &state;

    };

 

基本上还是很容易理解的,就是有些奇怪的添加了Z坐标,难道将来会有立体触摸?-_-!OIS的作者想的还真远啊。。。。。。。

此外,这种参数封装还是有些弱的,iOS的SDK要强大一些,直接内置了多次触摸的查询等的支持,OIS为了通用,看来是不行了。

 

然后,在touchMoved函数中,实现了camera的旋转。

#if OGRE_PLATFORM == OGRE_PLATFORM_IPHONE

bool OgreFramework::touchMoved(const OIS::MultiTouchEvent &evt)

{

    OIS::MultiTouchState state = evt.state;

    int origTransX = 0, origTransY = 0;

    switch(m_pCamera->getViewport()->getOrientationMode())

    {

        case Ogre::OR_LANDSCAPELEFT:

            origTransX = state.X.rel;

            origTransY = state.Y.rel;

            state.X.rel = -origTransY;

            state.Y.rel = origTransX;

            break;

            

        case Ogre::OR_LANDSCAPERIGHT:

            origTransX = state.X.rel;

            origTransY = state.Y.rel;

            state.X.rel = origTransY;

            state.Y.rel = origTransX;

            break;

            

            // Portrait doesn't need any change

        case Ogre::OR_PORTRAIT:

        default:

            break;

    }

    m_pCamera->yaw(Degree(state.X.rel * -0.1));

    m_pCamera->pitch(Degree(state.Y.rel * -0.1));

    

    return true;

}

大部分代码是为了不同的设备方向而进行的参数调整,其实主要也就是Camera的yaw,pitch旋转而已,有意思的是,touch参数里面的相对值的存在,使得旋转速度的设定非常简洁。

 

调试信息输出

框架类中剩下的还有点用的东西就是调试信息的输出了,代码如下:

//|||||||||||||||||||||||||||||||||||||||||||||||

void OgreFramework::updateStats()

{

    static String currFps = "Current FPS: ";

    static String avgFps = "Average FPS: ";

    static String bestFps = "Best FPS: ";

    static String worstFps = "Worst FPS: ";

    static String tris = "Triangle Count: ";

    static String batches = "Batch Count: ";

    

    OverlayElement* guiAvg = OverlayManager::getSingleton().getOverlayElement("Core/AverageFps");

    OverlayElement* guiCurr = OverlayManager::getSingleton().getOverlayElement("Core/CurrFps");

    OverlayElement* guiBest = OverlayManager::getSingleton().getOverlayElement("Core/BestFps");

    OverlayElement* guiWorst = OverlayManager::getSingleton().getOverlayElement("Core/WorstFps");

    

    const RenderTarget::FrameStats& stats = m_pRenderWnd->getStatistics();

    guiAvg->setCaption(avgFps + StringConverter::toString(stats.avgFPS));

    guiCurr->setCaption(currFps + StringConverter::toString(stats.lastFPS));

    guiBest->setCaption(bestFps + StringConverter::toString(stats.bestFPS)

                        +" "+StringConverter::toString(stats.bestFrameTime)+" ms");

    guiWorst->setCaption(worstFps + StringConverter::toString(stats.worstFPS)

                         +" "+StringConverter::toString(stats.worstFrameTime)+" ms");

    

    OverlayElement* guiTris = OverlayManager::getSingleton().getOverlayElement("Core/NumTris");

    guiTris->setCaption(tris + StringConverter::toString(stats.triangleCount));

    

    OverlayElement* guiBatches = OverlayManager::getSingleton().getOverlayElement("Core/NumBatches");

    guiBatches->setCaption(batches + StringConverter::toString(stats.batchCount));

    

    OverlayElement* guiDbg = OverlayManager::getSingleton().getOverlayElement("Core/DebugText");

    guiDbg->setCaption("");

}

都是获取到响应的UI元素,然后进行输出,没有太多好讲的。

 

框架驱动代码

上述的框架还不足以构成一个完成的程序,仅仅是跨平台实现中干了一些脏活,可以跨平台的部分,我们还需要实际的驱动代码来使用这个框架。

这些代码又分两个部分,DemoApp类和AppDelegate类。

DemoApp:

class DemoApp : public OIS::KeyListener

{

public:

    DemoApp();

    ~DemoApp();

    void startDemo();

    void setupDemoScene();

    void setShutdown(bool flag) { m_bShutdown = flag; }

    

    bool keyPressed(const OIS::KeyEvent &keyEventRef);

    bool keyReleased(const OIS::KeyEvent &keyEventRef);

private:

    void runDemo();

    Ogre::SceneNode*            m_pCubeNode;

    Ogre::Entity*                m_pCubeEntity;

    bool                        m_bShutdown;

};

 

很简洁也很直接的定义,没有去继承使用Ogre的ExampleApplication等类。仅仅继承了KeyListener

,响应keyPressed

keyReleased

,而在iPhone中我们又不用管。

那么,需要看到就是真实创建天空盒和那个癞蛤蟆头的部分了。

 

void DemoApp::startDemo()

{

    new OgreFramework();

    if(!OgreFramework::getSingletonPtr()->initOgre("DemoApp v1.0", this, 0))

        return;

    

    m_bShutdown = false;

    OgreFramework::getSingletonPtr()->m_pLog->logMessage("Demo initialized!");

    setupDemoScene();

    runDemo();

}

 

此函数完成了前面讲到的OgreFramework的创建及初始化,于是,此时,我们Ogre程序需要的root,scene manager, viewport,camera,等东西都已经有了。

下面直接调用setupDemoScene来创建了场景,然后通过runDemo来运行程序(即进入主循环)。

 

void DemoApp::setupDemoScene()

{

    OgreFramework::getSingletonPtr()->m_pSceneMgr->setSkyBox(true, "Examples/SpaceSkyBox");

    OgreFramework::getSingletonPtr()->m_pSceneMgr->createLight("Light")->setPosition(75,75,75);

    m_pCubeEntity = OgreFramework::getSingletonPtr()->m_pSceneMgr->createEntity("Cube", "ogrehead.mesh");

    m_pCubeNode = OgreFramework::getSingletonPtr()->m_pSceneMgr->getRootSceneNode()->createChildSceneNode("CubeNode");

    m_pCubeNode->attachObject(m_pCubeEntity);

}

 

因为初始化完了,那么创建场景的部分就简单了,如上所示,也就几句代码。属于Ogre常规操作,也就不多说了。

 

//|||||||||||||||||||||||||||||||||||||||||||||||

void DemoApp::runDemo()

{

    OgreFramework::getSingletonPtr()->m_pLog->logMessage("Start main loop...");

    

    double timeSinceLastFrame = 0;

    double startTime = 0;

    OgreFramework::getSingletonPtr()->m_pRenderWnd->resetStatistics();

    

    while(!m_bShutdown && !OgreFramework::getSingletonPtr()->isOgreToBeShutDown())

    {

        if(OgreFramework::getSingletonPtr()->m_pRenderWnd->isClosed())m_bShutdown = true;

#if OGRE_PLATFORM != OGRE_PLATFORM_IPHONE

        Ogre::WindowEventUtilities::messagePump();

#endif    

        if(OgreFramework::getSingletonPtr()->m_pRenderWnd->isActive())

        {

            startTime = OgreFramework::getSingletonPtr()->m_pTimer->getMillisecondsCPU();

                    

#if OGRE_PLATFORM != OGRE_PLATFORM_IPHONE

            OgreFramework::getSingletonPtr()->m_pKeyboard->capture();

#endif

            OgreFramework::getSingletonPtr()->m_pMouse->capture();

            OgreFramework::getSingletonPtr()->updateOgre(timeSinceLastFrame);

            OgreFramework::getSingletonPtr()->m_pRoot->renderOneFrame();

        

            timeSinceLastFrame = OgreFramework::getSingletonPtr()->m_pTimer->getMillisecondsCPU() - startTime;

        }

        else

        {

#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32

           Sleep(1000);

#elif OGRE_PLATFORM == OGRE_PLATFORM_APPLE

           sleep(1000);

#endif

        }

    }

    OgreFramework::getSingletonPtr()->m_pLog->logMessage("Main loop quit");

    OgreFramework::getSingletonPtr()->m_pLog->logMessage("Shutdown OGRE...");

}

看完这里,代码上是没有什么疑问了,主循环嘛。。。。。。。。不过,对于代码实现的问题上,还是有些问题的,从前面的代码来看,主循环通过

    while(!m_bShutdown && !OgreFramework::getSingletonPtr()->isOgreToBeShutDown())

控制,而且里面完全没有帧率控制代码。。。。也没有使用Ogre在教程中提倡的使用FrameListeners的frameRenderingQueued

回调函数来完成update,这个感觉比较奇怪。

而假如是通过

if(OgreFramework::getSingletonPtr()->m_pRenderWnd->isActive())

即,是否Actice来控制帧率也不正常,这个应该是窗口在后台不需要处理输入的时候使用的,而且一次sleep了1秒钟,也不可能胜任这个工作。

带着疑问调试此函数的代码,发现根本没有调用此函数。。。。。。-_-!

这里搞这么多#if OGRE_PLATFORM != OGRE_PLATFORM_IPHONE,结果iPhone中根本不调用,还是比较具有迷惑性的。。。。。。

 

真实的版本,当然就看AppDelegate了,AppDelegate是ObjC类。。。。。。看到这里,突然感觉,我虽然仅仅是最近用了几个月的ObjC,但是因为最近一直在用Objc,竟然感觉比用了好几年的C++更加亲切了。。。。。-_-!

 

AppDelegate

在main函数中,首先看到AppDelegate的使用:

int main(int argc, char **argv)

#endif

{

#if OGRE_PLATFORM == OGRE_PLATFORM_IPHONE

  NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

  int retVal = UIApplicationMain(argc, argv, @"UIApplication", @"AppDelegate");

  [pool release];

  return retVal;

#else

}


以,我原本以为AppDelegate就是一个从ObjC到DemoApp的一个小外壳而已,处理一些简单的delegate回调,结果发现根本不是这
样,在iPhone版本的示例程序中,AppDelegate才是实际的DemoApp,而原来DemoApp,其实AppDelegate只是使用了其
setup场景的部分而已。原来我也是只猜到了开始,但是猜不到这个结果。。。。。。。

在applicationDidFinishLaunching中经过了一些在iPhone中必须进行的一些window,view操作后,直接进入了主题,go函数,看看go函数:

- (void)go {

  NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

  try {

    new OgreFramework();

    if(!OgreFramework::getSingletonPtr()->initOgre("DemoApp v1.0", &demo, 0))

      return;

    

    demo.setShutdown(false);

    

    OgreFramework::getSingletonPtr()->m_pLog->logMessage("Demo initialized!");

    

    demo.setupDemoScene();

    OgreFramework::getSingletonPtr()->m_pRenderWnd->resetStatistics();

    

    if (mDisplayLinkSupported)

    {

     
// CADisplayLink is API new to iPhone SDK 3.1. Compiling against
earlier versions will result in a warning, but can be dismissed

     
// if the system version runtime check for CADisplayLink exists in
-initWithCoder:. The runtime check ensures this code will

      // not be called in system versions earlier than 3.1.

      mDate = [[NSDate alloc] init];

      mLastFrameTime = -[mDate timeIntervalSinceNow];

      

      mDisplayLink = [NSClassFromString(@"CADisplayLink") displayLinkWithTarget:self selector:@selector(renderOneFrame:)];

      [mDisplayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];

    }

    else

    {

      mTimer = [NSTimer scheduledTimerWithTimeInterval:(NSTimeInterval)(1.0f / 60.0f) * mLastFrameTime

                                                target:self

                                              selector:@selector(renderOneFrame:)

                                              userInfo:nil

                                               repeats:YES];

    }

  } catch( Ogre::Exception& e ) {

    std::cerr << "An exception has occurred: " <<

    e.getFullDescription().c_str() << std::endl;

  }

 

  [pool release];

}

 

原来这才是真正驱动OgreFramework的地方啊。。。。。。前面那都是迷惑人的。。。。

 

    new OgreFramework();

    if(!OgreFramework::getSingletonPtr()->initOgre("DemoApp v1.0", &demo, 0))

      return;

    

    demo.setShutdown(false);

    

    OgreFramework::getSingletonPtr()->m_pLog->logMessage("Demo initialized!");

    

    demo.setupDemoScene();

 

在iPhone版本中,在go函数中进行了OgreFramework的创建及初始化,然后调用demo的setupDemoScene进行场景的创建(前面分析的也就这一部分是对的了。。。。。。其他demo部分分析仅适用于其它版本)。

 

    if (mDisplayLinkSupported)

    {

     
// CADisplayLink is API new to iPhone SDK 3.1. Compiling against
earlier versions will result in a warning, but can be dismissed

     
// if the system version runtime check for CADisplayLink exists in
-initWithCoder:. The runtime check ensures this code will

      // not be called in system versions earlier than 3.1.

      mDate = [[NSDate alloc] init];

      mLastFrameTime = -[mDate timeIntervalSinceNow];

      

      mDisplayLink = [NSClassFromString(@"CADisplayLink") displayLinkWithTarget:self selector:@selector(renderOneFrame:)];

      [mDisplayLink setFrameInterval:mLastFrameTime];

      [mDisplayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];

    }

 

可以看到此处是通过iPhone的CADisplayLink类来完成刷新及帧率控制的,(前面还特意检查了版本,以判断当前iPhone版本是否支持此特性,3.1以后才有的东西)而renderOneFrame里面的内容很简单,这里就不讲了。

 

看看CADisplayLink吧,在apple的文档中,有如下描述:

A
CADisplayLink object is a timer object that allows your application to
synchronize its drawing to the refresh rate of the display.

很明显,这个iPhone平台特定的东西估计要比Ogre跨平台的FrameListener要好用一些,所以此基础框架程序中使用了这个iPhone原生的东西。

不过这个代码有个问题:

      mDate = [[NSDate alloc] init];

      mLastFrameTime = -[mDate timeIntervalSinceNow];

      [mDisplayLink setFrameInterval:mLastFrameTime];

 

此时mLastFrameTime一般是个远小于1的变量,而在apple的文档中有如下描述:

frameInterval

The number of frames that must pass before the display link notifies the target again.

@property(nonatomic) NSInteger frameInterval

Discussion

The
default value is 1, which results in your application being notified at
the refresh rate of the display. If the value is set to a value larger
than 1, the display link notifies your application at a fraction of the
native refresh rate. For example, setting the interval to 2 causes the
display link to fire every other frame, providing half the frame rate.

Setting this value to less than 1 results in undefined behavior and is a programmer error.

 


意最后一句。。。。。。设置成小于1的值会导致未定义行为,并且是一个错误。。。。。既然默认是1,那么其实后面的frameInterval根本不用设
置。(原程序能够正常的运行,估计apple还是进行了一定的错误处理,当小于1时还是设置成1了)为了安全起见,这一句还是删掉吧。事实上,删掉后,运
行还是正常,此时使用默认值1,也就是每次显示刷新调用一次此函数。

 

相对的,看Cocos2D for iPhone的代码,现在默认的director类CCDisplayLinkDirector,对DisplayLink的使用实现代码如下:

    int frameInterval = (int) floor(animationInterval_ * 60.0f);

    

    CCLOG(@"cocos2d: Frame interval: %d", frameInterval);

    displayLink = [NSClassFromString(@"CADisplayLink") displayLinkWithTarget:self selector:@selector(preMainLoop:)];

    [displayLink setFrameInterval:frameInterval];

    [displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];

 


意,这里的animationInterval是秒为单位的表示时间间隔的变量,但是这里没有直接使用此变量来设定frameInterval,而是乘以
了60,(Cocos认为iPhone设备的刷新率近似为60)应该来说,这才是正确用法。。。。。。。我看来可以向Ogre的开发组提交个bug
了。。。。。。

 

原创文章作者保留版权 转载请注明原作者 并给出链接

write by 九天雁翎(JTianLing) -- www.jtianling.com

分类:  iOS  图形技术 
标签: 

Posted By 九天雁翎 at 九天雁翎的博客 on 2010年09月18日

前一篇: 从源码编译CEGUI for OGRE 的配置 后一篇: 初学3ds Max,做了个雪人,大家来评评哪个更好看