修复3D相机朝其面向的方向移动?

短版(TL; DR)

我有一个连接到SceneNodeCamera ,只要SceneNode的旋转/轴与世界对齐,运动就可以正常工作。 然而,当一个物体旋转到另一个方向“看”并被告知“向前”移动时,它不会沿着新的“向前”方向移动。 相反,它继续沿着应用旋转之前面向的方向移动。

细节和例子

我有一个场景图来管理3D场景。 该图是SceneNode对象的树,它们知道它们相对于父级和世界的变换。

根据TL; DR; 片段,假设您有一个零旋转的cameraNode (例如朝北),然后将cameraNode向左旋转+ Y“向上”轴向左旋转90度,即向西看。 到目前为止事情还可以。 如果您现在尝试将cameraNode “向前”移动(现在向西),则cameraNode会移动,就像“向前”仍然朝向北方一样。

简而言之,它就像从未在第一时间旋转一样移动。

下面的代码显示了我最近尝试过的内容以及我(当前)最佳猜测,即缩小最可能与问题相关的区域。

相关的SceneNode成员

SceneNode实现具有以下字段(仅显示与此问题相关的字段):

 class GenericSceneNode implements SceneNode { // this node's parent; always null for the root scene node in the graph private SceneNode parentNode; // transforms are relative to a parent scene node, if any private Vector3 relativePosition = Vector3f.createZeroVector(); private Matrix3 relativeRotation = Matrix3f.createIdentityMatrix(); private Vector3 relativeScale = Vector3f.createFrom(1f, 1f, 1f); // transforms are derived by combining transforms from all parents; // these are relative to the world --in world space private Vector3 derivedPosition = Vector3f.createZeroVector(); private Matrix3 derivedRotation = Matrix3f.createIdentityMatrix(); private Vector3 derivedScale = Vector3f.createFrom(1f, 1f, 1f); // ... } 

Camera添加到场景只是意味着它附加到图形中的SceneNode 。 由于Camera没有自己的位置/旋转信息,因此客户端只需处理Camera所连接的SceneNode即可。

除了这个问题中提到的问题,其他一切似乎都按预期工作。

SceneNode翻译

将节点转换为特定方向的数学运算很简单,基本归结为:

 currentPosition = currentPosition + normalizedDirectionVector * offset; 

SceneNode实现如下:

 @Override public void moveForward(float offset) { translate(getDerivedForwardAxis().mult(-offset)); } @Override public void moveBackward(float offset) { translate(getDerivedForwardAxis().mult(offset)); } @Override public void moveLeft(float offset) { translate(getDerivedRightAxis().mult(-offset)); } @Override public void moveRight(float offset) { translate(getDerivedRightAxis().mult(offset)); } @Override public void moveUp(float offset) { translate(getDerivedUpAxis().mult(offset)); } @Override public void moveDown(float offset) { translate(getDerivedUpAxis().mult(-offset)); } @Override public void translate(Vector3 tv) { relativePosition = relativePosition.add(tv); isOutOfDate = true; } 

除了这个问题中提到的问题,周围的事情如预期的那样。

SceneNode旋转

客户端应用程序按如下方式旋转cameraNode

 final Angle rotationAngle = new Degreef(-90f); // ... cameraNode.yaw(rotationAngle); 

SceneNode实现也相当简单:

 @Override public void yaw(Angle angle) { // FIXME?: rotate(angle, getDerivedUpAxis()) accumulates other rotations rotate(angle, Vector3f.createUnitVectorY()); } @Override public void rotate(Angle angle, Vector3 axis) { relativeRotation = relativeRotation.rotate(angle, axis); isOutOfDate = true; } 

旋转的数学/代码封装在3×3矩阵对象中。 请注意,在测试期间,您可以看到围绕摄像机旋转的场景,因此实际上正在应用旋转,这使得这个问题对我来说更加令人费解。

方向向量

方向向量只是从派生的3×3旋转矩阵中取得的相对于世界的列:

 @Override public Vector3 getDerivedRightAxis() { return derivedRotation.column(0); } @Override public Vector3 getDerivedUpAxis() { return derivedRotation.column(1); } @Override public Vector3 getDerivedForwardAxis() { return derivedRotation.column(2); } 

计算派生变换

如果它是相关的,那么这就是如何组合parentNode变换来计算this实例的派生变换:

 private void updateDerivedTransforms() { if (parentNode != null) { /** * derivedRotation = parent.derivedRotation * relativeRotation * derivedScale = parent.derivedScale * relativeScale * derivedPosition = parent.derivedPosition + parent.derivedRotation * (parent.derivedScale * relativePosition) */ derivedRotation = parentNode.getDerivedRotation().mult(relativeRotation); derivedScale = parentNode.getDerivedScale().mult(relativeScale); Vector3 scaledPosition = parentNode.getDerivedScale().mult(relativePosition); derivedPosition = parentNode.getDerivedPosition().add(parentNode.getDerivedRotation().mult(scaledPosition)); } else { derivedPosition = relativePosition; derivedRotation = relativeRotation; derivedScale = relativeScale; } Matrix4 t, r, s; t = Matrix4f.createTranslationFrom(relativePosition); r = Matrix4f.createFrom(relativeRotation); s = Matrix4f.createScalingFrom(relativeScale); relativeTransform = t.mult(r).mult(s); t = Matrix4f.createTranslationFrom(derivedPosition); r = Matrix4f.createFrom(derivedRotation); s = Matrix4f.createScalingFrom(derivedScale); derivedTransform = t.mult(r).mult(s); } 

这用于通过场景图传播变换,以便子SceneNode可以将其父变换考虑在内。


其他/相关问题

在发布这个问题之前的最后~3周内,我在SO内外都经历了几个答案(例如, 这里 , 这里 , 这里 ,以及其他几个)。 显然,虽然相关,但在我的案例中,它们确实没有帮助。


评论中的问题答案

你确定在计算derivedTransform你父亲的derivedTransform已经被计算了吗?

是的,在更新子项之前,始终更新父SceneNodeupdate逻辑是:

 @Override public void update(boolean updateChildren, boolean parentHasChanged) { boolean updateRequired = parentHasChanged || isOutOfDate; // update this node's transforms before updating children if (updateRequired) updateFromParent(); if (updateChildren) for (Node n : childNodesMap.values()) n.update(updateChildren, updateRequired); emitNodeUpdated(this); } @Override public void updateFromParent() { updateDerivedTransforms(); // implementation above isOutOfDate = false; } 

这篇文章调用了上一节中的私有方法。

这不是一个直接的答案,而是根据OP的要求提供参考。

使用旧API调用的OpenGL v1.0:在Scene类的Scene Graph之外的Scene类中使用Camera Class对象时实现它。 这是用C ++编写的

Camera.h

 #ifndef CAMERA_H #define CAMERA_H #include "Core.h" class Camera { private: Vector3 _v3EyePosition; Vector3 _v3LookCenter; Vector3 _v3Up; public: Camera(); ~Camera(); void Get3rdPersonLocation( Vector3 &v3Position, float &fAngle ); void Set( Vector3 v3EyePosition, Vector3 v3LookCenter, Vector3 v3Up = Vector3( 0.0f, 1.0f, 0.0f ) ); void Render(); }; #endif 

Camera.cpp

 #include "stdafx.h" #include "Camera.h" Camera::Camera() { _v3EyePosition = Vector3( 0.0f, 0.0f, 0.0f ); _v3LookCenter = Vector3( 0.0f, 0.0f, -1.0f ); _v3Up = Vector3( 0.0f, 1.0f, 0.0f ); } Camera::~Camera() { } void Camera::Get3rdPersonLocation( Vector3 &v3Position, float &fAngle ) { v3Position._fX = _v3LookCenter._fX; v3Position._fY = _v3EyePosition._fY; v3Position._fZ = _v3LookCenter._fZ; // Find Angle float fX = _v3LookCenter._fX - _v3EyePosition._fX; float fZ = _v3LookCenter._fZ - _v3EyePosition._fZ; // Angle In Degrees fAngle = Math::Radian2Degree( atan2( fX, fZ ) ); } void Camera::Set( Vector3 v3EyePosition, Vector3 v3LookCenter, Vector3 v3Up ) { _v3EyePosition = v3EyePosition; _v3LookCenter = v3LookCenter; _v3Up = v3Up; } void Camera::Render() { glMatrixMode( GL_MODELVIEW ); glLoadIdentity(); gluLookAt( _v3EyePosition._fX, _v3EyePosition._fY, _v3EyePosition._fZ, _v3LookCenter._fX, _v3LookCenter._fY, _v3LookCenter._fZ, _v3Up._fX, _v3Up._fY, _v3Up._fZ ); } 

在使用旧的OpenGL API调用的CameraRender函数中,我们首先加载Modelview矩阵,然后加载单位矩阵; 然后我们最终使用glu的gluLookAt(…)方法来设置所需矢量的位置。

Scene.h – 有很多成员和function; 但就像Camera Object而言,它有一个Camera作为成员而不是指向Camera的指针。

Scene.cpp – 渲染()

 void Scene::Render() { // Update Camera _Camera.Set( _Player.GetPosition(), _Player.GetLookCenter() ); // Position Camera _Camera.Render(); if ( UserSettings::Get()->_bQuit ) { return; } if ( _vpNodes.size() < 1 ) { // No SceneGraph To Render return; } EnableLights(); // Send Items To Be Rendered // Clear 2nd Render Pass Container DeleteAllAlphaObjects(); // Render All Opaque Objects (1st Pass) & Store 2nd Pass Objects _vpNodes[0]->RenderOGL( false, true ); // Render All Objects With Alpha Values (2nd Pass) glEnable( GL_BLEND ); glMatrixMode( GL_MODELVIEW ); for ( std::vector::iterator it = _vpAlphaObjects.begin(); it != _vpAlphaObjects.end(); ++it ) { // Set Model View Matrix glMatrixMode( GL_MODELVIEW ); glPushMatrix(); glLoadMatrixf( &(*it)->f16Matrix[0] ); (*it)->pShape->RenderOGL( true, false ); glMatrixMode( GL_MODELVIEW ); glPopMatrix(); } // Show Selected Weapon _Player.RenderWeapon(); glDisable( GL_BLEND ); DisableLights(); return; } 

这里Camera独立于Player类以及Scene的Scene Graph Hierarchy,我们在Scene的Render Call中使用Camera 。 在这里,我们通过获取Player的当前位置和Player's LookCenter方向来设置Camera

编辑 – 为运动计算添加播放器类和相关代码

 enum Action { NO_ACTION = -1, MOVING_FORWARD = 0, MOVING_BACK, MOVING_LEFT, MOVING_RIGHT, LOOKING_LEFT, LOOKING_RIGHT, LOOKING_UP, LOOKING_DOWN, }; // Action 

Player.h

 #ifndef PLAYER_H #define PLAYER_H #include "Core.h" class Weapon; class NodeTransform; class Player { private: enum MouseLook { ML_NORMAL = 1, ML_INVERT = -1, } _MouseLookState; // MouseLook Vector3 _v3Position; Vector3 _v3LookCenter; float _fLookDistance; float _fMaxUp; float _fMaxDown; float _fLinearSpeed; float _fAngularSpeed; public: Player( float fLookDistance ); ~Player(); void SetSpeed( float fLinear, float fAngular ); void SetMouseY( bool bInvert ); void SetLocation( Vector3 v3Position, Vector3 v3Direction = Vector3( 0.0f, 0.0f, -1.0f ) ); void Move( Action action, float fDeltaTime ); bool Update(); inline void SetPosition( Vector3 v3Position ); inline Vector3 GetPosition(); inline Vector3 GetLookCenter(); inline Vector3 GetLookDirection(); }; inline void Player::SetPosition( Vector3 v3Position ) { Vector3 v3LookDirection; v3LookDirection = _v3LookCenter - _v3Position; _v3Position = v3Position; _v3LookCenter = v3Position + v3LookDirection; } inline Vector3 Player::GetPosition() { return _v3Position; } inline Vector3 Player::GetLookCenter() { return _v3LookCenter; } inline Vector3 Player::GetLookDirection() { Vector3 v3LookDirection; v3LookDirection = _v3LookCenter - _v3Position; v3LookDirection.Normalize(); return v3LookDirection; } #endif 

Player.cpp

 #include "stdafx.h" #include "Player.h" #include "UserSettings.h" #include "NodeTransform.h" Player::Player( float fLookDistance ) { _fLookDistance = fLookDistance; // Calculate Maximum Limits For Looking Up And Down _fMaxUp = _fLookDistance * tan( Math::Degree2Radian( 50 ) ); _fMaxDown = _fLookDistance * tan( Math::Degree2Radian( 40 ) ); _v3Position = Vector3( 0.0f, 0.5f, 0.0f ); _v3LookCenter = Vector3( 0.0f, 0.5f, -fLookDistance ); _fLinearSpeed = 15.0f; // Units Per Second _fAngularSpeed = 3.0f; // Radians Per Second SetMouseY( UserSettings::Get()->GetMouseInvert() ); } Player::~Player() { } // ~Player void Player::SetMouseY( bool bInvert ) { if ( bInvert ) { _MouseLookState = ML_INVERT; } else { _MouseLookState = ML_NORMAL; } } void Player::SetLocation( Vector3 v3Position, Vector3 v3Direction ) { _v3Position = v3Position; _v3LookCenter = v3Position + _fLookDistance*v3Direction; } void Player::Move( Action action, float fDeltaTime ) { Vector3 v3LookDirection; v3LookDirection = _v3LookCenter - _v3Position; switch ( action ) { case MOVING_FORWARD: { // Prevent Vertical Motion v3LookDirection._fY = 0.0f; _v3Position += v3LookDirection*fDeltaTime*_fLinearSpeed; _v3LookCenter += v3LookDirection*fDeltaTime*_fLinearSpeed; break; } case MOVING_BACK: { // Prevent Vertical Motion v3LookDirection._fY = 0.0f; _v3Position -= v3LookDirection*fDeltaTime*_fLinearSpeed; _v3LookCenter -= v3LookDirection*fDeltaTime*_fLinearSpeed; break; } case MOVING_LEFT: { // Get "Side" Direction & Prevent Vertical Motion v3LookDirection._fY = v3LookDirection._fX; v3LookDirection._fX = -v3LookDirection._fZ; v3LookDirection._fZ = v3LookDirection._fY; v3LookDirection._fY = 0.0f; _v3Position -= v3LookDirection*fDeltaTime*_fLinearSpeed; _v3LookCenter -= v3LookDirection*fDeltaTime*_fLinearSpeed; break; } case MOVING_RIGHT: { // Get "Side" Direction & Prevent Vertical Motion v3LookDirection._fY = v3LookDirection._fX; v3LookDirection._fX = -v3LookDirection._fZ; v3LookDirection._fZ = v3LookDirection._fY; v3LookDirection._fY = 0.0f; _v3Position += v3LookDirection*fDeltaTime*_fLinearSpeed; _v3LookCenter += v3LookDirection*fDeltaTime*_fLinearSpeed; break; } case LOOKING_LEFT: { /*float fSin = -sin( fDeltaTime*_fAngularSpeed ); float fCos = cos( fDeltaTime*_fAngularSpeed ); _v3LookCenter._fX = _v3Position._fX + (-fSin * v3LookDirection._fZ + fCos * v3LookDirection._fX ); _v3LookCenter._fZ = _v3Position._fZ + ( fCos * v3LookDirection._fZ + fSin * v3LookDirection._fX ); break;*/ // Third Person float fSin = sin( fDeltaTime*_fAngularSpeed ); float fCos = -cos( fDeltaTime*_fAngularSpeed ); _v3Position._fX = _v3LookCenter._fX + (-fSin * v3LookDirection._fZ + fCos * v3LookDirection._fX ); _v3Position._fZ = _v3LookCenter._fZ + ( fCos * v3LookDirection._fZ + fSin * v3LookDirection._fX ); break; } case LOOKING_RIGHT: { /*float fSin = sin( fDeltaTime*_fAngularSpeed ); float fCos = cos( fDeltaTime*_fAngularSpeed ); _v3LookCenter._fX = _v3Position._fX + (-fSin * v3LookDirection._fZ + fCos * v3LookDirection._fX ); _v3LookCenter._fZ = _v3Position._fZ + ( fCos * v3LookDirection._fZ + fSin * v3LookDirection._fX ); break;*/ // Third Person float fSin = -sin( fDeltaTime*_fAngularSpeed ); float fCos = -cos( fDeltaTime*_fAngularSpeed ); _v3Position._fX = _v3LookCenter._fX + (-fSin * v3LookDirection._fZ + fCos * v3LookDirection._fX ); _v3Position._fZ = _v3LookCenter._fZ + ( fCos * v3LookDirection._fZ + fSin * v3LookDirection._fX ); break; } case LOOKING_UP: { _v3LookCenter._fY -= fDeltaTime*_fAngularSpeed*_MouseLookState; // Check Maximum Values if ( _v3LookCenter._fY > (_v3Position._fY + _fMaxUp ) ) { _v3LookCenter._fY = _v3Position._fY + _fMaxUp; } else if ( _v3LookCenter._fY < (_v3Position._fY - _fMaxDown) ) { _v3LookCenter._fY = _v3Position._fY - _fMaxDown; } break; } } } bool Player::Update() { // Stripped Down This Deals With Player's Weapons } void Player::SetSpeed( float fLinear, float fAngular ) { _fLinearSpeed = fLinear; _fAngularSpeed = fAngular; } 

Scene.h - 与Camera相同; 有一个Player Object而不是一个指向玩家对象的指针。 但是有一个指向playerTransform的指针,它是一个NodeTransform。 由于播放器与场景的交互,因此这里列出的function太多了,因为这是一个有效的3D游戏。 我可以提供一些可能感兴趣的function。

Scene.cpp Scene::Update()

 // ----------------------------------------------------------------------- // Update // Animate Objects, Pickup Checks Etc. This All Happens At The // Physics Refresh Rate void Scene::Update() { UserSettings* pUserSettings = UserSettings::Get(); AudioManager* pAudio = AudioManager::GetAudio(); bool bPlayerMoving = false; // Movement if ( pUserSettings->IsAction( MOVING_FORWARD ) ) { _Player.Move( MOVING_FORWARD, GameOGL::GetPhysicsTimeStep() ); bPlayerMoving = true; } if ( pUserSettings->IsAction( MOVING_BACK ) ) { _Player.Move( MOVING_BACK, GameOGL::GetPhysicsTimeStep() ); bPlayerMoving = true; } if ( pUserSettings->IsAction( MOVING_LEFT ) ) { _Player.Move( MOVING_LEFT, GameOGL::GetPhysicsTimeStep() ); bPlayerMoving = true; } if ( pUserSettings->IsAction( MOVING_RIGHT ) ) { _Player.Move( MOVING_RIGHT, GameOGL::GetPhysicsTimeStep() ); bPlayerMoving = true; } if ( bPlayerMoving && !_bPlayerWalking ) { pAudio->SetLooping( AUDIO_FOOTSTEPS, true ); pAudio->Play( AUDIO_FOOTSTEPS ); _bPlayerWalking = true; } else if ( !bPlayerMoving && _bPlayerWalking ) { pAudio->Stop( AUDIO_FOOTSTEPS ); _bPlayerWalking = false; } // ... Other Code Here } 

编辑 - 添加NodeTransform :: Render() - 显示MVP的操作顺序

 // Move Model View Matrix M = (TCRSC^) void NodeTransform::RenderOGL( bool bSecondPass, bool bRenderNext ) { if ( _pIn && _bVisible ) { // Put Matrix Onto Stack For Later Retrieval glMatrixMode( GL_MODELVIEW ); glPushMatrix(); if ( _bHaveMatrix ) { // Use Transformation Matrix glMultMatrixf( &_f16Matrix[0] ); } // Transalate glTranslatef( _v3Translate._fX, _v3Translate._fY, _v3Translate._fZ ); // Move Back To Center glTranslatef( _v3Center._fX, _v3Center._fY, _v3Center._fZ ); // Rotate glRotatef( _fRotateAngle, _v3RotateAxis._fX, _v3RotateAxis._fY, _v3RotateAxis._fZ ); // Scale glScalef( _v3Scale._fX, _v3Scale._fY, _v3Scale._fZ ); // Offset By -ve Center Value glTranslatef( -_v3Center._fX, -_v3Center._fY, -_v3Center._fZ ); // Move Down The Tree _pIn->RenderOGL( bSecondPass, true ); // Get Old Matrix glMatrixMode( GL_MODELVIEW ); glPopMatrix(); } if ( _pNext && bRenderNext ) { _pNext->RenderOGL( bSecondPass, true ); } } // RenderOGL