🎯 OrbitControls 的完整原理(最精简 + 最准确版)
OrbitControls 的核心目标很简单:
让相机围绕一个目标点(target)做旋转、缩放、平移,同时保持视角稳定。****
它的实现不是直接修改相机的 rotation,而是:
✅ 1. 使用球坐标系存储相机相对 target 的位置
OrbitControls 内部用 球坐标系 (spherical) 表示相机在球面上的位置:
| spherical 属性 | 代表含义 | 决定什么 |
|---|---|---|
| radius | 相机到 target 的距离 | 缩放(远近) |
| theta | 水平旋转(绕 Y 轴) | 左右旋转 |
| phi | 垂直旋转(从 Y+ 向下量角) | 上下旋转 |
OrbitControls 并不直接存相机 position,而是依赖 spherical。
✅ 2. 每次更新,都会根据 spherical 重新计算 camera.position
伪代码:
scss
// 球坐标转换为世界坐标
offset.setFromSpherical(spherical);
// camera.position = target + offset
camera.position.copy(target).add(offset);
// 相机永远朝向 target
camera.lookAt(target);
也就是说:
✨ 相机的位置永远落在一个以 target 为圆心的球面上。****
✅ 3. 用户的三种交互对应修改 spherical 或 target
✔(1)旋转(左键)
修改 spherical 的角度:
theta ← 左右拖动
phi ← 上下拖动
效果:相机绕 target 转圈。
✔(2)缩放(滚轮)
修改 spherical.radius:
makefile
radius += delta
效果:相机沿着射线(camera → target)前进或后退。
✔(3)平移(右键)
修改 target(以及 camera.position 同步移动):
arduino
target += panDelta
camera.position += panDelta
这样保持相机与 target 的相对距离不变,视野整体平移。
📌 Why?为什么不直接改 camera.rotation?
因为:
-
欧拉角 rotation 会出现万向节锁 (gimbal lock)
-
旋转结果顺序依赖 Euler 的 order
-
不能保证相机固定绕某点旋转
-
平移与旋转混合会很混乱
使用 spherical + lookAt(target) 是最稳定、最可控的方案。
🎨 图示(概念图)
perl
Y+
|
| camera ●
| (theta, phi, radius)
| /
| /
| /
| /
●----+------------------ X+
target
📌 相机始终在半径为 radius 的球面上
📌 用户的操作实质是改变球坐标的位置
🎯 OrbitControls 本质上做的两件事
无论用户怎么操作,都只是修改:
-
camera.position****
-
controls.target
最终调用:
ini
camera.lookAt(target);
而不动 rotation。
📦 大总结(你可以直接放到文档里)
OrbitControls = 基于球坐标的相机系统,通过改变 spherical(旋转/缩放)和 target(平移),生成 camera.position,然后使用 lookAt 保持视角稳定。****
-
旋转 = 改 theta 和 phi
-
缩放 = 改 radius
-
平移 = 改 target
相机不会直接修改 rotation,而是由 target 和 spherical 决定最终的相机朝向和位置。