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

如何相对于世界坐标系设置相机四元数的Pitch、Yaw、Roll

基于世界坐标系设置相机四元数的Pitch/Yaw/Roll参数

我有一个存储相机朝向的四元数,希望相对于世界坐标系设置该四元数的Pitch(俯仰)、Yaw(偏航)、Roll(滚转)参数。

场景说明

想象四周墙面上各有一个指向世界上方向的箭头。无论当前四元数的姿态如何:

  • 当看向任意箭头(任意Yaw角度)但带有一定Roll(箭头不指向向上)时,调用setRoll(0°),相机视角中箭头应变为向上;
  • 调用setRoll(90°)时,无论调用多少次,相机的滚转角度都被固定为90°,箭头指向左侧。

现有代码

以下是完整的相机类代码,核心待实现的是setPitch、setYaw、setRoll函数:

Camera.hpp

#ifndef CAMERA_HPP
#define CAMERA_HPP

// GLAD
#include <glad/glad.h>
// GLM
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <glm/gtc/quaternion.hpp>

class QuaternionCamera
{
public:
    QuaternionCamera(glm::vec3 position, float zNear, float zFar);
    QuaternionCamera(glm::vec3 position, glm::quat orientation, float zNear, float zFar);
    QuaternionCamera(glm::vec3 position, glm::vec3 orientation, float zNear, float zFar);

    // 设置世界坐标系下的位置
    void setPosition(glm::vec3 position);
    // 设置世界坐标系下的朝向
    void setOrientation(glm::quat orientation);
    // 设置世界坐标系下的朝向(欧拉角)
    void setOrientation(glm::vec3 orientation);
    // 设置世界坐标系下的俯仰角
    void setPitch(float amount);
    // 设置世界坐标系下的偏航角
    void setYaw(float amount);
    // 设置世界坐标系下的滚转角
    void setRoll(float amount);

    // 设置相机近裁剪面
    void setZNear(float zNear);
    // 设置相机远裁剪面
    void setZFar(float zFar);

    // 相对于当前相机坐标系移动
    void move(glm::vec3 movement);
    // 相对于世界坐标系移动
    void moveAxis(glm::vec3 translation);

    // 相对于当前姿态旋转
    void rotate(glm::quat rotation);
    // 相对于当前姿态旋转(欧拉角)
    void rotate(glm::vec3 rotation);
    // 相对于当前姿态俯仰
    void pitch(float amount);
    // 相对于当前姿态偏航
    void yaw(float amount);
    // 相对于当前姿态滚转
    void roll(float amount);

    // 获取世界坐标系下的位置
    glm::vec3 getPosition() const;
    // 获取世界坐标系下的相机朝向
    glm::vec3 getDirection() const;
    // 获取相机右向量
    glm::vec3 getRight() const;
    // 获取相机上向量
    glm::vec3 getUp() const;
    // 获取世界坐标系下的朝向四元数
    glm::quat getOrientation() const;

    // 获取近裁剪面距离
    float getZNear() const;
    // 获取远裁剪面距离
    float getZFar() const;

    // 获取视图矩阵
    glm::mat4 getViewMatrix() const;
    // 获取投影矩阵
    glm::mat4 getProjectionMatrix() const;
    // 获取视图投影矩阵
    glm::mat4 getViewProjectionMatrix() const;

protected:
    // updateProjectionMatrix为虚函数,由派生类实现(透视/正交相机)
    virtual void updateProjectionMatrix() = 0;
    void updateViewProjectionMatrix();

    float zNear;
    float zFar;

    glm::mat4 projectionMatrix;
    glm::mat4 viewProjectionMatrix;

private:
    void updateCameraVectors();
    void updateViewMatrix();
    glm::mat4 viewMatrix;

    glm::vec3 position;
    glm::quat orientation;
    glm::vec3 direction; // 相机朝向
    glm::vec3 right;
    glm::vec3 up;
};

#endif

Camera.cpp

QuaternionCamera::QuaternionCamera(glm::vec3 position, float zNear, float zFar)
{
    this->position = position;
    this->orientation = glm::quat(1.0f, 0.0f, 0.0f, 0.0f); // glm四元数构造参数为(w,x,y,z),存储为(x,y,z,w)

    right     = glm::vec3( 1,  0,  0);
    up        = glm::vec3( 0,  1,  0);
    direction = glm::vec3( 0,  0, -1);

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

    updateViewMatrix();
    updateViewProjectionMatrix();
}

QuaternionCamera::QuaternionCamera(glm::vec3 position, glm::quat orientation, float zNear, float zFar)
{
    this->position = position;
    this->orientation = orientation;

    right     = glm::vec3( 1,  0,  0);
    up        = glm::vec3( 0,  1,  0);
    direction = glm::vec3( 0,  0, -1);

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

    updateViewMatrix();
    updateViewProjectionMatrix();
}

QuaternionCamera::QuaternionCamera(glm::vec3 position, glm::vec3 orientation, float zNear, float zFar)
{
    this->position = position;
    this->orientation = glm::quat(glm::vec3(glm::radians(orientation.x), glm::radians(orientation.y), glm::radians(orientation.z)));

    right     = glm::vec3( 1,  0,  0);
    up        = glm::vec3( 0,  1,  0);
    direction = glm::vec3( 0,  0, -1);

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

    updateCameraVectors();
    updateViewMatrix();
    updateViewProjectionMatrix();
}

void QuaternionCamera::setPosition(glm::vec3 position)
{
    this->position = position;
}

void QuaternionCamera::setOrientation(glm::quat orientation)
{
    this->orientation = orientation;
}

void QuaternionCamera::setOrientation(glm::vec3 orientation)
{
    glm::quat orientationQuat = glm::quat(orientation);
    this->orientation = orientationQuat;
}

void QuaternionCamera::setPitch(float amount)
{
    // TODO: 实现基于世界坐标系的俯仰角设置
    updateCameraVectors();
    updateViewMatrix();
    updateViewProjectionMatrix();
}

void QuaternionCamera::setYaw(float amount)
{
    // TODO: 实现基于世界坐标系的偏航角设置
    updateCameraVectors();
    updateViewMatrix();
    updateViewProjectionMatrix();
}

void QuaternionCamera::setRoll(float amount)
{
    // TODO: 实现基于世界坐标系的滚转角设置
    updateCameraVectors();
    updateViewMatrix();
    updateViewProjectionMatrix();
}

void QuaternionCamera::setZNear(float zNear)
{
    this->zNear = zNear;

    updateProjectionMatrix();
}

void QuaternionCamera::setZFar(float zFar)
{
    this->zFar = zFar;

    updateProjectionMatrix();
}

void QuaternionCamera::move(glm::vec3 movement)
{
    position += (orientation * glm::vec3(1, 0, 0)) * movement.x + (orientation * glm::vec3(0, 1, 0)) * movement.y + (orientation * glm::vec3(0, 0, -1)) * movement.z;
    updateViewMatrix();
    updateViewProjectionMatrix();
}

void QuaternionCamera::moveAxis(glm::vec3 translation)
{
    position += translation;
    updateViewMatrix();
    updateViewProjectionMatrix();
}

void QuaternionCamera::rotate(glm::quat rotation)
{
    orientation *= rotation;

    updateCameraVectors();
    updateViewMatrix();
    updateViewProjectionMatrix();
}

void QuaternionCamera::rotate(glm::vec3 rotation)
{
    glm::quat rotationQuat = glm::quat(rotation);
    orientation *= rotationQuat;

    updateCameraVectors();
    updateViewMatrix();
    updateViewProjectionMatrix();
}

void QuaternionCamera::pitch(float amount)
{
    glm::quat rotation = glm::angleAxis(glm::radians(amount), glm::vec3(1, 0, 0));
    orientation *= rotation;

    updateCameraVectors();
    updateViewMatrix();
    updateViewProjectionMatrix();
}

void QuaternionCamera::yaw(float amount)
{
    glm::quat rotation = glm::angleAxis(glm::radians(-amount), glm::vec3(0, 1, 0));
    orientation *= rotation;

    updateCameraVectors();
    updateViewMatrix();
    updateViewProjectionMatrix();
}

void QuaternionCamera::roll(float amount)
{
    glm::quat rotation = glm::angleAxis(glm::radians(amount), glm::vec3(0, 0, -1));
    orientation *= rotation;

    updateCameraVectors();
    updateViewMatrix();
    updateViewProjectionMatrix();
}

glm::vec3 QuaternionCamera::getPosition() const
{
    return position;
}

glm::vec3 QuaternionCamera::getDirection() const
{
    return direction;
}

glm::vec3 QuaternionCamera::getRight() const
{
    return right;
}

glm::vec3 QuaternionCamera::getUp() const
{
    return up;
}

glm::quat QuaternionCamera::getOrientation() const
{
    return orientation;
}

float QuaternionCamera::getZNear() const
{
    return zNear;
}

float QuaternionCamera::getZFar() const
{
    return zFar;
}

glm::mat4 QuaternionCamera::getViewMatrix() const
{
    return viewMatrix;
}

glm::mat4 QuaternionCamera::getProjectionMatrix() const
{
    return projectionMatrix;
}

glm::mat4 QuaternionCamera::getViewProjectionMatrix() const
{
    return viewProjectionMatrix;
}

void QuaternionCamera::updateViewProjectionMatrix()
{
    viewProjectionMatrix = getProjectionMatrix() * getViewMatrix();
}

void QuaternionCamera::updateCameraVectors()
{
    right = glm::normalize(orientation * glm::vec3(1, 0, 0));
    up = glm::normalize(orientation * glm::vec3(0, 1, 0));
    direction = glm::normalize(orientation * glm::vec3(0, 0, -1));

    if (glm::dot(up, glm::cross(right, direction)) < 0)
    {
        up *= -1;
    }
}

void QuaternionCamera::updateViewMatrix()
{
    viewMatrix = glm::lookAt(position, position + direction, up/*glm::cross(right, direction)*/);
}



PerspectiveCamera::PerspectiveCamera(glm::vec3 position, float fov, float aspectRatio, float zNear, float zFar) : QuaternionCamera(position, zNear, zFar)
{
    this->fov = fov;
    this->aspectRatio = aspectRatio;

    updateProjectionMatrix();
}

PerspectiveCamera::PerspectiveCamera(glm::vec3 position, glm::quat rotation, float fov, float aspectRatio, float zNear, float zFar) : QuaternionCamera(position, rotation, zNear, zFar)
{
    this->fov = fov;
    this->aspectRatio = aspectRatio;

    updateProjectionMatrix();
}


PerspectiveCamera::PerspectiveCamera(glm::vec3 position, glm::vec3 rotation, float fov, float aspectRatio, float zNear, float zFar) : QuaternionCamera(position, rotation, zNear, zFar)
{
    this->fov = fov;
    this->aspectRatio = aspectRatio;

    updateProjectionMatrix();
}

void PerspectiveCamera::setFOV(float fov)
{
    this->fov = fov;

    updateProjectionMatrix();
}

void PerspectiveCamera::setAspectRatio(float aspectRatio)
{
    this->aspectRatio = aspectRatio;

    updateProjectionMatrix();
}

void PerspectiveCamera::setAspectRatio(float width, float height)
{
    this->aspectRatio = height / width;

    updateProjectionMatrix();
}

float PerspectiveCamera::getFOV() const
{
    return fov;
}

float PerspectiveCamera::getAspectRatio() const
{
    return aspectRatio;
}

void PerspectiveCamera::updateProjectionMatrix()
{
    projectionMatrix = glm::perspective(glm::radians(fov), aspectRatio, zNear, zFar);
}

实现方案

核心思路是:保留当前姿态中除目标轴外的其他分量,再叠加目标轴的固定世界坐标系角度,最后重新构造四元数。

setRoll实现

void QuaternionCamera::setRoll(float amount)
{
    const glm::vec3 worldUp = glm::vec3(0, 1, 0);
    const glm::vec3 forward = glm::normalize(direction);

    // 计算无滚转的正交向量组
    glm::vec3 rightNoRoll = glm::normalize(glm::cross(worldUp, forward));
    glm::vec3 upNoRoll = glm::cross(forward, rightNoRoll);

    // 应用目标滚转角
    const float rad = glm::radians(amount);
    const glm::quat rollQuat = glm::angleAxis(rad, forward);
    const glm::vec3 newRight = rollQuat * rightNoRoll;
    const glm::vec3 newUp = rollQuat * upNoRoll;

    // 从正交向量构造四元数
    orientation = glm::quat(glm::mat3(newRight, newUp, -forward));

    updateCameraVectors();
    updateViewMatrix();
    updateViewProjectionMatrix();
}

setYaw实现

void QuaternionCamera::setYaw(float amount)
{
    const float rad = glm::radians(amount);
    // 计算目标偏航的水平朝向
    const glm::vec3 yawDir = glm::normalize(glm::vec3(glm::sin(rad), 0, -glm::cos(rad)));

    // 保留当前俯仰角
    const float pitchAngle = glm::asin(direction.y);
    const glm::vec3 forward = glm::normalize(glm::vec3(
        yawDir.x * glm::cos(pitchAngle),
        glm::sin(pitchAngle),
        yawDir.z * glm::cos(pitchAngle)
    ));

    // 构造无滚转正交向量组
    const glm::vec3 worldUp = glm::vec3(0, 1, 0);
    glm::vec3 right = glm::normalize(glm::cross(worldUp, forward));
    glm::vec3 up = glm::cross(forward, right);

    // 保留当前滚转角
    const glm::vec3 originalRight = getRight();
    float rollAngle = glm::acos(glm::clamp(glm::dot(originalRight, right), -1.0f, 1.0f));
    if (glm::dot(up, originalRight) < 0) {
        rollAngle = -rollAngle;
    }
    const glm::quat rollQuat = glm::angleAxis(rollAngle, forward);
    right = rollQuat * right;
    up = rollQuat * up;

    // 构造最终四元数
    orientation = glm::quat(glm::mat3(right, up, -forward));

    updateCameraVectors();
    updateViewMatrix();
    updateViewProjectionMatrix();
}

setPitch实现

void QuaternionCamera::setPitch(float amount)
{
    const float rad = glm::radians(amount);
    // 保留当前偏航角
    const glm::vec3 horizontalDir = glm::normalize(glm::vec3(direction.x, 0, direction.z));
    float yawAngle = 0.0f;
    if (glm::length(horizontalDir) > 0.0001f) {
        yawAngle = glm::atan(horizontalDir.x, -horizontalDir.z);
    }

    // 构造带有固定俯仰的朝向向量
    const glm::vec3 forward = glm::normalize(glm::vec3(
        glm::sin(yawAngle) * glm::cos(rad),
        glm::sin(rad),
        -glm::cos(yawAngle) * glm::cos(rad)
    ));

    // 构造无滚转正交向量组
    const glm::vec3 worldUp = glm::vec3(0, 1, 0);
    glm::vec3 right = glm::normalize(glm::cross(
相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 13:15:31