第三人稱(chēng)攝象機(jī)系統(tǒng)例子
Translator:自由騎士篤志(王宏張)
Data:2008-5-14
l 前言
Ogre是一款優(yōu)秀的3D圖形渲染引擎,在國(guó)內(nèi),很多前輩從04年甚至更早就對(duì)它有了深入的了解,并留了許多譯文和心得,極大的便利了我的學(xué)習(xí)。雖然我起步比較晚,但仍希望自己學(xué)習(xí)間的這些記錄和翻譯能夠幫助到他人,如您對(duì)Ogre有所心得和資料,望提供至Ogre中文Wiki http://ogre3d.cn/wiki中大家共享交流。
因能力所限,翻譯和整理難免有錯(cuò)誤之處,歡迎各位指正。
Blog: http:\\hi.baidu.com\freedomknightduzhi
E-mail: duzhi5368@163.com
大部分譯文來(lái)自 http://www.ogre3d.org ,如原文內(nèi)無(wú)說(shuō)明,不再額外查證來(lái)源以及原作者名稱(chēng)。
l 想法來(lái)源
這個(gè)第三人稱(chēng)攝象機(jī)系統(tǒng)的想法來(lái)源于我玩的一款游戲《寂靜嶺2》,在其中有一些圖象上的BUG,我甚至能從其中發(fā)現(xiàn)一些不正常的“線”,如果你也看過(guò),你會(huì)明白我說(shuō)的情況。
一般情況下,該系統(tǒng)中同一時(shí)刻內(nèi)整個(gè)場(chǎng)景中有且僅有一個(gè)攝象機(jī)。和其他的第一/三人稱(chēng)攝象機(jī)系統(tǒng)不同的是,我的這套系統(tǒng)中,攝象機(jī)是完全獨(dú)立與整個(gè)場(chǎng)景的(除根結(jié)點(diǎn)Root以外 --- 它是程序的核心)。
這個(gè)系統(tǒng)能使我們獲得非常優(yōu)秀的特效和軟件環(huán)境,也提供了我們一個(gè)非常友好的方式來(lái)查看整個(gè)場(chǎng)景。
概括來(lái)說(shuō),這個(gè)核心攝象機(jī)ExtendedCamera由
兩個(gè)場(chǎng)景結(jié)點(diǎn)共同組成,它們分別是“攝象機(jī)管理器”和“攝象機(jī)的對(duì)象”。攝象機(jī)管理器用來(lái)保證攝象機(jī)永遠(yuǎn)朝向攝象機(jī)的對(duì)象。如果我們移動(dòng)“對(duì)象”,攝象機(jī)
將會(huì)平移。如果我們移動(dòng)“管理器”,則會(huì)使攝象機(jī)圍繞著“對(duì)象”進(jìn)行旋轉(zhuǎn)。當(dāng)然,我們也可以?xún)烧咭黄鹨苿?dòng)獲得更多的情況。
上面是對(duì)整個(gè)系統(tǒng)的一個(gè)簡(jiǎn)單功能的說(shuō)明。我們還可以為攝象機(jī)增加一些震動(dòng)特效,也可以通過(guò)虛擬攝象機(jī)實(shí)現(xiàn)一些電影效果等等。
下面,我將講述如何在一個(gè)Demo中實(shí)現(xiàn)三種攝象機(jī)模式。
l 可旋轉(zhuǎn)第三人稱(chēng)攝象機(jī)
首先,我們有一個(gè)主角,它有三重性質(zhì)。首先,它本身是一個(gè)重要的場(chǎng)景結(jié)點(diǎn)(主角),又是一個(gè)可見(jiàn)的結(jié)點(diǎn)(我們的攝象機(jī)的目標(biāo)結(jié)點(diǎn)),還是一個(gè)可旋轉(zhuǎn)的攝象機(jī)結(jié)點(diǎn)(主角的眼睛本身可以進(jìn)行旋轉(zhuǎn)和觀察)。還有很多方法可以實(shí)現(xiàn)同樣的功能,但是我認(rèn)為這種方法比較簡(jiǎn)單。
我們接下來(lái)要做的就是,使它本身“可見(jiàn)的結(jié)點(diǎn)”這一特性結(jié)點(diǎn)做為我們“攝象機(jī)的目標(biāo)”,而“可旋轉(zhuǎn)的攝象機(jī)結(jié)點(diǎn)”這一特性結(jié)點(diǎn)做為攝象機(jī)本身。
“可見(jiàn)的結(jié)點(diǎn)”意味著主角大部分時(shí)間應(yīng)該處于屏幕的中心,但當(dāng)主角處于“調(diào)查”狀態(tài)時(shí),它自身就變成了一個(gè)攝象機(jī),這時(shí)它能夠獲得更寬闊的視野。
(譯者:還不理解的朋友可以玩玩單機(jī)的《零》系列或射擊類(lèi)游戲。通常為第三人稱(chēng),一旦開(kāi)啟瞄準(zhǔn)鏡時(shí),就變成了第一人稱(chēng),即作者所說(shuō)的“主角成為了一個(gè)攝象機(jī)結(jié)點(diǎn)”。)
l 綁定的第三人稱(chēng)攝象機(jī)
這種類(lèi)型的攝象機(jī)非常常見(jiàn),例如《寂靜嶺》……(譯者:- -看來(lái)作者是SH的粉絲……)這種攝象機(jī)意味著攝象機(jī)永遠(yuǎn)鎖定主角,但僅鎖定一個(gè)方面。(譯者:簡(jiǎn)單來(lái)說(shuō)即永遠(yuǎn)無(wú)法旋轉(zhuǎn)攝象機(jī)看到主角的正面。)
這種類(lèi)型的攝象機(jī)與可旋轉(zhuǎn)的第三人稱(chēng)攝象機(jī)類(lèi)似。
l 第一人稱(chēng)攝象機(jī)
我們使用主角的位置作為攝象機(jī)的位置。在該模式下,我們將攝象機(jī)的“目標(biāo)結(jié)點(diǎn)”設(shè)置為空,同時(shí)也將以主角結(jié)點(diǎn)為目標(biāo)的攝象機(jī)隱藏起來(lái)。
以上,就是三種攝象機(jī)的區(qū)別。
l 需要緊記的一些事情
l 調(diào)整系統(tǒng)
如果攝象機(jī)受到場(chǎng)景中的一些對(duì)象影響,我們則需要對(duì)攝象機(jī)進(jìn)行一些調(diào)整。
l 攝象機(jī)移動(dòng)
攝象機(jī)以及攝象機(jī)目標(biāo)點(diǎn)可以這樣移動(dòng):我們計(jì)算攝象機(jī)坐標(biāo)和目標(biāo)點(diǎn)之間的差距,最終得到一個(gè)矩陣,讓它作用于攝象機(jī)使其移動(dòng)至目標(biāo)點(diǎn)。
l 緊密器
按照我們上面說(shuō)的移動(dòng)方式,我們會(huì)感覺(jué)到攝象機(jī)的移動(dòng)很生硬,不夠平滑。所以這個(gè)“移動(dòng)緊密器”的概念就出來(lái)了。這個(gè)緊密器因素是一個(gè)0,0 – 1.0之間的值,我們?cè)谟?jì)算出攝象機(jī)的移動(dòng)轉(zhuǎn)換矩陣時(shí),用它來(lái)進(jìn)行影響。
1:若該因素為1.0,則表明當(dāng)目標(biāo)單元移動(dòng),攝象機(jī)同樣移動(dòng)。
2:若該因素為0.0,則當(dāng)目標(biāo)單元移動(dòng)時(shí),攝象機(jī)完全不動(dòng)。
3:該因素在0.0 – 1.0之間時(shí),將會(huì)對(duì)攝象機(jī)的變換矩陣做出影響。
l 源代碼以及說(shuō)明
你可以從http://www.ugr.es/local/agsh/ogre/ExtendedCameraSample.zip這里下載Alberts的這套代碼。
首先,我們包含OGRE的例子框架文件頭。
// 攝象機(jī)系統(tǒng)例子 By Kencho
#include “ExampleApplication.h”
接下來(lái),我們定義一個(gè)角色類(lèi)。這個(gè)類(lèi)中將定義一個(gè)對(duì)象所處的多種特性屬性(即是攝象機(jī)又是攝象機(jī)的目標(biāo)對(duì)象)。當(dāng)然,在一個(gè)游戲中,你需要為角色類(lèi)定義更多的屬性。 :)
// 普通角色類(lèi)
Class Charater
{
Protected:
SceneNode *mMainNode; // 主角結(jié)點(diǎn)
SceneNode *mSightNode; // 可見(jiàn)結(jié)點(diǎn),主角應(yīng)當(dāng)一直看著這里
SceneNode *mCameraNode; // 鎖定攝象機(jī)結(jié)點(diǎn),該結(jié)點(diǎn)圍繞主角移動(dòng)和旋轉(zhuǎn)
Entity *mEntity; // 角色實(shí)體
SceneManager *mSceneMgr;
Public:
// 更新主角狀態(tài)(如移動(dòng)等……)
Virtual void update (Real elapsedTime, InputReader *input) = 0;
// 下面兩個(gè)方法返回?cái)z象機(jī)結(jié)點(diǎn)指針
SceneNode *getSightNode(){ return mSightNode ;}
SceneNode *getCameraNode(){ return mCameraNode; }
// 返回主角當(dāng)前坐標(biāo)(第一人稱(chēng)攝象機(jī)時(shí)需要)
Vector3 getWorldPosition(){ return mMainNode->getWorldPosition(); }
};
接下來(lái),我們對(duì)角色類(lèi)特化實(shí)現(xiàn)一下。我認(rèn)為在Demo弄一個(gè)漂浮的食人魔腦袋做主角挺不錯(cuò)。所以我們寫(xiě)一個(gè)實(shí)例化的類(lèi)。
// 特殊的實(shí)例化角色類(lèi) – 我們可愛(ài)的Ogre
Class OgreCharater : public Character
{
Protected:
String mName;
Public:
OgreCharater(String name, SceneManager *sceneMgr)
{
// 保存類(lèi)成員變量
mName = name;
mSceneMgr = sceneMgr;
// 創(chuàng)建節(jié)點(diǎn)用來(lái)存放綁定第三人稱(chēng)的攝象機(jī)
mMainNode = mSceneMgr->getRootSceneNode()->createChildSceneNode(mName);
mSightNode = mMainNode-> createChildSceneNode(mName + “_sight”, Vector3(0, 0, 100));
mCameraNode = mMainNode-> createChildSceneNode(mName + “_camera”, Vector3(0, 50, -100));
// 為角色綁定一個(gè)實(shí)體
mEntity = mSceneMgr->createEntity(mName, “OgreHead.mesh”);
mMainNode->attachObject(mEntity);
}
Void update(Real elapsedTime, InputReader *input)
{
// 移動(dòng)
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));;
}
}
// 設(shè)置自身是否可見(jiàn),這對(duì)于第一人稱(chēng)視角很有用。
Void setVisible(bool visible)
{
mMainNode->setVisible(visible);
}
};
為了保證代碼簡(jiǎn)單,我這里不再寫(xiě)一些關(guān)于主角的模型動(dòng)畫(huà),控制等函數(shù)。
現(xiàn)在,進(jìn)入有趣的部分:擴(kuò)展的攝象機(jī)類(lèi)。它就如我之前所說(shuō)明的思路一樣去設(shè)計(jì)的,所以,如果你對(duì)本部分代碼有什么疑問(wèn),你可以看一下先前我的講述。
// 我們的擴(kuò)展攝象機(jī)類(lèi)
Class ExtendedCamera
{
Protected:
SceneNode *mTargetNode; // 攝象機(jī)目標(biāo)結(jié)點(diǎn)
SceneNode *mCameraNode; // 攝象機(jī)自身結(jié)點(diǎn)
Camera *mCamera; // Ogre攝象機(jī)
SceneManager *mSceneMgr;
String mName;
Bool mOwnCamera; // 判斷是否是本類(lèi)創(chuàng)建的攝象機(jī),或是類(lèi)外傳入的攝象機(jī)
Real mTightness; // 攝象機(jī)捆綁緊密度。1.0表示攝象機(jī)與目標(biāo)的移動(dòng)保持完全一致。0.0表示攝象機(jī)不移動(dòng),完全不受目標(biāo)移動(dòng)的影響。
Public:
ExtendedCamera(String name, SceneManager *sceneMgr, Camera *camera = 0)
{
mName = name;
mSceneMgr = sceneMgr;
// 創(chuàng)建攝象機(jī)結(jié)點(diǎn)結(jié)構(gòu)
mCamreaNode = mSceneMgr->getRootSceneNode()->createChildSceneNode(mName);
mTargetNode = mSceneMgr->getRootSceneNode()->createChildSceneNode(mName + “_target”);
// 攝象機(jī)永遠(yuǎn)朝向目標(biāo)結(jié)點(diǎn)
mCameraNode->setAutoTracking(true, mTargetNode);
// 因?yàn)樾枰詣?dòng)跟蹤,所以該項(xiàng)要設(shè)為true
mCameraNode->setFixedYawAxis(true);
// 假如沒(méi)有從參數(shù)獲得一個(gè)攝象機(jī),我們則自己創(chuàng)建
If (camera == 0)
{
mCamera = mSceneMgr->createCamera(mName);
mOwnCamera = true;
}else
{
mCamera = camera;
mOwnCamera = false;
}
// 為攝象機(jī)結(jié)點(diǎn)綁定一個(gè)Ogre攝象機(jī)
mCameraNode->attachObject(mCamera);
// 默認(rèn)的攝象機(jī)捆綁緊密系數(shù)
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)
{
// 管理移動(dòng)
Vector3 displacement;
Displacement = (cameraPosition – mCameraNode->getPosition()) * mTightness;
mCameraNode->translate( displacement );
Displacement = (targetPosition – mTargetNode->getPosition()) * mTightness;
mTargetNode ->translate( displacement );
}
};
在每一楨的楨監(jiān)聽(tīng)中,更變攝象機(jī),角色和攝象機(jī)模式。
Class SampleListener : public ExampleFrameListener
{
Protected:
Character *mChar;
ExtendedCamera *mExCamera;
Unsigned int mMode; // 攝象機(jī)模式,現(xiàn)在支持第一人稱(chēng),綁定的第三人稱(chēng)攝象機(jī)和動(dòng)態(tài)捕捉第三人稱(chēng)攝象機(jī)
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)
{
// 動(dòng)態(tài)捕捉型第三人稱(chēng)攝象機(jī)
case 0:
mExCamera->update(evt.timeSinceLastFrame,
mChar->getCameraNode()->getWorldPosition(),
mChar->getSightNode()->getWorldPosition());
break;
// 綁定的第三人稱(chēng)攝象機(jī)
case 1:
mExCamera->update(evt.timeSinceLastFrame, Vector3(0, 200, 0), mChar->getSightNode()->getWorldPosition());
break;
// 第一人稱(chēng)攝象機(jī)
case 2:
mExCamera->update(evt.timeSinceLastFrame, mChar->getWorldPosition(), mChar->getSightNode()->getWorldPosition());
break;
}
}
}
// F1鍵切換為動(dòng)態(tài)捕捉型第三人稱(chēng)攝象機(jī)
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切換為綁定的第三人稱(chēng)攝象機(jī)
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鍵切換為第一人稱(chēng)攝象機(jī)
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;
}
};
下面是一個(gè)App程序的樣本,如果你不理解這段代碼,你可以看一下其他的Ogre文章。
Class SampleApplication : public ExampleApplication
{
Public:
SampleApplication(){}
~ SampleApplication(){}
Protected:
// 創(chuàng)建場(chǎng)景
Void createScene(void)
{
// 設(shè)置環(huán)境光
mSceneMgr->setAmbientLight(ColourValue(0.2, 0.2, 0.2));
// 創(chuàng)建一個(gè)點(diǎn)光源
Light* l = mSceneMgr->createLight(“MainLight”);
// 這個(gè)點(diǎn)光源使用默認(rèn)設(shè)置
l->setType(Light::LT_DIRECTIONAL);
l->setDirection(-0.5, -0.5, 0);
// 攝象機(jī)位置
mCamera->setPosition(0, 0, 0);
// 為場(chǎng)景添加一些實(shí)體對(duì)象
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);
// 楨監(jiān)聽(tīng)器用來(lái)管理主角和攝象機(jī)的更新和不同攝象機(jī)模式間的切換
mFrameListener = new SampleListener(mWindow, mCamera);
static_cast<SampleListener*>( mFrameListener)->setCharacter(ogre);
static_cast<SampleListener*>( mFrameListener)->setExtendedCamera(exCamera);
}
Void destroyScene(void){}
Void createFrameListener(void)
{
// 放棄實(shí)例化我們自己的楨監(jiān)聽(tīng)器
// mFrameListener = new SampleListener(mWindow, mCamera);
mRoot->addFrameListener(mFrameListener);
}
};
注意:在createFrameListener()函數(shù)中,我們并沒(méi)有構(gòu)造創(chuàng)建楨監(jiān)聽(tīng)器,所以不會(huì)有兩個(gè)監(jiān)聽(tīng)器。這是非常重要的,若我們創(chuàng)建兩個(gè)楨監(jiān)聽(tīng)器,這個(gè)程序?qū)?huì)當(dāng)機(jī)。
#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
{
// 創(chuàng)建APP實(shí)例
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;
}
l 結(jié)束語(yǔ)
希望這篇文章對(duì)你有用處,我會(huì)之后再更新它來(lái)進(jìn)行更多的解釋說(shuō)明。感謝您的閱讀。
Kencho