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

如何用四元数存储相机旋转?基于glm的6自由度相机实现

6自由度相机的GLM四元数实现

核心原则

所有旋转状态仅通过glm::quat orientation维护,right、up、direction向量由四元数实时推导,避免欧拉角万向锁问题,适配太空场景无固定up轴的6自由度操作需求。


构造函数修正

初始化时必须从四元数生成正确的方向向量,替换原构造函数中的默认向量初始化逻辑:

Camera::Camera(glm::vec3 position, glm::quat orientation, float zNear, float zFar)
{
    this->position = position;
    this->orientation = glm::normalize(orientation); // 四元数必须保持归一化

    // 从四元数推导初始相机轴向量
    updateAxesFromOrientation();

    this->zNear = zNear;
    this->zFar = zFar;

    updateViewMatrix();
}

Camera::Camera(glm::vec3 position, glm::vec3 eulerAnglesDeg, float zNear, float zFar)
{
    this->position = position;
    // 欧拉角转四元数,GLM默认顺序为pitch(x)、yaw(y)、roll(z),需先转弧度
    this->orientation = glm::normalize(glm::quat(glm::radians(eulerAnglesDeg)));

    updateAxesFromOrientation();

    this->zNear = zNear;
    this->zFar = zFar;

    updateViewMatrix();
}

新增辅助函数,用于从四元数计算相机三个轴向量:

void Camera::updateAxesFromOrientation()
{
    // 四元数转旋转矩阵,提取矩阵列向量作为相机局部轴
    glm::mat3 rotationMat = glm::mat3(orientation);
    right = rotationMat[0];   // 矩阵第一列为右轴
    up = rotationMat[1];      // 矩阵第二列为上轴
    direction = -rotationMat[2]; // 矩阵第三列为世界空间前轴,相机朝向取反匹配GLM默认规则
}

旋转相关函数实现

1. 增量旋转(pitch/yaw/roll)

基于当前相机姿态追加旋转量,完全遵循局部坐标系规则:

void Camera::pitch(float amountDeg)
{
    float amountRad = glm::radians(amountDeg);
    // 绕相机自身右轴旋转(抬头/低头)
    glm::quat pitchQuat = glm::angleAxis(amountRad, right);
    orientation = glm::normalize(pitchQuat * orientation);
    updateAxesFromOrientation();
    updateViewMatrix();
}

void Camera::yaw(float amountDeg)
{
    float amountRad = glm::radians(amountDeg);
    // 绕相机自身上轴旋转(左右转头)
    glm::quat yawQuat = glm::angleAxis(amountRad, up);
    orientation = glm::normalize(yawQuat * orientation);
    updateAxesFromOrientation();
    updateViewMatrix();
}

void Camera::roll(float amountDeg)
{
    float amountRad = glm::radians(amountDeg);
    // 绕相机自身前轴旋转(滚转)
    glm::quat rollQuat = glm::angleAxis(amountRad, direction);
    orientation = glm::normalize(rollQuat * orientation);
    updateAxesFromOrientation();
    updateViewMatrix();
}

2. 设置绝对旋转角度(setPitch/setYaw/setRoll)

将当前姿态转为欧拉角后修改对应分量,再转回四元数(注:此方式仍可能触发万向锁,仅适用于需要单独设置单轴角度的场景):

void Camera::setPitch(float pitchDeg)
{
    glm::vec3 euler = glm::degrees(glm::eulerAngles(orientation));
    euler.x = pitchDeg;
    orientation = glm::normalize(glm::quat(glm::radians(euler)));
    updateAxesFromOrientation();
    updateViewMatrix();
}

void Camera::setYaw(float yawDeg)
{
    glm::vec3 euler = glm::degrees(glm::eulerAngles(orientation));
    euler.y = yawDeg;
    orientation = glm::normalize(glm::quat(glm::radians(euler)));
    updateAxesFromOrientation();
    updateViewMatrix();
}

void Camera::setRoll(float rollDeg)
{
    glm::vec3 euler = glm::degrees(glm::eulerAngles(orientation));
    euler.z = rollDeg;
    orientation = glm::normalize(glm::quat(glm::radians(euler)));
    updateAxesFromOrientation();
    updateViewMatrix();
}

3. 四元数/欧拉角批量旋转

void Camera::rotate(glm::quat rotation)
{
    orientation = glm::normalize(rotation * orientation);
    updateAxesFromOrientation();
    updateViewMatrix();
}

void Camera::rotate(glm::vec3 rotationDeg)
{
    glm::quat rotationQuat = glm::normalize(glm::quat(glm::radians(rotationDeg)));
    orientation = glm::normalize(rotationQuat * orientation);
    updateAxesFromOrientation();
    updateViewMatrix();
}

视图矩阵更新修正

使用推导后的相机轴向量生成视图矩阵,替换原错误的默认向量引用:

void Camera::updateViewMatrix()
{
    // lookAt参数:相机位置、相机看向的目标点(位置+朝向)、相机局部上轴
    viewMatrix = glm::lookAt(position, position + direction, up);
    viewProjectionMatrix = projectionMatrix * viewMatrix;
}

关键注意事项

  • 所有修改orientation的操作后必须调用glm::normalize,避免四元数非归一化导致的缩放问题。
  • 6DOF相机无固定世界up轴,所有旋转均基于相机自身局部坐标系,需与常规地面相机的旋转逻辑区分开。
  • updateAxesFromOrientation是核心联动函数,每次旋转后必须调用,确保move等依赖局部轴的函数逻辑正确。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.21 04:48:28