You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

DirectX11四元数相机调整俯仰偏航时出现滚转问题求助

问题:四元数实现相机俯仰/偏航时出现滚转现象

我正在用DirectX11开发一款小型引擎,将Transform类的旋转实现从纯欧拉角改为四元数后,遇到了问题:仅调整相机的俯仰(pitch)和偏航(yaw)时,相机最终会出现滚转(roll)现象。尝试调整矩阵与四元数的乘法顺序后,问题仍未解决。

相关代码

相机旋转处理代码:

void FreeMovement::HandleRotation(float deltaTime)
{
    // Get data
    Transform* pTransform = GetGameObject()->GetTransform();
    InputManager* pInput = InputManager::GetInstance();

    Rotator cameraRotation = pTransform->GetLocalRotation();
    const float deltaAngle = m_RotateSpeed * deltaTime;

    // Handle input
    if (pInput->IsKeyPressed(VK_LEFT))
    {
        cameraRotation = Rotator::FromAxisAngle(Vector3::Up(), -deltaAngle) * cameraRotation;
    }
    if (pInput->IsKeyPressed(VK_RIGHT))
    {
        cameraRotation = Rotator::FromAxisAngle(Vector3::Up(), deltaAngle) * cameraRotation;
    }

    if (pInput->IsKeyPressed(VK_UP))
    {
        cameraRotation = Rotator::FromAxisAngle(Vector3::Right(), -deltaAngle) * cameraRotation;
    }
    if (pInput->IsKeyPressed(VK_DOWN))
    {
        cameraRotation = Rotator::FromAxisAngle(Vector3::Right(), deltaAngle) * cameraRotation;
    }

    // Set data
    cameraRotation.Normalize();
    pTransform->SetLocalRotation(cameraRotation);
}

FromAxisAngle与Rotator乘法实现:

DirectX_Rotator DirectX_Rotator::FromAxisAngle(const Vector3& axis, float angle)
{
    return DirectX_Rotator{ DirectX::XMQuaternionRotationAxis(axis.GetVector(), angle * toRadians) };
}

DirectX_Rotator DirectX_Rotator::operator* (const DirectX_Rotator& other) const
{
    return DirectX_Rotator{ DirectX::XMQuaternionMultiply(GetVector(), other.GetVector()) };
}

注:Rotator与DirectX_Rotator是通过using关键字关联的别名,用于适配不同渲染API。

问题原因

核心问题在于你使用了全局坐标系的Up/Right轴生成旋转四元数,再与当前旋转相乘。当相机存在俯仰角度后,全局Right轴和相机自身的局部Right轴不再重合,此时用全局Right轴做俯仰旋转,会额外引入滚转分量,最终导致相机出现不必要的滚转。

解决方案

1. 改用相机局部坐标系的轴生成旋转

从当前相机的旋转中提取局部Up/Right方向,而非使用全局轴。可以通过将全局轴乘以当前相机旋转的逆矩阵,或直接用四元数将全局轴旋转到相机局部空间。

2. 调整四元数乘法顺序

DirectX的XMQuaternionMultiply(q1, q2)表示先应用q2的旋转,再应用q1的旋转。要让新旋转基于相机当前的局部空间生效,需将新生成的局部旋转四元数右乘到当前旋转上。

修改后的代码示例

void FreeMovement::HandleRotation(float deltaTime)
{
    Transform* pTransform = GetGameObject()->GetTransform();
    InputManager* pInput = InputManager::GetInstance();

    Rotator cameraRotation = pTransform->GetLocalRotation();
    const float deltaAngle = m_RotateSpeed * deltaTime;

    // 获取相机局部坐标系的Up和Right轴
    Vector3 localUp = cameraRotation.RotateVector(Vector3::Up());
    Vector3 localRight = cameraRotation.RotateVector(Vector3::Right());

    // 处理偏航(Yaw):绕局部Up轴旋转
    if (pInput->IsKeyPressed(VK_LEFT))
    {
        Rotator yawRot = Rotator::FromAxisAngle(localUp, -deltaAngle);
        cameraRotation = cameraRotation * yawRot; // 右乘,基于局部空间叠加旋转
    }
    if (pInput->IsKeyPressed(VK_RIGHT))
    {
        Rotator yawRot = Rotator::FromAxisAngle(localUp, deltaAngle);
        cameraRotation = cameraRotation * yawRot;
    }

    // 处理俯仰(Pitch):绕局部Right轴旋转
    if (pInput->IsKeyPressed(VK_UP))
    {
        Rotator pitchRot = Rotator::FromAxisAngle(localRight, -deltaAngle);
        cameraRotation = cameraRotation * pitchRot;
    }
    if (pInput->IsKeyPressed(VK_DOWN))
    {
        Rotator pitchRot = Rotator::FromAxisAngle(localRight, deltaAngle);
        cameraRotation = cameraRotation * pitchRot;
    }

    cameraRotation.Normalize();
    pTransform->SetLocalRotation(cameraRotation);
}

补充说明

  • 需要实现Rotator::RotateVector方法,内部调用DirectX的XMVector3Rotate,用当前四元数旋转向量。
  • 建议限制俯仰角度(比如±89度),避免相机过度翻转导致的异常行为。

内容的提问来源于stack exchange,提问作者RenzoDP

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 22:12:48