如何沿所有轴正确旋转四元数?

时间:2013-05-05 12:33:35

标签: c++ 3d camera rotation quaternions

我想编码第一人称相机,其旋转存储在四元数中。不幸的是,轮换有问题。

以下功能负责旋转相机。参数MouseSpeed传递鼠标移动和旋转速度。然后该函数获取旋转四元数,旋转它并存储结果。顺便说一句,我正在使用类型和函数来自的Bullet Physics

void Rotate(vec2 Mouse, float Speed)
{
    btTransform transform = camera->getWorldTransform();
    btQuaternion rotation = transform.getRotation();

    Mouse = Mouse * Speed;                    // apply mouse sensitivity
    btQuaternion change(Mouse.y, Mouse.x, 0); // create quaternion from angles
    rotation = change * rotation;             // rotate camera by that

    transform.setRotation(rotation);
    camera->setWorldTransform(transform);
}

为了说明鼠标移动时产生的相机旋转,我给你看一个手绘图。在左侧显示相机实际执行的错误旋转。在右侧显示了所需的正确情况。箭头表示向上移动鼠标(橙色)和向下移动(蓝色)时相机的旋转方式。

wrong rotation on the left and desired rotation on the right

如您所见,只要偏航为零,旋转就是正确的。但是它的偏航越多,相机旋转的圆就越小。相反,圆圈应该像经度一样沿着整个球体运行。

我对四元数不太熟悉,所以我在这里问如何正确旋转它们。

2 个答案:

答案 0 :(得分:9)

我发现了如何正确地自行旋转四元数。关键是找到我想要旋转的轴的矢量。当角度是围绕实际轴旋转的量时,它们用于从轴和角度创建四元数。

以下代码显示了我最终的结果。它还允许滚动相机,这可能有用。

void Rotate(btVector3 Amount, float Sensitivity)
{
    // fetch current rotation
    btTransform transform = camera->getWorldTransform();
    btQuaternion rotation = transform.getRotation();

    // apply mouse sensitivity
    Amount *= Sensitivity;

    // create orientation vectors
    btVector3 up(0, 1, 0);
    btVector3 lookat = quatRotate(rotation, btVector3(0, 0, 1));
    btVector3 forward = btVector3(lookat.getX(), 0, lookat.getZ()).normalize();
    btVector3 side = btCross(up, forward);

    // rotate camera with quaternions created from axis and angle
    rotation = btQuaternion(up,      Amount.getY()) * rotation;
    rotation = btQuaternion(side,    Amount.getX()) * rotation;
    rotation = btQuaternion(forward, Amount.getZ()) * rotation;

    // set new rotation
    transform.setRotation(rotation);
    camera->setWorldTransform(transform);
}

由于我很少发现有关四元数旋转的信息,我会花些时间进一步解释上面的代码。

获取和设置旋转特定于物理引擎,与此问题无关,因此我不会详细说明。下一部分,将数量乘以鼠标敏感度应该非常清楚。让我们继续使用方向向量。

  • up向量取决于您自己的实现。最方便的是,正Y轴指向上方,因此我们最终得到0, 1, 0
  • lookat向量表示相机查看的方向。我们简单地通过相机旋转四元数旋转指向前方的单位矢量。同样,前向指向向量取决于您的约定。如果Y轴向上,则正Z轴可能指向前方,即0, 0, 1
  • 不要将其与下一个向量混合。它被命名为forward,它引用了相机旋转。因此,我们只需要将lookat向量投射到地面。在这种情况下,我们只需取lookat向量并忽略向上指向组件。为了整洁,我们将该矢量标准化。
  • side向量指向相机方向的左侧。因此,它垂直于upforward向量,我们可以使用cross product来计算它。

鉴于这些矢量,我们可以正确旋转它们周围的相机四元数。你从哪个开始,Z,Y或Z,取决于Euler angle sequence,这也是一个从应用程序到应用程序不同的约定。由于我想要以Y X Z顺序应用旋转,我执行以下操作。

  • 首先,围绕up轴旋转相机Y旋转量。这是偏航。
  • 然后围绕指向左侧的side轴旋转X个量。这是投球。
  • 最后,围绕forward向量旋转Z量以应用滚动。

要应用这些旋转,我们需要将轴和角度创建的四元数与当前相机旋转相乘。最后,我们将结果四元数应用于物理模拟中的主体。

答案 1 :(得分:0)

矩阵和俯仰/偏航/滚动都有其局限性,我不再使用它们而是使用四元数。我旋转视图矢量并首先重新计算相机矢量,然后重新计算与旋转视图矢量相关的视图矩阵。

void Camera::rotateViewVector(glm::quat quat) {

    glm::quat rotatedViewQuat;

    quat = glm::normalize(quat);
    m_viewVector = glm::normalize(m_viewVector);

    glm::quat viewQuat(0.0f,
        m_viewVector.x,
        m_viewVector.y,
        m_viewVector.z);

    viewQuat = glm::normalize(viewQuat);

    rotatedViewQuat = (quat * viewQuat) * glm::conjugate(quat);
    rotatedViewQuat = glm::normalize(rotatedViewQuat);

    m_viewVector = glm::normalize(glm::vec3(rotatedViewQuat.x, rotatedViewQuat.y, rotatedViewQuat.z));
    m_rightVector = glm::normalize(glm::cross(glm::vec3(0.0f, 1.0f, 0.0f), m_viewVector));
    m_upVector = glm::normalize(glm::cross(m_viewVector, m_rightVector));
}