天天看點

Ogre第三人稱攝象機系統

Ogre第三人稱攝象機系統

前言

Ogre是一款優秀的3D圖形渲染引擎,在國内,很多前輩從04年甚至更早就對它有了深入的了解,并留了許多譯文和心得,極大的便利了我的學習。雖然我起步比較晚,但仍希望自己學習間的這些記錄和翻譯能夠幫助到他人,如您對Ogre有所心得和資料,望提供至Ogre中文Wiki中大家共享交流。因能力所限,翻譯和整理難免有錯誤之處,歡迎各位指正。大部分譯文來自Ogre官方,如原文内無說明,不再額外查證來源以及原作者名稱。

先睹為快

想直接看看效果嗎?這裡有完整代碼,是通過調試的^_^。--Xiao7cn 16:34 2008年5月21日 (CST)FriedChicken(xiao7cn) QQ:12507561

想法來源

這個第三人稱攝象機系統的想法來源于我玩的一款遊戲《寂靜嶺2》,在其中有一些圖象上的BUG,我甚至能從其中發現一些不正常的“線”,如果你也看過,你會明白我說的情況。一般情況下,該系統中同一時刻内整個場景中有且僅有一個攝象機。和其他的第一/三人稱攝象機系統不同的是,我的這套系統中,攝象機是完全獨立與整個場景的(除根結點Root以外 --- 它是程式的核心)。這個系統能使我們獲得非常優秀的特效和軟體環境,也提供了我們一個非常友好的方式來檢視整個場景。概括來說,這個核心攝象機ExtendedCamera由兩個場景結點共同組成,它們分别是“攝象機管理器”和“攝象機的對象”。攝象機管理器用來保證攝象機永遠朝向攝象機的對象。如果我們移動“對象”,攝象機将會平移。如果我們移動“管理器”,則會使攝象機圍繞着“對象”進行旋轉。當然,我們也可以兩者一起移動獲得更多的情況。上面是對整個系統的一個簡單功能的說明。我們還可以為攝象機增加一些震動特效,也可以通過虛拟攝象機實作一些電影效果等等。下面,我将講述如何在一個Demo中實作三種攝象機模式。

自動動态捕捉第三人稱攝象機

首先,我們有一個主角,它有三重性質。首先,它本身是一個重要的場景結點(主角),又是一個可見的結點(我們的攝象機的目标結點),還是一個可旋轉的攝象機結點(主角的眼睛本身可以進行旋轉和觀察)。還有很多方法可以實作同樣的功能,但是我認為這種方法比較簡單。我們接下來要做的就是,使它本身“可見的結點”這一特性結點做為我們“攝象機的目标”,而“可旋轉的攝象機結點”這一特性結點做為攝象機本身。 “可見的結點”意味着主角大部分時間應該處于螢幕的中心,但當主角處于“調查”狀态時,它自身就變成了一個攝象機,這時它能夠獲得更寬闊的視野。 (譯者:還不了解的朋友可以玩玩單機的《零》系列或射擊類遊戲。通常為第三人稱,一旦開啟瞄準鏡時,就變成了第一人稱,即作者所說的“主角成為了一個攝象機結點”。)

綁定的第三人稱攝象機

這種類型的攝象機非常常見,例如《寂靜嶺》……(譯者:- -看來作者是SH的粉絲……)這種攝象機意味着攝象機永遠鎖定主角,但僅鎖定一個方面。(譯者:簡單來說即永遠無法旋轉攝象機看到主角的正面。)這種類型的攝象機與可旋轉的第三人稱攝象機類似。

第一人稱攝象機

我們使用主角的位置作為攝象機的位置。在該模式下,我們将攝象機的“目标結點”設定為空,同時也将以主角結點為目标的攝象機隐藏起來。

• 以上,就是三種攝象機的差別

需要緊記的一些事

調整系統

如果攝象機受到場景中的一些對象影響,我們則需要對攝象機進行一些調整。

攝象機移動

攝象機以及攝象機目标點可以這樣移動:我們計算攝象機坐标和目标點之間的差距,最終得到一個矩陣,讓它作用于攝象機使其移動至目标點。

緊密器

按照我們上面說的移動方式,我們會感覺到攝象機的移動很生硬,不夠平滑。是以這個“移動緊密器”的概念就出來了。這個緊密器因素是一個0,0 – 1.0之間的值,我們在計算出攝象機的移動轉換矩陣時,用它來進行影響。

• 1:若該因素為1.0,則表明當目标單元移動,攝象機同樣移動。

• 2:若該因素為0.0,則當目标單元移動時,攝象機完全不動。

• 3:該因素在0.0 – 1.0之間時,将會對攝象機的變換矩陣做出影響。

源代碼以及說明

• 你可以從這裡下載下傳該套代碼(這是C#版本的).

我整理了一個C++版的,可以直接編譯運作,這裡。

首先,我們包含OGRE的例子架構檔案頭。

// 攝象機系統例子 By Kencho

#include “ExampleApplication.h”

接下來,我們定義一個角色類。這個類中将定義一個對象所處的多種特性屬性(即是攝象機又是攝象機的目标對象)。當然,在一個遊戲中,你需要為角色類定義更多的屬性。 :)

// 普通角色類

Class Charater

{

Protected:

    SceneNode *mMainNode;      // 主角結點

     SceneNode *mSightNode;      // 可見結點,主角應當一直看着這裡

     SceneNode *mCameraNode;  // 鎖定攝象機結點,該結點圍繞主角移動和旋轉

     Entity *mEntity;                   // 角色實體

     SceneManager *mSceneMgr;

Public:

    // 更新主角狀态(如移動等……)

     Virtual void update (Real elapsedTime, InputReader *input) = 0;

    // 下面兩個方法傳回攝象機結點指針

     SceneNode *getSightNode(){ return mSightNode ;}

    SceneNode *getCameraNode(){ return mCameraNode; }

    // 傳回主角目前坐标(第一人稱攝象機時需要)

     Vector3 getWorldPosition(){ return mMainNode->getWorldPosition(); }

};

接下來,我們對角色類特化實作一下。我認為在Demo弄一個漂浮的食人魔腦袋做主角挺不錯。是以我們寫一個執行個體化的類。

// 特殊的執行個體化角色類 – 我們可愛的Ogre

Class OgreCharater : public Character

{

Protected:

     String mName;

Public:

     OgreCharater(String name, SceneManager *sceneMgr)

     {

          // 儲存類成員變量

           mName = name;

         mSceneMgr = sceneMgr;

         // 建立節點用來存放綁定第三人稱的攝象機

           mMainNode = mSceneMgr->getRootSceneNode()->createChildSceneNode(mName);

         mSightNode = mMainNode-> createChildSceneNode(mName + “_sight”, Vector3(0, 0, 100));

         mCameraNode = mMainNode-> createChildSceneNode(mName + “_camera”, Vector3(0, 50, -100));

         // 為角色綁定一個實體

          mEntity = mSceneMgr->createEntity(mName, “OgreHead.mesh”);

        mMainNode->attachObject(mEntity);

      }

Void update(Real elapsedTime, InputReader *input)

      {

           // 移動

          if(input->isKeyDown(KC_W))

         {

              mMainNode->translate(mMainNode->getOrientation() * Vector3(0, 0, 100 * elapsedTime));

         }

         if(input->isKeyDown(KC_S))

         {

              mMainNode->translate(mMainNode->getOrientation() * Vector3(0, 0, -50 * elapsedTime));

         }

         if(input->isKeyDown(KC_A))

         {

              mMainNode->yaw(Radian(2 * elapsedTime));;

         }

         If (input->isKeyDown(KC_D))

         {

              mMainNode->yaw(Radian(-2 * elapsedTime));;

         }

    }

// 設定自身是否可見,這對于第一人稱視角很有用。

    Void setVisible(bool visible)

    {

         mMainNode->setVisible(visible);

    }

};

為了保證代碼簡單,我這裡不再寫一些關于主角的模型動畫,控制等函數。現在,進入有趣的部分:擴充的攝象機類。它就如我之前所說明的思路一樣去設計的,是以,如果你對本部分代碼有什麼疑問,你可以看一下先前我的講述。

// 我們的擴充攝象機類

Class ExtendedCamera

{

Protected:

    SceneNode *mTargetNode;    // 攝象機目标結點

     SceneNode *mCameraNode;  // 攝象機自身結點

     Camera *mCamera;              // Ogre攝象機

     SceneManager *mSceneMgr;

    String mName;

    Bool mOwnCamera;             // 判斷是否是本類建立的攝象機,或是類外傳入的攝象機

     Real mTightness;                  // 攝象機捆綁緊密度。1.0表示攝象機與目标的移動保持完全一緻。0.0表示攝象機不移動,完全不受目标移動的影響。

Public:

    ExtendedCamera(String name, SceneManager *sceneMgr, Camera *camera = 0)

    {

         mName = name;

         mSceneMgr = sceneMgr;

         // 建立攝象機結點結構

         mCamreaNode = mSceneMgr->getRootSceneNode()->createChildSceneNode(mName);

        mTargetNode = mSceneMgr->getRootSceneNode()->createChildSceneNode(mName + “_target”);

         // 攝象機永遠朝向目标結點

          mCameraNode->setAutoTracking(true, mTargetNode);

         // 因為需要自動跟蹤,是以該項要設為true

         mCameraNode->setFixedYawAxis(true);

         // 假如沒有從參數獲得一個攝象機,我們則自己建立

         if (camera == 0)

        {

              mCamera = mSceneMgr->createCamera(mName);

              mOwnCamera = true;

        }else

        {

              mCamera = camera;

              mOwnCamera = false;

        }

        // 為攝象機結點綁定一個Ogre攝象機

         mCameraNode->attachObject(mCamera);

        // 預設的攝象機捆綁緊密系數

         mTightness = 0.01f;

    }

~ExtendedCamera()

    {

         mCameraNode->detachAllObjects();

         if (mOwnCamera)

              delete mCamera;

         mSceneMgr->destroySceneNode(mName);

         mSceneMgr-> destroySceneNode(mName + “_target”);

    }

Void setTightness(Real tightness)

    {

         mTightness = tightness;

    }

Real getTightness()

    {

         Return mTightness;

    }

Vector3 getCameraPosition()

    {

         Return mCameraNode->getPosition();

    }

Void instantUpdate(Vertor3 cameraPosition, Vector3 targetPosition)

    {

         mCameraNode->setPosition(cameraPosition);

         mTargetNode->setPosition(targetPosition);

    }

Void update(Real elapsedTime, Vector3 cameraPosition, Vector3 targetPositon)

    {

         // 管理移動

          Vector3 displacement;

         Displacement = (cameraPosition – mCameraNode->getPosition()) * mTightness;

         mCameraNode->translate( displacement );

         Displacement = (targetPosition – mTargetNode->getPosition()) * mTightness;

         mTargetNode ->translate( displacement );   

    }

};

在每一桢的桢監聽中,更變攝象機,角色和攝象機模式。

Class SampleListener : public ExampleFrameListener

{

Protected:

    Character *mChar;

    ExtendedCamera *mExCamera;

    Unsigned int mMode;    // 攝象機模式,現在支援第一人稱,綁定的第三人稱攝象機和動态捕捉第三人稱攝象機

Public:

    SampleListener(RenderWindow *win, Camera *cam) : ExampleFrameListener(win, cam)

    {

         mChar = 0;

         mExCamera = 0;

         mMode = 0;

    }

Void setCharacter(Character * character)

    {

         mChar = character;

    }

Void setExtendedCamera(ExtendedCamera *cam)

    {

         mExCamera = cam;

    }

Bool frameStarted( const FrameEvent& evt )

    {

         mInputDevice->capture();

         if (mChar)

         {

              mChar->update(evt.timeSinceLastFrame, mInputDevice);

              if (mExCamera)

              {

                   Switch(mMode)

                   {

                   // 動态捕捉型第三人稱攝象機

                      case 0:

                   mExCamera->update(evt.timeSinceLastFrame, mChar->getCameraNode()->getWorldPosition(), mChar->getSightNode()->getWorldPosition());

                   break;

                   // 綁定的第三人稱攝象機

                      case 1:

                   mExCamera->update(evt.timeSinceLastFrame, Vector3(0, 200, 0), mChar->getSightNode()->getWorldPosition());

                   break;

                   // 第一人稱攝象機

                      case 2:

                   mExCamera->update(evt.timeSinceLastFrame, mChar->getWorldPosition(), mChar->getSightNode()->getWorldPosition());

                   break;

                   }

              }

         }

         // F1鍵切換為動态捕捉型第三人稱攝象機

         if (mInputDevice->isKeyDown(KC_F1))

        {

              mMode = 0;

              if (mChar)

                   static_cast<OgreCharacter* >(mChar)->setVisible(true);

              if (mExCamera)

              {

                   If (mChar)

                   {

                        mExCamera->instantUpdate(mChar->getCameraNode()->getWorldPosition(), mChar->getSightNode()->getWorldPosition());

                   }     

                   mExCamera->setTightness(0.01f);      

              }

         }

         //  F2切換為綁定的第三人稱攝象機

         If( mInputDevice->isKeyDown(KC_F2))

        {

              mMode = 1;

              if (mChar)

                   static_cast<OgreCharacter*>(mChar)->setVisible(true);

              if (mExCamera)

              {

                   If (mChar)

                   mExCamera->instanceUpdate(Vector3(0, 200, 0), mChar->getSightNode()->getWorldPosition());

                   mExCamera->setTightness(0.01f);

              }

         }

         // F3鍵切換為第一人稱攝象機

         if (mInputDevice->isKeyDown(KC_F3))

        {

              mMode = 2;

              if (mChar)

                   static_cast<OgreCharacter*>(mChar)->setVisible(true);

              if(mExCamera)

              {

                   If (mChar)

                   {

                        mExCamera->instantUpdate(mChar->getWorldPosition(), mChar->getSightNode()->getWorldPosition());

                   }     

                   mExCamera->setTightness(1.0f); 

              }

         }

         // 若按下ESC就退出

          if(mInputDevice->isKeyDown(KC_ESCAPE))

              return false;

return true;

    }

};

下面是一個App程式的樣本,如果你不了解這段代碼,你可以看一下其他的Ogre文章。

Class SampleApplication : public ExampleApplication

{

Public:

    SampleApplication(){}

    ~SampleApplication(){}

Protected:

    // 建立場景

     Void createScene(void)

    {

         // 設定環境光

          mSceneMgr->setAmbientLight(ColourValue(0.2, 0.2, 0.2));

         // 建立一個點光源

          Light* l = mSceneMgr->createLight(“MainLight”);

         // 這個點光源使用預設設定

          l->setType(Light::LT_DIRECTIONAL);

         l->setDirection(-0.5, -0.5, 0);

         // 攝象機位置

          mCamera->setPosition(0, 0, 0);

         // 為場景添加一些實體對象

          SceneNode* razorNode;

         Entity* razorEntity;

         For (unsigned int i = 0; i< 30; ++i)

         {

              razorNode = mSceneMgr->getRootSceneNode()->createChildSceneNode(StringConverter::toString(i), Vector3(Mesh::RangeRandom(-1000, 1000), 0, Math::RangeRandom(-1000, 1000)));

              razorEntity = mSceneMgr->createEntity(StringConverter::toString(i), “razor.mesh”);

              rezorNode->attachObject(razorEntity);

         }

         // 主角

          OgreCharacter *ogre = new OgreCharacter(“Ogre 1”, mSceneMgr);

         ExtendedCamera *exCamera = new ExtendedCamera(“Extended Camera”, mSceneMgr, mCamera);

         // 桢監聽器用來管理主角和攝象機的更新和不同攝象機模式間的切換

          mFrameListener = new SampleListener(mWindow, mCamera);

         static_cast<SampleListener*>( mFrameListener)->setCharacter(ogre);

         static_cast<SampleListener*>( mFrameListener)->setExtendedCamera(exCamera);

    }

Void destroyScene(void){}

Void createFrameListener(void)

    {

        // 放棄執行個體化我們自己的桢監聽器

         // mFrameListener = new SampleListener(mWindow, mCamera);

        mRoot->addFrameListener(mFrameListener);

    }

};

注意:在createFrameListener()函數中,我們并沒有構造建立桢監聽器,是以不會有兩個監聽器。這是非常重要的,若我們建立兩個桢監聽器,這個程式将會當機。

#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32

#define WIN32_LEAN_AND_MEAN

#include “windows,h”

INT WINAPI WinMain(HINSTANCE hInst, HINSTANCE, LPSTR strCmdLine, INT)

#else

Int main(int argc, char** argv)

#endif

{

    // 建立APP執行個體

    SampleApplication app;

     try

     { app.go(); }

     catch( Exception & e)

     {

         #if OGRE_PLATFORM == OGRE_PLATFORM_WIN32

         MessageBox( NULL, e.getFullDescription().c_str(), “An Exception has occurred!”, MB_OK | MB_ICONERROR );

         #else

         fprintf(stderr, “An Exception has occurred: %s/n”, e.getFullDescription().c_str());

         #endif

     }

     return 0;

}

結束語

希望這篇文章對你有用處,我會之後再更新它來進行更多的解釋說明。感謝您的閱讀。