更新于 

Gimbal Lock

Gimbal Lock

环架锁定

三维物体旋转有很多种方式,其中有一种叫做欧拉角的方式,其特点就是将一次旋转分为3次旋转,

这三次旋可以是XYZ、XZY、ZYX……等等顺序,这个不重要,

但是第一次旋转的那个轴总是世界坐标轴,也就是不变的,旋转角度称为静态欧拉角

静态欧拉角
静态欧拉角

其余的两次旋转则是相对物体自身的坐标轴,旋转角度称为动态欧拉角

动态欧拉角与维度重叠
动态欧拉角与维度重叠

区别就是:
动态欧拉角坐标系旋转时,其余两个坐标系都会随物体转动而转动,
而静态欧拉角转动时则不会。

假设三个轴旋转的顺序是ABC,
A轴作为第一个旋转轴,其使用静态欧拉角旋转,旋转时BC坐标轴不变,

B轴旋转时,为了保证A轴刚才的旋转不丢失,A轴也会随物体一起旋转,但C轴不变

C旋转时,为了保证AB旋转角度不丢失,AB都会随物体一起旋转,

但是C轴本身是静止的,能够旋转的只有AB轴而已,

万向锁问题出现在第二步:B轴带动A轴转动上,
这时如果B轴转动的角度是±90°,会导致BC轴重叠,
造成维度丢失的问题。

万向锁的解决方式

四元组

通过四元组旋转物体,可以解决万向锁。

1
2
3
4
5
6
7
8
// XYZ模式下 沿Y、Z轴的旋转
const quaternionY = new THREE.Quaternion()
quaternionY.setFromAxisAngle(new THREE.Vector3(0,1,0).normalize(),Math.PI/2)
cloneMonkey.applyQuaternion(quaternionY)

const quaternionZ = new THREE.Quaternion()
quaternionZ.setFromAxisAngle(new THREE.Vector3(0,0,1).normalize(),Math.PI/2)
cloneMonkey.applyQuaternion(quaternionZ)
四元组的其他用法

同时四元组还可以用来实现物体沿任意(而非坐标轴)轴线旋转的功能:

左侧为四元组旋转,右侧为欧拉旋转
左侧为四元组旋转,右侧为欧拉旋转

如果将旋转效果应用到position而不是rotation上,
能够实现围绕世界坐标系旋转的效果:

1
2
3
const quaternionO = new THREE.Quaternion()
.setFromAxisAngle(new THREE.Vector3(0,1,0).normalize(), 0.01)
model.position.applyQuaternion(quaternionO)

相机的旋转也是同样的道理:

1
2
3
4
5
6
function rotateCamera(){
const quaternionO = new THREE.Quaternion()
quaternionO.setFromAxisAngle(new THREE.Vector3(0,1,0).normalize(), -0.01)
camera.position.applyQuaternion(quaternionO)
camera.lookAt(0,0,0)
}

rotateTowards && slerp

setFromAxisAngle可以通过指定坐标轴和旋转角来配置四元组,

rotateTowards需要两个参数:

  • q Quaternion类型
  • step Float类型

可以将当前的quaternion按照step逐步移动接近最终的q数值:

1
cube.quaternion.rotateTowards(cube2.quaternion, 0.01)

使用slerp也是相同的方式:

1
cube.quaternion.slerp(cube2.quaternion, 0.1)

slerp的第二个参数控制单次旋转量,能实现更加丝滑的旋转效果

左侧是slerp实现的旋转效果
左侧是slerp实现的旋转效果