參考資料:
- ogre1.72 character sample
-
Creating a simple first-person camera system
-
3rd person camera system tutorial -
Make A Character Look At The Camera Using quaternions and SLERP to make a character look at a camera (or any other object for that matter) naturally, with constraints on head movement
I.character sample
本節(jié)是對(duì)ogre 1.72 sample character 的分析,源碼見本節(jié)尾的說明。
1.chase攝像頭的基本對(duì)象
對(duì)象關(guān)系圖
goal作為pivot的子節(jié)點(diǎn),放置在(0 ,0 , 15)處,也就是說goal永遠(yuǎn)會(huì)在pivot的正后方,不離不棄,。pivot就是那美麗的月亮女神,goal是永遠(yuǎn)的追隨者。camNode是獵殺者,只有取代goal的地位(postion,direct)才能贏得月亮女神。任何時(shí)刻camNode都在追逐goal這個(gè)目標(biāo)。這也是chase攝像機(jī)的基本原理。
這里將pivot放置在角色的肩膀處,在幀循環(huán)里同步這個(gè)位置永遠(yuǎn)不變。
2.鼠標(biāo)邏輯
MouseMove事件影響
pitch --- 只影響pivot的pitch。
yaw --- 只影響pivot的yaw。
zoom --- 只影響goal的local postion,決定了goal與pivot的z向距離。goal永遠(yuǎn)在pivot的正后方,也就是只在pivot的z軸上移動(dòng)。
鼠標(biāo)的移動(dòng)只會(huì)造成pivot的yaw和pitch,以及goal的local-z的移動(dòng)。同角色的移動(dòng)是沒有關(guān)系的。
code:
3.幀循環(huán)邏輯
更新角色
取得按鍵方向矢量,根據(jù)這個(gè)矢量設(shè)置角色的positon,direction
更新攝相機(jī)
將永遠(yuǎn)的中心月亮女神pivot放到角色的肩膀上。(女神的圣斗士goal會(huì)永遠(yuǎn)在pivot女神的正后方,,同時(shí)goal的獵殺者camNode也會(huì)死死緊逼)
獵殺者camNode用自己的速度向goal前進(jìn)一步
獵殺者將視線對(duì)準(zhǔn)月亮女神pivot(雖然postion是向goal逼近,但是方向卻向著永遠(yuǎn)的中心月亮女神pivot)
至此chase攝像機(jī)的基本實(shí)現(xiàn)原理水落石出。無非就是女神的圣斗士被獵殺者時(shí)刻緊追,獵殺者死死的盯住女神用目光表示內(nèi)容,用行動(dòng)追逐女神的斗士。
4.角色的移動(dòng)
按鍵事件決定了角色的移動(dòng)方向,用keydirection表示角色在local中的移動(dòng)方向,用goaldirection表示角色在world中的移動(dòng)。在幀循環(huán)中根據(jù)這2個(gè)方向移動(dòng)角色------用角色自己的速度移動(dòng)。
按鍵決定了移動(dòng)方向:
// player's local intended direction based on WASD keys
Vector3 mKeyDirection;
// actual intended direction in world-space
Vector3 mGoalDirection;

void injectKeyDown(const OIS::KeyEvent& evt)


{
// keep track of the player's intended direction
if (evt.key == OIS::KC_W) mKeyDirection.z = -1;
else if (evt.key == OIS::KC_A) mKeyDirection.x = -1;
else if (evt.key == OIS::KC_S) mKeyDirection.z = 1;
else if (evt.key == OIS::KC_D) mKeyDirection.x = 1;
}

void injectKeyUp(const OIS::KeyEvent& evt)


{
// keep track of the player's intended direction
if (evt.key == OIS::KC_W && mKeyDirection.z == -1) mKeyDirection.z = 0;
else if (evt.key == OIS::KC_A && mKeyDirection.x == -1) mKeyDirection.x = 0;
else if (evt.key == OIS::KC_S && mKeyDirection.z == 1) mKeyDirection.z = 0;
else if (evt.key == OIS::KC_D && mKeyDirection.x == 1) mKeyDirection.x = 0;
}
幀循環(huán)中update角色的position和direction:
//! 在世界坐標(biāo)系中,取得角色將要面對(duì)的方向
// calculate actually goal direction in world based on player's key directions
mGoalDirection += mKeyDirection.z * mCameraNode->getOrientation().zAxis();
mGoalDirection += mKeyDirection.x * mCameraNode->getOrientation().xAxis();

mGoalDirection.y = 0;
mGoalDirection.normalise();

if((mKeyDirection != Vector3::ZERO))


{
//! 角色的正前方
Vector3 charFront = mBodyNode->getOrientation().zAxis();
Quaternion toGoal = charFront.getRotationTo(mGoalDirection);

// calculate how much the character has to turn to face goal direction
Real yawToGoal = toGoal.getYaw().valueDegrees();
// this is how much the character CAN turn this frame
Real yawAtSpeed = yawToGoal / Math::Abs(yawToGoal) * deltaTime * TURN_SPEED;
// reduce "turnability" if we're in midair
if (mBaseAnimID == ANIM_JUMP_LOOP) yawAtSpeed *= 0.2f;

//! 限制旋轉(zhuǎn)角度,不要旋轉(zhuǎn)過量
// turn as much as we can, but not more than we need to
if (yawToGoal < 0)

{
yawToGoal = std::min<Real>(0, std::max<Real>(yawToGoal, yawAtSpeed));
//yawToGoal = Math::Clamp<Real>(yawToGoal, yawAtSpeed, 0);
}
else if (yawToGoal > 0)

{
yawToGoal = std::max<Real>(0, std::min<Real>(yawToGoal, yawAtSpeed));
//yawToGoal = Math::Clamp<Real>(yawToGoal, 0, yawAtSpeed);
}
//! 角色yaw操作
mBodyNode->yaw(Degree(yawToGoal));

//! 每次按鍵動(dòng)作,角色都要用當(dāng)前速度往正前方移動(dòng)
// move in current body direction (not the goal direction)
mBodyNode->translate(0, 0, deltaTime * RUN_SPEED * mAnims[mBaseAnimID]->getWeight(),Node::TS_LOCAL);
}

5.各種坐標(biāo)系變換總結(jié)
pivot的平移操作:
幀循環(huán)中,相機(jī)update操作時(shí),將pivot設(shè)置到角色的肩膀處
pivot的yaw、pitch操作:
鼠標(biāo)move事件中,根據(jù)鼠標(biāo)的x、y坐標(biāo)做yaw、pitch操作
goal的平移操作:
鼠標(biāo)move事件中,根據(jù)鼠標(biāo)的z坐標(biāo)進(jìn)行l(wèi)oca-z的平移
goal不會(huì)有l(wèi)ocal旋轉(zhuǎn)操作
相機(jī)的操作:
幀循環(huán),相機(jī)update時(shí),相機(jī)相goal平移逼近,并lookat pivot
角色的平移:
角色的旋轉(zhuǎn),角色只會(huì)有yaw操作。角色從當(dāng)前方向向按鍵和相機(jī)的矢量合成的目標(biāo)方向逼近
角色在方向鍵keydirection不為0的時(shí)候,完成yaw操作后,向當(dāng)前+z方向移動(dòng)
6.修改到第一人稱視角
相機(jī)始終在角色的背后,正對(duì)角色。只需修改代碼中的updateCamera即可:
void SinbadCharacterController::updateCamera(Real deltaTime)


{
// place the camera pivot roughly at the character's shoulder
mCameraPivot->setPosition(mBodyNode->getPosition() + Vector3::UNIT_Y * CAM_HEIGHT);
//! 將pivot對(duì)準(zhǔn)角色的正前方,注意此時(shí)相機(jī)的+Z必須和角色的+Z相反,因?yàn)橄鄼C(jī)時(shí)從+Z看向-Z的
//! 這樣修改后,就完成了一個(gè)第一人稱的相機(jī),和魔獸世界類似
//! W鍵始終讓角色往自身正前方走,而不是相機(jī)的正前方
Vector3 front = mCameraPivot->getOrientation().zAxis();
Vector3 goal = -mBodyNode->getOrientation().zAxis();
Quaternion toGoal = front.getRotationTo(goal);
Real yawToGoal = toGoal.getYaw().valueDegrees();
mCameraPivot->yaw(Degree(yawToGoal) , Node::TS_WORLD );

// move the camera smoothly to the goal
Vector3 goalOffset = mCameraGoal->_getDerivedPosition() - mCameraNode->getPosition();

mCameraNode->translate(goalOffset * deltaTime * 1.0f);
// always look at the pivot
mCameraNode->lookAt(mCameraPivot->_getDerivedPosition(), Node::TS_WORLD);
}只是增加了幾行代碼,讓pivot的front與角色的front在一個(gè)平面,示意圖如下:
7.角色根據(jù)WASD方向與自身方向的合成移動(dòng),而不是與相機(jī)方向合成的移動(dòng)
updateBody中方向合成的代碼
mGoalDirection += mKeyDirection.z * mCameraNode->getOrientation().zAxis();
mGoalDirection += mKeyDirection.x * mCameraNode->getOrientation().xAxis();本來以為將紅色字體處標(biāo)識(shí)符替換為“mBodyNode”即可。運(yùn)行時(shí)發(fā)現(xiàn)方向還算正常,但是角色會(huì)發(fā)生嚴(yán)重的角色抖動(dòng)。不得其解。
本節(jié)完整源碼:
https://3dlearn.googlecode.com/svn/trunk/Samples/Ogre/sinbad此源碼來自ogre 1.72 sample character:
https://bitbucket.org/sinbad/ogre/src/d1f2eab81f08/Samples/Character/