前段时间看了一些网上关于三维旋转的文章和内容,发现很多人都会从欧拉角讲到四元数,洋洋洒洒讲了很多概念,看完后还是很难建立起清晰的认知以及如何正确使用。所以我就打算来写这么一篇文章,从程序开发的角度(其他领域可能对旋转的认识有所不同)把常见的4种表达方式(欧拉角、矩阵、轴角旋转、四元数)理一下。
表达方式的不同类型
首先我们这里定义两种表达方式的类型:几何表达和数学表达(名字瞎起的)。
几何表达
几何表达指的是通过这个表达方式可以立马在大脑里想象出来是如何旋转的,很明显上面4种表达方式中只有欧拉角和轴角旋转可以立马想象出旋转。
R(Euler): x=45°, y=45°, z=45°, order=xyz
表达的就是先绕x轴转45度,然后绕y转45度,最后绕z转45°可以得到结果。
R(Axis-angle): axis=(0, 0, 1), angle=45°
表达的就是绕(0, 0, 1)这个轴旋转45度
几何表达很直观,但是计算多个角度的组合是十分常见的场景,用这种方式计算十分麻烦,你可以尝试用几何知识计算一下一个物体绕 axis=(0,0,1),angle=45°
和 axis(1,0,0),angle=45°
旋转后结果如何使用轴角的方式表示。
数学表达
数学表达不像几何表达那么直观,但是这种表达方式在计算多个角度组合时十分方便
比如上面提到的axis=(0,0,1),angle=45°
对应的矩阵M(R1)
长这样
$$\begin{bmatrix}0.9996242168594817 & -0.027412133592044294 & 0 \\ 0.027412133592044294 & 0.9996242168594817 & 0 \\ 0 & 0 & 1 \end{bmatrix}$$
axis(1,0,0),angle=45°
对应的矩阵M(R2)
长这样
$$\begin{bmatrix}1 &0&0 \\ 0&0.9996242168594817&-0.027412133592044294 \\ 0&0.027412133592044294&0.9996242168594817 \end{bmatrix}$$
然后要计算这两个旋转的组合直接通过矩阵乘法即可 M(R2) x M(R1)
(矩阵从右到左应用,先进行的旋转在右侧)。
四元数有点像矩阵,应用以及组合都是通过四元数乘法,左乘右乘的性质和矩阵一样,具体会在下文介绍。
实际应用
接下来看看这两种表达方式在实际的应用中是怎么分工的。
假设我们需要实现一个三维旋转控件,用户可以通过拖拽来对模型进行旋转,类似下图显示的blender里的旋转控件,拖红色圈使物体绕X轴旋转,绿色Y轴,蓝色Z轴(注意这里的轴都是全局坐标系)。
这里的流程本质就是用户输入 几何表达(轴角旋转),然后在程序内部将其转换为 数学表达,并将其与之前的旋转合并 R(输入旋转) x R(当前旋转)
,最后转化为变换矩阵来渲染模型。
另外在blender右侧会显示当前模型的旋转信息,这里就是将 数学表达 转换为 几何表达 显示给用户了。(这里直接显示四元数可能是有什么特殊目的,因为Axis Angle和四元数之间是可以直接转换的)
可以说 几何表达 是更接近 交互 的方式,而 数学表达 则是更偏向 计算 的方式。
欧拉角与万向节死锁
提到欧拉角大家一定会提起的是万向节死锁,然后再提到四元数。(已经成为固定思维了)
关于四元数下面一节再提,先谈谈万向节死锁的问题,这个问题的原因是什么?会带来什么影响?
万向节死锁的原因
解释万向节死锁时很多人喜欢带上这种图
我一直认为这种图很难理解,主要是因为很难把欧拉角与其对应,我更建议看一下这篇基于公式的解释。
一句话解释就是,假设欧拉角以XYZ的顺序旋转,当Y的旋转使旋转后的Z与旋转前的X重合时,那么就产生了万向节死锁,此时XZ轴本质是对全局坐标系下同一个轴的旋转。
万向节死锁的影响
那么丢失一个自由度会带来什么实际问题呢?
这个鲜有文章提及,我这里就提一点我遇到的吧。
当用户在输入R(Euler): x=0°, y=90°, z=45°, order=xyz
这么一个欧拉角后,在程序内部转换为 数学表达 后再转回欧拉角就会遇到问题了。很明显这是存在万向节死锁的一个欧拉角,所有 x + z == 45°
的旋转都是相等的,在转回来时是无法还原原来的角度的。
为什么需要四元数?
我一直不太理解四元数为什么总会和欧拉角一起被提及,首先他们是不同类型的表达,不应被相提并论。我认为正确的说法应是欧拉角存在万向节死锁的问题,需要使用轴角旋转来解决,而四元数则是轴角旋转的数学表达方式。
四元数基础
三言两语也解释的不是很清楚,建议有空看下这篇文章。
与矩阵对比
轴角旋转能转换为矩阵和四元数,那么如何选择使用哪种方式呢?
单纯的旋转计算上四元数会有一定的性能优势,但是如果涉及其他变换(偏移、缩放等)还是矩阵会快一点,看实际的使用场景选择吧。
另外四元数有个独特的特性就是插值计算很方便,目前旋转的插值实现基本都会使用四元数进行插值,所以如果是用于旋转动画的数据建议直接使用四元数,否则还有转换的成本。