如何在基于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
相关产品推荐
相关产品推荐

