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

如何在基于raymarching的3D分形渲染场景中正确实现相机平移与旋转

替换欧拉角存储为四元数

你当前用三个独立的欧拉角变量存储相机旋转,是万向节死锁的根本原因。直接改用四元数存储相机旋转状态,完全规避死锁问题,也不需要手动维护三角函数逻辑。

重构移动逻辑,直接从旋转矩阵提取局部方向

你不需要手动计算不同旋转角度下的移动分量,直接从相机的原始旋转矩阵(未求逆的版本)提取局部坐标系的右、上、前三个方向向量,按键时直接对应方向叠加位移即可。

原有渲染逻辑无需修改

你当前在着色器中将相机空间射线转到世界空间的实现是正确的,不需要调整,光照逻辑也完全不受影响,不会出现之前将相机矩阵乘入世界矩阵导致的光照偏移问题。


1. 调整相机状态定义

替换原有cameraRotateX/cameraRotateY/cameraRotateZ三个欧拉角变量:

// 用四元数存储相机旋转,初始为单位四元数
private cameraRotQuat = Quat.identity();
// 用三维向量存储相机位置
private cameraTranslate = [0, 0, 0];

如果没有四元数工具库,可以自己实现基础的四元数旋转、转矩阵逻辑,也可以引入轻量的gl-matrix库使用现成实现。

2. 重写move函数,删除所有手动三角函数计算

move(event: KeyboardEvent): void {
  // 从四元数生成未求逆的原始相机旋转矩阵
  const rawRotMat = Matrix3D.fromQuat(this.cameraRotQuat);
  // 列主序矩阵提取局部方向向量:右(第一列)、上(第二列)、前(第三列)
  // 如果你的矩阵是行主序,改为取对应行的元素即可
  const right = [rawRotMat[0], rawRotMat[1], rawRotMat[2]];
  const up = [rawRotMat[4], rawRotMat[5], rawRotMat[6]];
  const forward = [rawRotMat[8], rawRotMat[9], rawRotMat[10]];

  const moveStep = this.speed * this.clipSpaceFactor;

  switch (event.key) {
    case "w":
      this.cameraTranslate[0] += forward[0] * moveStep;
      this.cameraTranslate[1] += forward[1] * moveStep;
      this.cameraTranslate[2] += forward[2] * moveStep;
      break;
    case "s":
      this.cameraTranslate[0] -= forward[0] * moveStep;
      this.cameraTranslate[1] -= forward[1] * moveStep;
      this.cameraTranslate[2] -= forward[2] * moveStep;
      break;
    case "a":
      this.cameraTranslate[0] -= right[0] * moveStep;
      this.cameraTranslate[1] -= right[1] * moveStep;
      this.cameraTranslate[2] -= right[2] * moveStep;
      break;
    case "d":
      this.cameraTranslate[0] += right[0] * moveStep;
      this.cameraTranslate[1] += right[1] * moveStep;
      this.cameraTranslate[2] += right[2] * moveStep;
      break;
    case "q":
      this.cameraTranslate[0] += up[0] * moveStep;
      this.cameraTranslate[1] += up[1] * moveStep;
      this.cameraTranslate[2] += up[2] * moveStep;
      break;
    case "e":
      this.cameraTranslate[0] -= up[0] * moveStep;
      this.cameraTranslate[1] -= up[1] * moveStep;
      this.cameraTranslate[2] -= up[2] * moveStep;
      break;
    case "z":
      // 绕前轴左滚,直接叠加旋转到四元数
      Quat.rotateAxis(this.cameraRotQuat, this.cameraRotQuat, forward, this.sensitivity * this.speed * Math.PI / 180);
      Quat.normalize(this.cameraRotQuat, this.cameraRotQuat);
      break;
    case "c":
      // 绕前轴右滚
      Quat.rotateAxis(this.cameraRotQuat, this.cameraRotQuat, forward, -this.sensitivity * this.speed * Math.PI / 180);
      Quat.normalize(this.cameraRotQuat, this.cameraRotQuat);
      break;
  }
}

3. 调整鼠标环视逻辑

鼠标输入的旋转增量直接叠加到四元数上,不要修改欧拉角:

onMouseMove(deltaX: number, deltaY: number) {
  const rawRotMat = Matrix3D.fromQuat(this.cameraRotQuat);
  const right = [rawRotMat[0], rawRotMat[1], rawRotMat[2]];
  const mouseSens = this.mouseSensitivity * Math.PI / 180;

  // 俯仰绕相机右轴旋转
  Quat.rotateAxis(this.cameraRotQuat, this.cameraRotQuat, right, deltaY * mouseSens);
  // 航向绕世界Y轴旋转,如果要做完全无锁的太空模拟,也可以绕相机上轴
  Quat.rotateAxis(this.cameraRotQuat, this.cameraRotQuat, [0, 1, 0], deltaX * mouseSens);
  // 归一化避免精度漂移
  Quat.normalize(this.cameraRotQuat, this.cameraRotQuat);
}

4. 调整setCameraMatrix函数

仅修改旋转矩阵的生成逻辑,原有传参逻辑不变:

setCameraMatrix(): void {
  const cameraRotationMatrixLocation = this.currentContext.getUniformLocation(this.currentProgram, "u_cameraRotation");
  const cameraTranslationLocation = this.currentContext.getUniformLocation(this.currentProgram, "u_cameraTranslation");
  const foVLocation = this.currentContext.getUniformLocation(this.currentProgram, "u_foV");

  // 从四元数生成原始旋转矩阵
  let cameraRotationMatrix = Matrix3D.fromQuat(this.cameraRotQuat);
  // 原有求逆逻辑保留,用于将相机空间射线转到世界空间
  cameraRotationMatrix = Matrix3D.inverse(cameraRotationMatrix);

  const cameraPosition = [
    this.cameraTranslate[0],
    this.cameraTranslate[1],
    -this.cameraTranslate[2],
  ];

  this.currentContext.uniformMatrix4fv(cameraRotationMatrixLocation, false, cameraRotationMatrix);
  this.currentContext.uniform3fv(cameraTranslationLocation, cameraPosition);
  this.currentContext.uniform1f(foVLocation, this.foV);
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.29 07:06:08