的笔记

随时随地编辑

Chase camera笔记

参考资料:
 - 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

本节是对ogre 1.72 sample character 的分析,源码见本节尾的说明。

1.chase摄像头的基本对象

对象关系图
   
     
      goal作为pivot的子节点,放置在(0 ,0 , 15)处,也就是说goal永远会在pivot的正后方,不离不弃,。pivot就是那美丽的月亮女神,goal是永远的追随者。camNode是猎杀者,只有取代goal的地位(postion,direct)才能赢得月亮女神。任何时刻camNode都在追逐goal这个目标。这也是chase摄像机的基本原理。
      这里将pivot放置在角色的肩膀处,在帧循环里同步这个位置永远不变。


2.鼠标逻辑

MouseMove事件影响
pitch ---  只影响pivot的pitch。
yaw ---    只影响pivot的yaw。
zoom --- 只影响goal的local postion,决定了goal与pivot的z向距离。goal永远在pivot的正后方,也就是只在pivot的z轴上移动。

      鼠标的移动只会造成pivot的yaw和pitch,以及goal的local-z的移动。同角色的移动是没有关系的。
code:

void injectMouseMove(const OIS::MouseEvent& evt)
{
    
// update camera goal based on mouse movement
    updateCameraGoal(-0.05f * evt.state.X.rel, -0.05f * evt.state.Y.rel, -0.0005f * evt.state.Z.rel);
}


    
void updateCameraGoal(Real deltaYaw, Real deltaPitch, Real deltaZoom)
{
    mCameraPivot
->yaw(Degree(deltaYaw), Node::TS_WORLD);

    
// bound the pitch
    if (!(mPivotPitch + deltaPitch > 25 && deltaPitch > 0&&
        
!(mPivotPitch + deltaPitch < -60 && deltaPitch < 0))
    
{
        mCameraPivot
->pitch(Degree(deltaPitch), Node::TS_LOCAL);
        mPivotPitch 
+= deltaPitch;
    }

    
    Real dist 
= mCameraGoal->_getDerivedPosition().distance(mCameraPivot->_getDerivedPosition());
    Real distChange 
= deltaZoom * dist;

    
// bound the zoom
    if (!(dist + distChange < 8 && distChange < 0&&
        
!(dist + distChange > 25 && distChange > 0))
    
{
        mCameraGoal
->translate(00, distChange, Node::TS_LOCAL);
    }

}
      鼠标移动最终的结果是改变了goal在world中的position和direction,这个作为camera在帧循环中处理的唯一依据。

3.帧循环逻辑

更新角色
    取得按键方向矢量,根据这个矢量设置角色的positon,direction
更新摄相机
    将永远的中心月亮女神pivot放到角色的肩膀上。(女神的圣斗士goal会永远在pivot女神的正后方,,同时goal的猎杀者camNode也会死死紧逼)
    猎杀者camNode用自己的速度向goal前进一步
    猎杀者将视线对准月亮女神pivot(虽然postion是向goal逼近,但是方向却向着永远的中心月亮女神pivot)

至此chase摄像机的基本实现原理水落石出。无非就是女神的圣斗士被猎杀者时刻紧追,猎杀者死死的盯住女神用目光表示内容,用行动追逐女神的斗士。

4.角色的移动

      按键事件决定了角色的移动方向,用keydirection表示角色在local中的移动方向,用goaldirection表示角色在world中的移动。在帧循环中根据这2个方向移动角色------用角色自己的速度移动。

按键决定了移动方向:
// 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;
}

    帧循环中update角色的position和direction:

//! 在世界坐标系中,取得角色将要面对的方向
// 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;

    
//! 限制旋转角度,不要旋转过量
    
// 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));

    
//! 每次按键动作,角色都要用当前速度往正前方移动
    
// move in current body direction (not the goal direction)
    mBodyNode->translate(00, deltaTime * RUN_SPEED * mAnims[mBaseAnimID]->getWeight(),Node::TS_LOCAL);
}

 


5.各种坐标系变换总结

pivot的平移操作:
帧循环中,相机update操作时,将pivot设置到角色的肩膀处
pivot的yaw、pitch操作:
鼠标move事件中,根据鼠标的x、y坐标做yaw、pitch操作

goal的平移操作:
鼠标move事件中,根据鼠标的z坐标进行loca-z的平移
goal不会有local旋转操作

相机的操作:
帧循环,相机update时,相机相goal平移逼近,并lookat pivot

角色的平移:
角色的旋转,角色只会有yaw操作。角色从当前方向向按键和相机的矢量合成的目标方向逼近
角色在方向键keydirection不为0的时候,完成yaw操作后,向当前+z方向移动

6.修改到第一人称视角

相机始终在角色的背后,正对角色。只需修改代码中的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对准角色的正前方,注意此时相机的+Z必须和角色的+Z相反,因为相机时从+Z看向-Z的
    
//! 这样修改后,就完成了一个第一人称的相机,和魔兽世界类似
    
//! W键始终让角色往自身正前方走,而不是相机的正前方
    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在一个平面,示意图如下:

7.角色根据WASD方向与自身方向的合成移动,而不是与相机方向合成的移动

updateBody中方向合成的代码
mGoalDirection += mKeyDirection.z * mCameraNode->getOrientation().zAxis();
mGoalDirection 
+= mKeyDirection.x * mCameraNode->getOrientation().xAxis();

本来以为将红色字体处标识符替换为“mBodyNode”即可。运行时发现方向还算正常,但是角色会发生严重的角色抖动。不得其解。

本节完整源码:https://3dlearn.googlecode.com/svn/trunk/Samples/Ogre/sinbad
此源码来自ogre 1.72 sample character:https://bitbucket.org/sinbad/ogre/src/d1f2eab81f08/Samples/Character/

posted on 2011-06-15 12:15 的笔记 阅读(2424) 评论(0)  编辑 收藏 引用


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理