万向节死锁的理解(Gimbal Lock)
最新在看Steve LaValle的Virtual Reality,里面讲到了矩阵旋转,于是想到之前做项目看的万向节死锁弄的还不是很明白,于是又翻资料看了一下,下面谈一下对其的理解。
Gimbal Lock的原理就不再赘述了,自己主要是对介绍视频(戳我)中讲的现象有点不是很理解。视频讲到Gimbal Lock的时候提到x,y,z三个坐标轴是有从属关系(hierarchical system)的,也就是说对于如图所示的系统,绕y轴旋转的时候x,z轴都会随着改变;绕x旋转的时候z轴会改变;绕z旋转的时候y,x都不会变,正是因为这样,所以当物体绕x旋转90°时y轴与z轴会处在同一平面,此时DoF就会减少1,导致了Gimbal Lock的现象。
但是仔细思索会想到,对于一个物体旋转以后,世界坐标系是一直不会改变的,而局部坐标系是随着物体位置变化而改变的,这也意味着x,y,z轴都会随着物体的旋转而改变,那这种情况下视频中提到的这种情况实际上是不会出现的,那么Gimbal Lock到底是怎么回事呢?
首先我们以Unity3D来看看物体在其中是如何旋转的,别人的博客中提到
Editor中Transform组件的旋转轴是父节点的模型空间坐标轴,如果没有父节点,则旋转轴是世界空间坐标轴
脚本中用Rotate函数在Space.World中进行旋转,旋转轴就是世界坐标系的坐标轴
脚本中用Rotate函数在Space.Self中进行旋转,旋转轴就是局部坐标系的坐标轴
并且在文章中还做了实验:
1. 静态欧拉角
这种情况对应着上面所述的使用Space.World进行旋转,以及Inspector中的旋转。即使旋转轴在旋转的过程中保持不变,旋转的顺序会决定最后的旋转结果。我们看下面的例子会很清晰的理解:
- 情形一:首先绕世界坐标系的x轴旋转90度,再绕世界坐标系的y轴旋转90度
- 情形二:首先绕世界坐标系的y轴旋转90度,再绕世界坐标系的x轴旋转90度
可以看到,由于旋转顺序的不同,最终导致了旋转结果的不同!(究其本质,是因为矩阵乘法不满足交换律)
但是在我自己在Inspector测试中发现不管第一种情形还是第二种情形旋转的结果是一样的!(脚本的情况我没测试,文章中提到的现象应该是针对的是脚本的情况)然后我做了第二个实验,想看物体在Inspector中到底是依照世界坐标系旋转还是局部坐标系旋转,然后发现好像物体有时候是绕世界坐标旋转的,有时候是绕局部坐标旋转的,有时候好像都不是绕这二者旋转的,那到底是什么回事呢?
在一番测试后得出了结论:假设物体初始状态(没有任何旋转)是A,将Rotation的值变为(x1,y1,z1)后得到的状态是B,此时B的位置是是按照z-x-y的顺序(Unity文档中提到的顺规)将物体从初始状态A依次绕z轴,y轴,x轴(世界坐标系)旋转z1,y1,x1角度得到的。若此时再将Rotation的值变为(x1,y1,z2),称为状态C,那么实际上物体发生的变化不是在B的基础上绕z轴旋转z2-z1角度,而是重新在A状态的基础上重新计算了旋转角(x1,y1,z2),得到状态C。再将物体从现状态B变换过去,所以我们观察到的现象是物体不一定是按照局部坐标系或者世界坐标系旋转的。
在有了上述的知识铺垫之后我们就能对在Inspector旋转出现的Gimbal Lock现象做出解释了。我们先将Rotation中x的值改为90°,然后再改变y的值与z的值,会发现改变这两个值物体都是在同一个方向上进行旋转的,这种现象就是我们遇到的Gimbal Lock。原因就是实际上在每一次有新的Rotation值的时候Unity都重新进行了一次计算,对于旋转角度为(90,y1,z1)时,进行的过程是先先将物体绕z轴旋转z1°,再绕x轴旋转90°,此时z轴转到了现在世界坐标系y轴的位置上,意思就是说接下来在y轴旋转y1°的操作与原来绕z轴旋转z1°实际上是在同一个方向,这也就是所谓的在世界坐标系下旋转(静态欧拉角)的Gimbal Lock现象了。局部坐标系下旋转(动态欧拉角)的情况之前的参考文章也有提到,与世界坐标系不同的是物体以当前旋转的坐标作为状态A再进行操作,其他过程与上述情况类似,产生Gimbal Lock的情况也是类似的,就不再说了
图1:初始位置 图2:绕x轴旋转90°后z轴所在的位置与世界坐标系下z轴位置重合
总结一下,视频中提到的从属关系实际上说的是三维旋转的顺规问题,也正是由于这种机制,物体在某个旋转方向上会失去一个DoF,这种现象就是Gimbal Lock。解决方法就是采用四元数旋转