LWJGL - 使用四元数和平移矩阵在6DOF相机中实现'roll'的问题

时间:2011-10-14 18:47:56

标签: java matrix-multiplication lwjgl quaternions rotational-matrices

我已经花了几个星期来解决这个问题,似乎找不到合适的解决方案,需要一些建议。

我正在使用LWJGL / Java创建一个Camera类,并使用Quaternions来处理轴承(偏航),俯仰和滚转。我希望这款相机可以处理3D空间中的所有6度移动,并且可以滚动。轴承,俯仰和滚动都是四元数。我将它们乘以“变化”四元数,并从中创建一个平移矩阵。我把它放在一个浮动缓冲区中,并将模型视图矩阵乘以包含旋转矩阵的缓冲区。

我可以让轴承和俯仰旋转正常工作,但是当我实施滚动时,我遇到了问题。主要是围绕Z轴旋转(滚动)似乎不起作用。当我“滚动”相机时,它似乎围绕全局Z轴而不是本地相机方向轴滚动。我通常可以让3个中的2个工作,这取决于我乘以四元数的顺序,但我不能让它们一起工作。

由于它们都是独立工作的,我假设我的方向方法有问题,我将它们组合起来并构建一个旋转矩阵。我在整个课程中遇到了问题,所以这里有与轮换相关的方法和声明:

private final static float DEGTORAD = (float)(Math.PI/180);    

//Eye - position of the camera in the 3D world.
private Vector3f eye;

//Camera axis vectors, calculated each time reorient() is called.
//Initialized to global x, y, and z axis initially.
private Vector3f up;
private Vector3f right;
private Vector3f direction;

//Angles of rotation (in degrees)    
private float pitchAngle;
private float bearingAngle;
private float rollAngle;

private Quaternion pitch;
private Quaternion bearing;
private Quaternion roll;

private FloatBuffer viewMatrixBuffer = BufferUtils.createFloatBuffer(16);
private Quaternion currentOrientation;

...

/**
 * Change the bearing (yaw)
 * @param bearing delta in degrees
 */
public void bearing(float bearingDelta){
    bearingAngle += bearingDelta;
    if(bearingAngle > 360){
        bearingAngle -= 360;
    }else if(bearingAngle < 0){
        bearingAngle += 360;
    }
    bearing.setFromAxisAngle(new Vector4f(0f, 1f, 0f, bearingAngle * DEGTORAD));
    bearing.normalise();
}

/**
 * Change the pitch
 * @param pitch delta in degrees
 */
public void pitch(float pitchDelta){
    pitchAngle += pitchDelta;
    if(pitchAngle > 360){
        pitchAngle -= 360;
    }else if(pitchAngle < 0){
        pitchAngle += 360;
    }
    pitch.setFromAxisAngle(new Vector4f(1f, 0f, 0f, pitchAngle * DEGTORAD));
    pitch.normalise();
}

/**
 * @param initialRoll
 */
public void roll(float initialRoll) {
    rollAngle += initialRoll;
    if(rollAngle > 360){
        rollAngle -= 360;
    }else if(rollAngle < 0){
        rollAngle += 360;
    }
    roll.setFromAxisAngle(new Vector4f(0, 0, 1, rollAngle * DEGTORAD));
    roll.normalise();
}

/**
 * Change direction to focus on a certain point in the world
 * @param eye
 */
public void lookThrough(){
    reorient();
    GL11.glMultMatrix(viewMatrixBuffer);
}    

public void reorient(){
    //Multiply in order: bearing, pitch, roll.  Non-commutative!
    Quaternion change = new Quaternion();
    Quaternion.mul(bearing, pitch, change);
    Quaternion.mul(roll, change, change);
    // orient the camera...
    Matrix4f rotationMatrix = getRotationMatrix(change);

    //Get the looking direction
    direction.x = rotationMatrix.m20;
    direction.y = rotationMatrix.m21;
    direction.z = rotationMatrix.m22;

    //Set the position
    rotationMatrix.m30 = eye.x;
    rotationMatrix.m31 = eye.y;
    rotationMatrix.m32 = eye.z;
    rotationMatrix.m33 = 1;

    rotationMatrix.invert();
    rotationMatrix.store(viewMatrixBuffer);

    viewMatrixBuffer.rewind();

    Vector3f.cross(new Vector3f(0,1,0), direction, null).normalise(right);
    Vector3f.cross(right, direction, null).normalise(up);               
}

Vector3f,Quaternion和Matrix4f都是LWJGL类,不是定制的。

所以我的问题是,给定3个Quaternions代表Bearing,Pitch和Roll,如何修改ModelView矩阵以准确表示这些旋转?

编辑:我觉得这非常接近。请参阅RiverC评论中的Gist链接。在旋转了这么多度之后,视图会在滚动之前跳回来,然后再回到正常状态。它的要点在那里,但它仍然略有偏离。

2 个答案:

答案 0 :(得分:3)

我知道这是旧的,但无论如何请允许我猜。问题正是你自己说的:

  

当我“翻转”相机时,它似乎围绕全局Z轴而不是本地相机方向轴滚动。

这样做是因为你要求它围绕矢量(0,0,1)滚动,即全局Z轴。

这是单位四元数的作用:它们通过w标量的某个角度函数围绕由虚构矢量部分(x,y,z)指定的轴旋转矢量(这里是一组矢量,旋转矩阵)( w = cos(角度/ 2))。

如果我明白你要做什么,那就是在你的头部从左向右倾斜时滚动你的相机,那么你应该围绕你的方向向量建立一个滚动四元数:

 roll.setFromAxisAngle(new Vector4f(direction.x, direction.y, direction.z, rollAngle * DEGTORAD));

我假设您的方向向量已标准化,或者LWJGL在调用 setFromAxisAngle 时知道如何处理非单一轴向量。

答案 1 :(得分:3)

你正在以错误的顺序进行乘法。

对于两个旋转q1和q2,如果q2跟随q1(因为旋转通常是非共用的),则乘以q2 * q1。

在万向节风格的系统中,例如FPS的控制,优先顺序始终是偏航,俯仰,滚动。这将建议以下数学:

roll * pitch * yaw

作为一个java点,我建议不要为每次更新创建新的四元数,但无论如何

change = Quaternion.mul(Quaternion.mul(roll, pitch, change), yaw, change);

如果查看代码,第三个Quaternion将被覆盖结果,因此无需重置或重新创建每个帧/更新。

这个旋转顺序令人困惑,但是如果你看一下Quaternions上的Wiki页面,那就是一般规则。