学了这么久的graphics,居然连四元数都迷迷糊糊的,真是丢人急先锋,索性花点时间相关东西理清楚。
相关内容的C++实现
复数与二维旋转
二维情形下,将某个点( x , y ) 绕原点( 0 , 0 ) 逆时针旋转θ 的变换矩阵为:
[ cos θ sin θ − sin θ cos θ ] [ x y ] = [ x cos θ − y sin θ x sin θ + y cos θ ]
也可以利用复数乘法的几何解释来完成旋转:
e i θ ( x + i y ) = ( cos θ + i sin θ ) ( x + i y ) = ( cos θ − y sin θ ) + i ( x sin θ + y cos θ )
籍由这两种旋转表示方式的等价性,可以将其拓展到三维空间。
三维旋转公式
现需要在右手系下将向量a 绕单位向量d 旋转θ 度,显然a 可以被分解为平行于d 和垂直于d 的两个分量,其中前者无需修改:
a = a ⊥ + a ∥ a ⊥ = a − a ∥ a ∥ = Proj d a = ( a ⋅ d ) d
若将e a ⊥ 视为x ′ 轴,将d 视为z ′ 轴,则求它们的叉积即可得到y ′ 轴。此时只需要将经典的二维旋转变换应用到x ′ y ′ z ′ 坐标系中的x ′ y ′ 平面上,即可求出旋转后的a ⊥ ′ ,也就求出了a ′ = a ⊥ ′ + a ∥ 。设L p = ∣ a ⊥ ∣ ,则a ⊥ 在x ′ y ′ z ′ 中的坐标为( L p , 0 , 0 ) ,此时在x ′ y ′ 平面上进行旋转变换,得到:
a ⊥ ′ = [ e x ′ e y ′ ] ( [ cos θ sin θ − sin θ cos θ ] [ L p 0 ] ) = cos θ a ⊥ + sin θ ( d × a ⊥ ) = cos θ a ⊥ + sin θ ( d × a )
从而旋转后的a ′ 为
a ′ = a ⊥ + a ∥ ′ = ( 1 − cos θ ) ( a ⋅ d ) d + cos θ a + sin θ ( d × a )
四元数
顾名思义,四元数包含一个实部和三个虚部,虚部单位分别以i , j , k 表示,它们满足:
ij = k , jk = i , ki = j , i 2 = j 2 = k 2 = − 1
四元数被定义为1 , i , j , k 的线性组合(没错,四元数定义到这就算结束了),加法减法乘法共轭以及范数都可以直接拓展复数的相关定义得到:
( a + ib + j c + k d ) + ( e + i f + j g + kh ) ( a + ib + j c + k d ) − ( e + i f + j g + kh ) ( a + ib + j c + k d ) × ( e + i f + j g + kh ) ( a + ib + j c + k d ) ∗ ∣ a + ib + j c + k d ∣ = ( a + e ) + i ( b + f ) + j ( c + g ) + k ( d + h ) = ( a − e ) + i ( b − f ) + j ( c − g ) + k ( d − h ) = ( a e − b f − c g − d h ) + i ( b e + a f − d g + c h ) + j ( ce + df + a g − bh ) + k ( d e − c f + b g + a g ) = a − ib − j c − k d = a 2 + b 2 + c 2 + d 2
四元数a + ib + j c + k d 常被记作[ a , v ] ,其中:
v = b c d
可以验证:
[ s , v ] × [ t , u ] [ s , v ] + [ t , u ] [ s , v ] ∗ ∣ [ s , v ] ∗ ∣ = [ s t − v ⋅ u , s u + t v + v × u ] = [ s + t , v + u ] = [ s , − v ] ∗ = ∣ [ s , v ] ∣
最后来个逆:四元数q 的逆是指满足q q − 1 = q − 1 = 1 的四元数q − 1 。根据q ∗ 和q 的长度关系,有:
∣ q ∣ 2 q q ∗ = 1 ⇒ q − 1 = ∣ q ∣ 2 q ∗
特别地,单位四元数的逆就是其共轭。
四元数和三维旋转
根据之前的讨论,有:
a ⊥ ′ = cos θ a ⊥ + sin θ ( d × a ⊥ )
若令四元数a ⊥ = [ 0 , a ⊥ ] , d = [ 0 , d ] ,则d a ⊥ = [ 0 , d × a ⊥ ] ,于是a ⊥ ′ = [ 0 , a ⊥ ′ ] 满足:
a ⊥ ′ = [ 0 , cos θ a ⊥ + sin θ ( d × a ⊥ )] = cos θ a ⊥ + sin θ ( d a ⊥ ) = ( cos θ + sin θ d ) a ⊥
可见这一旋转可以认为是将四元数cos θ + sin θ d 作用到a ⊥ 上得到的。
验证可知若d 为单位向量,则[ cos θ , sin θ d ] 2 = [ cos ( 2 θ ) , sin ( 2 θ ) d ] ,其意义很直观——如果把单位四元数q 视为一个旋转变换,那么q 2 相当于进行两次这样的变换,即将旋转角翻倍。基于此,若令:
a ′ a ∥ p = [ 0 , a ] = [ 0 , a ∥ ] = [ cos ( 2 1 θ ) , sin ( 2 1 θ ) d ]
则有:
a ′ = a ∥ + pp a ⊥ = p p − 1 a ∥ + pp a ⊥ = p p ∗ a ∥ + pp a ⊥
容易验证p a ∥ = a ∥ p ,p a ⊥ = a ⊥ p ∗ ,代入上式得:
a ′ = p a ∥ p ∗ + p a ⊥ p ∗ = p ( a ∥ + a ⊥ ) p ∗ = p a p ∗
这就得到了用四元数进行绕任意轴旋转的公式。
球面线性插值
我们通常用旋转矩阵来进行三维空间中的旋转操作,但这在需要对旋转角进行插值时(比如涉及到旋转的动画)并不好用,因为这不能通过直接对矩阵元素进行线性插值来实现。四元数则提供了一种方便的插值方式——由于旋转角θ 在四元数中直接出现,因此可以把将θ 线性插值的四元数用于旋转操作,这将提供一个平滑的旋转过程,称为球面线性插值(Spherical Linear Interpolation,Slerp)。
设初始旋转四元数是q ,结束旋转四元数是q ′ ,插值系数为t ∈ [ 0 , 1 ] ,若令:
Δ q = q ′ ⇒ Δ = q ′ q − 1 = q ′ q ∗
则Δ 也是一个旋转四元数,它必然具有[ cos ( 2 1 θ ) , sin ( 2 1 θ ) d ] 的形式,此时对Δ 的旋转角在[ 0 , 2 1 θ ] 间进行线性插值即可,即:
q ( t ) = [ cos ( 2 t θ ) , sin ( 2 t θ ) d ]
在实际应用中,Slerp有个小坑——朝某个方向旋转θ 角度和朝它的反方向旋转2 π − θ 在结果上是等价的,在插值过程中却表现得完全不同,因此,若q 和q ′ 间的夹角超过π ,即q ⋅ q ′ < 0 ,则有必要将q ′ 换成− q ′ 后再进行插值。