修复了3D相机向其面向的方向移动的问题?简短版本详细信息和示例评论中问题的答案

2022-09-04 23:30:39

简短版本

我有一个附加到a和运动工作正常,只要的旋转/轴与世界对齐。但是,当一个物体旋转以“看”不同的方向并被告知“向前”移动时,它不会沿着新的“向前”方向移动。相反,它继续沿应用旋转之前所面向的同一方向移动。CameraSceneNodeSceneNode

详细信息和示例

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

根据TL;DR;片段,假设你有一个零旋转(例如朝北),然后围绕+Y“向上”轴向左旋转90度,即让它向西看。到目前为止,一切都很好。如果你现在试图移动“前进”,现在是向西移动,而是移动,就好像“前进”仍然朝北一样。cameraNodecameraNodecameraNodecameraNode

简而言之,它就像它从未被旋转过一样移动。

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

相关成员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);
    // ...
}

将 a 添加到场景仅意味着它被附加到图形中的 a。由于 没有自己的位置/旋转信息,因此客户端只需处理 所附加到的,仅此而已。CameraSceneNodeCameraSceneNodeCamera

除了这个问题中提到的问题外,其他所有内容似乎都按预期工作。

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;
}

旋转的数学/代码封装在 3x3 矩阵对象中。请注意,在测试期间,您可以看到场景围绕相机旋转,因此确实应用了旋转,这使这个问题更加困扰我。

方向矢量

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

@Override
public Vector3 getDerivedRightAxis() {
    return derivedRotation.column(0);
}

@Override
public Vector3 getDerivedUpAxis() {
    return derivedRotation.column(1);
}

@Override
public Vector3 getDerivedForwardAxis() {
    return derivedRotation.column(2);
}

计算派生变换

如果相关,则说明转换是如何组合以计算实例的派生转换的:parentNodethis

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内部和外部浏览了几个答案(例如,这里这里这里这里,以及其他几个)。显然,虽然相关,但它们对我的情况真的没有帮助。


评论中问题的答案

您确定在计算时已经计算了父母的计算吗?derivedTransformderivedTransform

是的,在更新子级之前,始终会更新父项。逻辑是: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;
}

本文调用上一节中的私有方法。


答案 1

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

OpenGL v1.0 使用旧的 API 调用:在场景类的场景图之外的场景类中使用相机类对象时,实现该对象。这是用C++写的

相机.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

相机.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调用的函数中,我们首先加载到Modelview矩阵中,然后加载单位矩阵;然后我们最终使用 glu 的 gluLookAt(...) 方法来设置所需向量的位置。CameraRender

Scene.h - 具有许多成员和功能;但是对于对象,它具有相机作为成员,而不是指向相机的指针。Camera

场景.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<AlphaObject*>::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;    
} 

在这里,独立于类以及场景的场景图层次结构,我们在场景的调用中使用。在这里,我们通过获取 的当前位置和方向来设置 。CameraPlayerCameraRenderCameraPlayerPlayer'sLookCenter

编辑 - 添加玩家类和相关代码以进行移动计算

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

播放器.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

球员.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 - 此处与相机相同;有一个玩家对象,而不是指向玩家对象的指针。但是,有一个指向玩家Transform的指针,它是一个NodeTransform。由于玩家与场景的交互,这里列出的功能太多了,因为这是一个工作的3D游戏。我可以提供一些可能感兴趣的功能。

场景.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    
}

编辑 - 添加节点变形::Render() - 显示 MVP 的操作顺序

// Move Model View Matrix M = (T C R S C^)
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

答案 2

推荐