修复3D相机朝其面向的方向移动?
短版(TL; DR)
我有一个连接到SceneNode
的Camera
,只要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
已经被计算了吗?
是的,在更新子项之前,始终更新父SceneNode
。 update
逻辑是:
@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调用的Camera
的Render
函数中,我们首先加载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