如何用四元数存储相机旋转?基于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
相关产品推荐
相关产品推荐

