0%

08 - 四元数

1. 四元数记法

一个四元数包含一个 标量分量(实部) 和一个 3D 向量分量(虚部)

经常记标量分量为 ww ,记向量分量为单一的 v=(x,y,z)\bold{v} = (x, y, z)
两种记法分别如下:

{[w,v][w,(x,y,z)]\begin{cases} [w, \bold{v}] \\[1.5ex] [w, (x, y, z)] \end{cases}

2. 复数

复数 对一个二维坐标 (a,b)(a, b) 定义为数 a+bia + biii 即称为 虚数 ,其中:

  • i2=1i^2 = -1
  • aa 称作实部, bb 称作虚部

NOTE

  • 实数集 自身也包含于 复数集 中,任何实数 kk 都能表示为复数 (k,0)=k+0i(k, 0) = k + 0 \cdot i

2.1 复数的计算

复数能够相加、相减、相乘

(a+bi)+(c+di)=(a+c)+(b+d)i(a+bi)(c+di)=(ac)+(bd)i(a+bi)(c+di)=ac+adi+bci+bdi2=(acbd)+(ad+bc)i\begin{aligned} (a + bi) + (c + di) &= (a + c) + (b + d)i \\[2ex] (a + bi) - (c + di) &= (a - c) + (b - d)i \\[2ex] (a + bi) \cdot (c + di) &= ac + ad \cdot i + bc \cdot i + bd \cdot i^2 \\ &= (ac - bd) + (ad + bc) \cdot i \end{aligned}

2.2 共轭复数

通过使虚部变负,获得复数的 共轭

{p=(a+bi)p=(abi)\begin{cases} \bold{p} = (a + bi) \\[1.5ex] \bold{p^*} = (a - bi) \end{cases}

2.3 复数的模

p=ppa+bi=(a+bi)(a+bi)=(a+bi)(abi)=a2+b2\begin{aligned} \Vert {\bold{p}} \Vert &= \sqrt{\bold{p} \cdot \bold{p^*}} \\[2ex] \Vert {a + bi} \Vert &= \sqrt{(a + bi)(a + bi)^*} \\ &= \sqrt{(a + bi)(a - bi)} \\ &= \sqrt{a^2 + b^2} \end{aligned}

2.4 用复数表示旋转变换

关于如何用复数来表示二维空间的线性变换,见 《01 - 线性变换》 4 使用复数表示线性变换:

  • 复数存在于一个 2D 平面上,可认为这个平面有两个轴:实轴虚轴
  • 就能将复数 (x,y)(x, y) 解释为 2D 向量,则能用来表示平面中的旋转。

复数 p\bold{p} 绕原点旋转角度 θ\theta 的情况如下:

screenShot.png

p=a+biq=cosθ+sinθip=pq=(a+bi)(cosθ+sinθi)=(acosθbsinθ)+(asinθ+bcosθ)i\begin{aligned} \bold{p} &= a + b \cdot i \\[2ex] \bold{q} &= \cos{\theta} + \sin{\theta} \cdot i \\[2ex] \bold{p'} &= \bold{p} \cdot \bold{q} \\[1.25ex] &= (a + b \cdot i) \cdot (\cos{\theta} + \sin{\theta} \cdot i) \\[1.25ex] &= (a\cos{\theta} - b\sin{\theta}) + (a\sin{\theta} + b\cos{\theta}) \cdot i \end{aligned}

3. 四元数

四元数复数 的一种扩展,如果说复数可以描述 2D 空间中的变换,那么四元数就是描述 3D 空间中的变换。

3.1 四元数虚部的计算规则

screenShot.png

{i2=j2=k2=1ij=k , ji=kjk=i , kj=iki=j , ik=j\begin{cases} i^2 = j^2 = k^2 = - 1 \\[1.5ex] ij = k \space,\space ji = - k \\[1.5ex] jk = i \space,\space kj = - i \\[1.5ex] ki = j \space,\space ik = - j \end{cases}

 

  • 各个不同的虚部相乘的结果类似于向量的 叉乘 ,方向可依此确定;
  • 一个四元数 [w,(x,y,z)][w, (x, y, z)] 定义了复数 (w+xi+yj+zk)(w + xi + yj + zk)

3.2 “ 轴 - 角对 ” 表示法

欧拉证明了一个旋转序列等价于单个旋转:
3D 中的任意角位移都能表示为绕单一轴的单一旋转(这里的轴是一般意义上的旋转轴,不是笛卡尔坐标轴,方向是任意的)。

符 号
n\bold{n} 旋转轴 由于仅表示方向,因此长度无意义,不妨设置其为单位向量
θ\theta 绕轴旋转的量 根据 “右手法则” 定义旋转的 “正” 方向

因此可以通过一个 “ 轴 - 角对 ” (n,θ)(\bold{n}, \theta) 定义一个 角位移 :绕 n\bold{n} 指定的轴旋转 θ\theta 角。

四元数能被解释为角位移的 “ 轴 - 角对 ” 表示方式

然而, n\bold{n}θ\theta 不是直接存储在四元数的四个分量中:

q=[cos( θ2 )sin( θ2 )n]=[cos( θ2 )sin( θ2 )nxsin( θ2 )nysin( θ2 )nz]\begin{aligned} \bold{q} &= \begin{bmatrix} \cos{(\space \cfrac{\theta}{2} \space)} & \sin{(\space \cfrac{\theta}{2} \space)} \cdot \bold{n} \end{bmatrix} \\[2ex] &= \begin{bmatrix} \cos{(\space \cfrac{\theta}{2} \space)} & \sin{(\space \cfrac{\theta}{2} \space)} \cdot \bold{n}_x & \sin{(\space \cfrac{\theta}{2} \space)} \cdot \bold{n}_y & \sin{(\space \cfrac{\theta}{2} \space)} \cdot \bold{n}_z \end{bmatrix} \end{aligned}

NOTE

  • q\bold{q}w\bold{w} 分量和 θ\theta 有关系,但不相同;
  • 向量 v\bold{v}n\bold{n} 同理,有关系但不相同。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
//---------------------------------------------------------------------------
// Quaternion::setToRotateAboutX
// Quaternion::setToRotateAboutY
// Quaternion::setToRotateAboutZ
// Quaternion::setToRotateAboutAxis
//
// 构造指定轴旋转的四元数

void Quaternion::setToRotateAboutX(float theta) {

// 计算半角
float thetaOver2 = theta * .5f;

// 赋值
w = cos(thetaOver2);
x = sin(thetaOver2);
y = 0.0f;
z = 0.0f;
}

void Quaternion::setToRotateAboutY(float theta) {

// 计算半角
float thetaOver2 = theta * .5f;

// 赋值
w = cos(thetaOver2);
x = 0.0f;
y = sin(thetaOver2);
z = 0.0f;
}

void Quaternion::setToRotateAboutZ(float theta) {

// 计算半角
float thetaOver2 = theta * .5f;

// 赋值
w = cos(thetaOver2);
x = 0.0f;
y = 0.0f;
z = sin(thetaOver2);
}

void Quaternion::setToRotateAboutAxis(const Vector3& axis, float theta) {

// 旋转轴必须标准化
assert(fabs(vectorMag(axis) - 1.0f) < .01f);

// 计算半角和 sin 值
float thetaOver2 = theta * .5f;
float sinThetaOver2 = sin(thetaOver2);

// 赋值
w = cos(thetaOver2);
x = axis.x * sinThetaOver2;
y = axis.y * sinThetaOver2;
z = axis.z * sinThetaOver2;
}

//---------------------------------------------------------------------------
// Quaternion::getRotationAngle
//
// 返回旋转角

float Quaternion::getRotationAngle() const {

// 计算半角,w = cos (theta/2)
float thetaOver2 = safeAcos(w);

// 返回旋转角
return thetaOver2 * 2.0f;
}

//---------------------------------------------------------------------------
// Quaternion::getRotationAxis
//
// 提取旋转轴

Vector3 Quaternion::getRotationAxis() const {

// 计算sin^2 (theta/2)
float sinThetaOver2Sq = 1.0f - w * w;

// 注意保证数值精度
if (sinThetaOver2Sq <= 0.0f) {

// 单位四元数或不精确的数值,只要返回有效的向量即可
return Vector3(1.0f, 0.0f, 0.0f);
}

// 计算 1 / sin(theta/2)
float oneOverSinThetaOver2 = 1.0f / sqrt(sinThetaOver2Sq);

// 返回旋转轴
return Vector3(
x * oneOverSinThetaOver2,
y * oneOverSinThetaOver2,
z * oneOverSinThetaOver2
);
}

3.3 负四元数

四元数求负,将每个分量都取负。

q=[w(xyz)]=[w(xyz)]=[wv]=[wv]\begin{aligned} -\bold{q} &= \begin{bmatrix} w &(x &y &z) \end{bmatrix} = \begin{bmatrix} -w &(-x &-y &-z) \end{bmatrix} \\[2ex] &= \begin{bmatrix} w &\bold{v} \end{bmatrix} = \begin{bmatrix} -w &-\bold{v} \end{bmatrix} \end{aligned}

q\bold{q}q\bold{-q} 代表的实际角位移是相同的,若我们将 θ\theta 加上 360°360° 的倍数,不会改变 q\bold{q} 代表的角位移,但它使 q\bold{q} 的四个分量都变负了。

因此,3D 中的任意角位移都有两种不同的四元数表示方法,他们互相为负。

3.4 单位四元数

几何上,存在两个 单位元 ,他们代表没有角位移:[1, 0][1,\space \bold{0}][1, 0][-1,\space \bold{0}](其中 0\bold{0} 代表零向量)。

θ\theta 的取值 形 式
θ=2k2π\theta = 2k \cdot 2\pi θ\theta360°360° 的偶数倍 cos(θ/2)=1\cos{(\theta/2)} = 1
θ=(2k+1)2π\theta = (2k + 1) \cdot 2\pi θ\theta360°360° 的奇数倍 cos(θ/2)=1\cos{(\theta/2)} = -1

显然,上述两种情况下,都有 sin(θ/2)=0\sin{(\theta/2)} = 0 ,因此 n\bold{n} 的值已经无意义了。

  • 当旋转角 θ\theta360°360° 的整数倍时,方位没有变化,因此旋转轴 n\bold{n} 无意义

数学上,实际只定义一个单位元四元数:[1, 0][1,\space \bold{0}]

{q[1, 0]=qq[1, 0]=q\begin{cases} \bold{q} \cdot [1,\space \bold{0}] = \bold{q} \\[2ex] \bold{q} \cdot [-1,\space \bold{0}] = \bold{-q} \end{cases}

  • 几何意义q\bold{q}q\bold{-q} 代表的角位移相同,结果可认为是相同的;
  • 数学意义q\bold{q}q\bold{-q} 不相等,因此 [1, 0][-1,\space \bold{0}] 不能作为单位元四元数。
1
2
3
4
5
// 全局单位四元数

const Quaternion kQuaternionIdentity = {
1.0f, 0.0f, 0.0f, 0.0f
};

3.5 四元数的模

q=[w(xyz)]=w2+x2+y2+z2=[wv]=w2+v2\begin{aligned} \Vert {\bold{q}} \Vert &= \left \Vert {\begin{bmatrix} w &(x &y &z) \end{bmatrix}} \right \Vert = \sqrt{w^2 + x^2 + y^2 + z^2} \\[2ex] &= \left \Vert {\begin{bmatrix} w &\bold{v} \end{bmatrix}} \right \Vert = \sqrt{w^2 + \Vert {\bold{v}} \Vert^2} \end{aligned}

几何意义 :代入 θ\thetan\bold{n}

q=[wv]=w2+v2=cos2(θ/2)+(sin(θ/2)n)2=cos2(θ/2)+sin2(θ/2)n2=cos2(θ/2)+sin2(θ/2)(1)=cos2(θ/2)+sin2(θ/2)=1=1\begin{aligned} \Vert {\bold{q}} \Vert &= \left \Vert {\begin{bmatrix} w &\bold{v} \end{bmatrix}} \right \Vert \\[1.5ex] &= \sqrt{w^2 + \Vert {\bold{v}} \Vert^2} \\[1.5ex] &= \sqrt{\cos^2(\theta/2) + (\sin(\theta/2)\Vert{\bold{n}}\Vert)^2} \\[1.5ex] &= \sqrt{\cos^2(\theta/2) + \sin^2(\theta/2)\Vert{\bold{n}}\Vert^2} \\[1.5ex] &= \sqrt{\cos^2(\theta/2) + \sin^2(\theta/2)(1)} \\[1.5ex] &= \sqrt{\cos^2(\theta/2) + \sin^2(\theta/2)} = \sqrt{1} = 1 \end{aligned}

若用四元数来表示方位,仅适用符合该规则的 单位四元数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
//---------------------------------------------------------------------------
// Quaternion::normalize
//
// 正则化四元数
// 通常,四元数都是正则化的(在数值精度的限制内)
//
// 提供这个函数主要是为了防止误差扩大,连续多个四元数操作可能导致误差扩大

void Quaternion::normalize() {

// 计算四元数的模
float mag = (float)sqrt(w * w + x * x + y * y + z * z);

// 检测长度,防止除零错误
if (mag > 0.0f) {

// 正则化
float oneOverMag = 1.0f / mag;
w *= oneOverMag;
x *= oneOverMag;
y *= oneOverMag;
z *= oneOverMag;

}
else {

// 出错
assert(false);

// 返回单位四元数
identity();
}
}

3.6 四元数共轭和逆

3.6.1 四元数的共轭

四元数的共轭 记作 q\bold{q}^* ,其值即四元数的虚部部分的分量变负:

q=[wv]=[wv]=[w(xyz)]=[w(xyz)]\begin{aligned} \bold{q}^* &= \begin{bmatrix} w &\bold{v} \end{bmatrix}^* = \begin{bmatrix} w &-\bold{v} \end{bmatrix} \\[1.5ex] &= \begin{bmatrix} w &(x &y &z) \end{bmatrix}^* = \begin{bmatrix} w &(-x &-y &-z) \end{bmatrix} \end{aligned}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//---------------------------------------------------------------------------
// conjugate
//
// 四元数共轭,即与原四元数旋转方向相反的四元数

Quaternion conjugate(const Quaternion& q) {
Quaternion result;

// 旋转量相同
result.w = q.w;

// 旋转轴相反
result.x = -q.x;
result.y = -q.y;
result.z = -q.z;

// 返回
return result;
}

3.6.2 四元数的逆

四元数的逆 记作 q1\bold{q^{-1}} ,定义为四元数的共轭除以它的模:

q1=qq\bold{q^{-1}} = \cfrac{\bold{q^*}}{\Vert{\bold{q}}\Vert}

其中有:

qq1=[1, 0]\bold{q} \bold{q^{-1}} = [1,\space \bold{0}]

q\bold{q} 为单位四元数,即 q=1\Vert {\bold{q}} \Vert = 1

q1=q\bold{q^{-1}} = \bold{q^*}

共轭的几何意义q\bold{q}q\bold{q^*} 代表相反的角位移。

  • v\bold{v} 变负时,旋转轴反向,旋转的方向也翻转,因此, q\bold{q} 绕轴旋转 θ\theta 角,而 q\bold{q^*} 沿相反的方向旋转相同的角度。

另一种几何意义q\bold{q}q1\bold{q^{-1}} 代表相反的角位移。

  • ww 变负时,旋转角直接变负。

3.7 四元数的乘法(叉乘)

类似于复数的乘法:

q1×q2=(w1+x1i^+y1j^+z1k^)(w2+x2i^+y2j^+z2k^)=w1w2+w1x2i^+w1y2j^+w1z2k^+x1w2i^+x1x2i^2+x1y2i^j^+x1z2i^k^+y1w2j^+y1x2j^i^+y1y2j^2+y1z2j^k^+z1w2k^+z1x2k^i^+z1y2k^j^+z1z2k^2=w1w2+w1x2i^+w1y2j^+w1z2k^+x1w2i^+x1x2(1)+x1y2(k^)+x1z2(j^)+y1w2j^+y1x2(k^)+y1y2(1)+y1z2(i^)+z1w2k^+z1x2(j^)+z1y2(i^)+z1z2(1)=w1w2x1x2y1y2z1z2+(w1x2+x1w2+y1z2z1y2)i^+(w1y2+y1w2+z1x2x1z2)j^+(w1z2+z1w2+x1y2y1x2)k^\begin{aligned} \bold{q_1} \times \bold{q_2} & = && (w_1 + x_1\hat{i} + y_1\hat{j} + z_1\hat{k})(w_2 + x_2\hat{i} + y_2\hat{j} + z_2\hat{k}) \\[1.5ex] & = && w_1w_2 + w_1x_2\hat{i} + w_1y_2\hat{j} + w_1z_2\hat{k} \\ & && + x_1w_2\hat{i} + x_1x_2\hat{i}^2 + x_1y_2\hat{i}\hat{j} + x_1z_2\hat{i}\hat{k} \\ & && + y_1w_2\hat{j} + y_1x_2\hat{j}\hat{i} + y_1y_2\hat{j}^2 + y_1z_2\hat{j}\hat{k} \\ & && + z_1w_2\hat{k} + z_1x_2\hat{k}\hat{i} + z_1y_2\hat{k}\hat{j} + z_1z_2\hat{k}^2 \\[1.5ex] & = && w_1w_2 + w_1x_2\hat{i} + w_1y_2\hat{j} + w_1z_2\hat{k} \\ & && + x_1w_2\hat{i} + x_1x_2(-1) + x_1y_2(\hat{k}) + x_1z_2(-\hat{j}) \\ & && + y_1w_2\hat{j} + y_1x_2(-\hat{k}) + y_1y_2(-1) + y_1z_2(\hat{i}) \\ & && + z_1w_2\hat{k} + z_1x_2(\hat{j}) + z_1y_2(-\hat{i}) + z_1z_2(-1) \\[1.5ex] & = && w_1w_2 - x_1x_2 - y_1y_2 - z_1z_2 \\ & && + (w_1x_2 + x_1w_2 + y_1z_2 - z_1y_2)\hat{i} \\ & && + (w_1y_2 + y_1w_2 + z_1x_2 - x_1z_2)\hat{j} \\ & && + (w_1z_2 + z_1w_2 + x_1y_2 - y_1x_2)\hat{k} \end{aligned}

四元数乘法的标准定义,以下是两种四元数记法:

  • 第一种记法:

[w1(x1y1z1)]×[w2(x2y2z2)]=[w1w2x1x2y1y2z1z2w1x2+x1w2+y1z2z1y2w1y2+y1w2+z1x2x1z2w1z2+z1w2+x1y2y1x2]\begin{aligned} & \begin{bmatrix} w_1 &(x_1 &y_1 &z_1) \end{bmatrix} \times \begin{bmatrix} w_2 &(x_2 &y_2 &z_2) \end{bmatrix} \\[2ex] & = \begin{bmatrix} w_1w_2 - x_1x_2 - y_1y_2 - z_1z_2 \\[1.25ex] \hdashline w_1x_2 + x_1w_2 + y_1z_2 - z_1y_2 \\[1.25ex] w_1y_2 + y_1w_2 + z_1x_2 - x_1z_2 \\[1.25ex] w_1z_2 + z_1w_2 + x_1y_2 - y_1x_2 \end{bmatrix} \end{aligned}

  • 第二种记法:

[w1v1]×[w2v2]=[w1w2v1v2w1v2+w2v1+v2×v1]\begin{aligned} & \begin{bmatrix} w_1 &\bold{v_1} \end{bmatrix} \times \begin{bmatrix} w_2 &\bold{v_2} \end{bmatrix} \\[2ex] & = \begin{bmatrix} w_1w_2 - \bold{v_1} \cdot \bold{v_2} &w_1\bold{v_2} + w_2\bold{v_1} + \bold{v_2} \times \bold{v_1} \end{bmatrix} \end{aligned}

四元数乘法满足结合律不满足交换律:

{(a×b)×c=a×(b×c)a×bb×a\begin{cases} (\bold{a} \times \bold{b}) \times \bold{c} = \bold{a} \times (\bold{b} \times \bold{c}) \\[2ex] \bold{a} \times \bold{b} \ne \bold{b} \times \bold{a} \end{cases}

两四元数叉乘的模:

q1×q2=[w1(x1y1z1)]×[w2(x2y2z2)]=[w1w2x1x2y1y2z1z2w1x2+x1w2+y1z2z1y2w1y2+y1w2+z1x2x1z2w1z2+z1w2+x1y2y1x2]=(w1w2x1x2y1y2z1z2)2+(w1x2+x1w2+y1z2z1y2)2+(w1y2+y1w2+z1x2x1z2)2+(w1z2+z1w2+x1y2y1x2)2=w12w22+x12x22+y12y22+z12z22+w12x22+x12w22+y12z22+z12y22+w12y22+y12w22+z12x22+x12z22+w12z22+z12w22+x12y22+y12x22=w12(w22+x22+y22+z22)+x12(w22+x22+y22+z22)+y12(w22+x22+y22+z22)+z12(w22+x22+y22+z22)=(w12+x12+y12+z12)(w22+x22+y22+z22)\begin{aligned} \Vert {\bold{q_1} \times \bold{q_2}} \Vert & = \left \Vert {\begin{bmatrix} w_1 &(x_1 &y_1 &z_1) \end{bmatrix} \times \begin{bmatrix} w_2 &(x_2 &y_2 &z_2) \end{bmatrix}} \right \Vert \\[2ex] & = \left \Vert \begin{bmatrix} w_1w_2 - x_1x_2 - y_1y_2 - z_1z_2 \\[1.25ex] \hdashline w_1x_2 + x_1w_2 + y_1z_2 - z_1y_2 \\[1.25ex] w_1y_2 + y_1w_2 + z_1x_2 - x_1z_2 \\[1.25ex] w_1z_2 + z_1w_2 + x_1y_2 - y_1x_2 \end{bmatrix} \right \Vert \\[2ex] & = \sqrt{ \begin{aligned} (w_1w_2 - x_1x_2 - y_1y_2 - z_1z_2)^2 \\ +(w_1x_2 + x_1w_2 + y_1z_2 - z_1y_2)^2 \\ +(w_1y_2 + y_1w_2 + z_1x_2 - x_1z_2)^2 \\ +(w_1z_2 + z_1w_2 + x_1y_2 - y_1x_2)^2 \end{aligned} } \\[2ex] & = \sqrt{ \begin{aligned} w_1^2 w_2^2 + x_1^2 x_2^2 + y_1^2 y_2^2 + z_1^2 z_2^2 \\ +w_1^2 x_2^2 + x_1^2 w_2^2 + y_1^2 z_2^2 + z_1^2 y_2^2 \\ +w_1^2 y_2^2 + y_1^2 w_2^2 + z_1^2 x_2^2 + x_1^2 z_2^2 \\ +w_1^2 z_2^2 + z_1^2 w_2^2 + x_1^2 y_2^2 + y_1^2 x_2^2 \end{aligned} } \\[2ex] & = \sqrt{ \begin{aligned} w_1^2 (w_2^2 + x_2^2 + y_2^2 + z_2^2 ) \\ +x_1^2 (w_2^2 + x_2^2 + y_2^2 + z_2^2 ) \\ +y_1^2 (w_2^2 + x_2^2 + y_2^2 + z_2^2 ) \\ +z_1^2 (w_2^2 + x_2^2 + y_2^2 + z_2^2 ) \end{aligned} } \\[2ex] & = \sqrt{ (w_1^2 + x_1^2 + y_1^2 + z_1^2)(w_2^2 + x_2^2 + y_2^2 + z_2^2) } \end{aligned}

四元数乘积的模等于模的乘积:

  • 两个单位四元数相乘的结果还是单位四元数

q1×q2=(w12+x12+y12+z12)(w22+x22+y22+z22)=q12q22=q1q2\begin{aligned} \Vert {\bold{q_1} \times \bold{q_2}} \Vert & = \sqrt{ (w_1^2 + x_1^2 + y_1^2 + z_1^2)(w_2^2 + x_2^2 + y_2^2 + z_2^2) } \\[2ex] & = \sqrt{\Vert {\bold{q_1}} \Vert^2 \Vert {\bold{q_2}} \Vert^2} \\[2ex] & = \sqrt{\Vert {\bold{q_1}} \Vert \Vert {\bold{q_2}} \Vert} \end{aligned}

四元数乘积的逆等于各个四元数的逆以相反的顺序相乘:

{(ab)1=b1a1(q1q2qn1qn)1=qn1qn11q21q11\begin{cases} (\bold{a} \bold{b})^{-1} = \bold{b}^{-1} \bold{a}^{-1} \\ (\bold{q_1} \bold{q_2} \cdots \bold{q_{n-1}} \bold{q_{n}})^{-1} = \bold{q_{n}}^{-1} \bold{q_{n-1}}^{-1} \cdots \bold{q_{2}}^{-1} \bold{q_{1}}^{-1} \end{cases}

3.7.1 四元数的旋转变换

“扩展”一个标准 3D 点 (x,y,z)(x, y, z) 到四元数空间,通过定义四元数 p=[0,(x,y,z)]\bold{p} = [0, (x, y, z)] 即可(此处 p\bold{p} 不用必须是单位四元数)。

q\bold{q} 为旋转四元数 q=[cos(θ/2), ncos(θ/2)]q = [\cos{(\theta/2)}, \space \bold{n}\cos{(\theta/2)}]

  • 其中,n\bold{n} 为旋转轴,单位向量 ; θ\theta 为旋转角。

使用下式可使 3D 点 p\bold{p}n\bold{n} 旋转:

p=qpq1\bold{p'} = \bold{q}\bold{p}\bold{q}^{-1}

将点 p\bold{p} 用一个四元数 a\bold{a} 旋转再用四元数 b\bold{b} 旋转:

p=b(apa1)b1=(ba)p(a1b1)=(ba)p(ba)1\begin{aligned} \bold{p'} &= \bold{b}(\bold{a}\bold{p}\bold{a}^{-1})\bold{b}^{-1} \\ &= (\bold{b}\bold{a})\bold{p}(\bold{a}^{-1}\bold{b}^{-1}) \\ &= (\bold{b}\bold{a})\bold{p}(\bold{b}\bold{a})^{-1} \end{aligned}

  • 先进行 a\bold{a} 旋转再进行 b\bold{b} 旋转等价于执行乘积 ba\bold{b}\bold{a} 代表的单一旋转。

3.7.2 重新定义四元数乘法

[w1(x1y1z1)]×[w2(x2y2z2)]=[w1w2x1x2y1y2z1z2w1x2+x1w2+z1y2y1z2w1y2+y1w2+x1z2z1x2w1z2+z1w2+y1x2x1y2]\begin{aligned} & \begin{bmatrix} w_1 &(x_1 &y_1 &z_1) \end{bmatrix} \times \begin{bmatrix} w_2 &(x_2 &y_2 &z_2) \end{bmatrix} \\[2ex] & = \begin{bmatrix} w_1w_2 - x_1x_2 - y_1y_2 - z_1z_2 \\[1.25ex] \hdashline w_1x_2 + x_1w_2 + z_1y_2 - y_1z_2 \\[1.25ex] w_1y_2 + y_1w_2 + x_1z_2 - z_1x_2 \\[1.25ex] w_1z_2 + z_1w_2 + y_1x_2 - x_1y_2 \end{bmatrix} \end{aligned}

此时运算顺序相反:

p=b1(a1pa)b=(b1a1)p(ab)=(ab)1p(ab)\begin{aligned} \bold{p'} &= \bold{b}^{-1}(\bold{a}^{-1}\bold{p}\bold{a})\bold{b} \\ &= (\bold{b}^{-1}\bold{a}^{-1})\bold{p}(\bold{a}\bold{b}) \\ &= (\bold{a}\bold{b})^{-1}\bold{p}(\bold{a}\bold{b}) \end{aligned}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//---------------------------------------------------------------------------
// Quaternion::operator *
//
// 四元数叉乘运算,用以连接多个角位移
// 乘的顺序是从左向右
// 这和四元数叉乘的“标准”定义相反

Quaternion Quaternion::operator *(const Quaternion& a) const {
Quaternion result;

result.w = w * a.w - x * a.x - y * a.y - z * a.z;
result.x = w * a.x + x * a.w + z * a.y - y * a.z;
result.y = w * a.y + y * a.w + x * a.z - z * a.x;
result.z = w * a.z + z * a.w + y * a.x - x * a.y;

return result;
}

//---------------------------------------------------------------------------
// Quaternion::operator *=
//
// 叉乘并赋值

Quaternion& Quaternion::operator *=(const Quaternion& a) {

// 乘并赋值
*this = *this * a;

// 返回左值
return *this;
}

3.8 四元数的差值

利用四元数的乘法和逆,计算两个四元数的“差值”。“差值”被定义为一个方位到另一个方位的角位移。

给定方位 a\bold{a}b\bold{b} ,能够计算从 a\bold{a} 旋转到 b\bold{b} 的角位移 d\bold{d}

da=b\bold{d}\bold{a} = \bold{b}

可推出:

(da)a1=ba1d=ba1\begin{aligned} (\bold{d}\bold{a})\bold{a}^{-1} &= \bold{b}\bold{a}^{-1} \\[1.5ex] \bold{d} &= \bold{b}\bold{a}^{-1} \end{aligned}

3.9 四元数点乘

q1q2=[w1v1][w2v2]=w1w2+v1v2=[w1(x1y1z1)][w2(x2y2z2)]=w1w2+x1x2+y1y2+z1z2\begin{aligned} \bold{q_1} \cdot \bold{q_2} &= \begin{bmatrix} w_1 &\bold{v_1} \end{bmatrix} \cdot \begin{bmatrix} w_2 &\bold{v_2} \end{bmatrix} \\[1.25ex] &= w_1w_2 + \bold{v_1} \cdot \bold{v_2} \\[2ex] &= \begin{bmatrix} w_1 &(x_1 &y_1 &z_1) \end{bmatrix} \cdot \begin{bmatrix} w_2 &(x_2 &y_2 &z_2) \end{bmatrix} \\[1.25ex] &= w_1w_2 + x_1x_2 + y_1y_2 + z_1z_2 \end{aligned}

和向量点乘一样,结果是标量:

  • 对于单位四元数 a\bold{a}b\bold{b} ,有 1ab1-1 \leqslant \bold{a} \cdot \bold{b} \leqslant 1
  • 由于四元数的正负号不影响其代表的角位移,因此结果往往只关心其绝对值 ab| \bold{a} \cdot \bold{b} |
1
2
3
4
5
6
7
8
9
//---------------------------------------------------------------------------
// dotProduct
//
// 四元数点乘
// 用非成员函数实现四元数点乘以避免在表达式中使用时出现 “ 怪异语法 ”

float dotProduct(const Quaternion& a, const Quaternion& b) {
return a.w * b.w + a.x * b.x + a.y * b.y + a.z * b.z;
}

几何解释

  • 四元数点乘的几何解释类似于向量点乘的几何解释。
  • 四元数点乘的绝对值 ab| \bold{a} \cdot \bold{b} | 越大, a\bold{a}b\bold{b} 代表的角位移越“相似”。

3.10 四元数的对数、指数和标量乘运算

重写四元数定义,引入一个新的变量 α=θ/2\alpha = \theta/2

q=[cosαnsinα]=[cosαnxsinαnysinαnzsinα]\begin{aligned} \bold{q} &= \begin{bmatrix} \cos{\alpha} &\bold{n} \sin{\alpha} \end{bmatrix} \\[1.25ex] &= \begin{bmatrix} \cos{\alpha} &\bold{n}_x \sin{\alpha} &\bold{n}_y \sin{\alpha} &\bold{n}_z \sin{\alpha} \end{bmatrix} \end{aligned}

3.10.1 四元数的对数

logq=log([cosαnsinα])[0αn]\begin{aligned} \log{\bold{q}} &= \log{(\begin{bmatrix} \cos{\alpha} &\bold{n} \sin{\alpha} \end{bmatrix})} \\[1.25ex] &\equiv \begin{bmatrix} 0 &\alpha \bold{n} \end{bmatrix} \end{aligned}

其中的结果 [0αn]\begin{bmatrix} 0 &\alpha \bold{n} \end{bmatrix} 一般不会是单位四元数。

四元数的对数运算中,有如下的映射关系:

(w,v)log(θ,n)(w,\bold{v}) \xmapsto{\log} (\theta,\bold{n})

3.10.2 四元数的指数

令四元数 p\bold{p} 如下:

p=[0αn]=[0(αnxαnyαnz)]\begin{aligned} \bold{p} &= \begin{bmatrix} 0 &\alpha \bold{n} \end{bmatrix} \\[1.25ex] &= \begin{bmatrix} 0 &(\alpha \bold{n}_x &\alpha \bold{n}_y &\alpha \bold{n}_z) \end{bmatrix} \end{aligned}

指数定义为:

ep=expp=exp([0αn])[cosαnsinα]\begin{aligned} e^{\bold{p}} = \exp{\bold{p}} &= \exp{(\begin{bmatrix} 0 &\alpha \bold{n} \end{bmatrix})} \\[1.25ex] &\equiv \begin{bmatrix} \cos{\alpha} &\bold{n} \sin{\alpha} \end{bmatrix} \end{aligned}

根据定义,expp\exp{\bold{p}} 总是返回单位四元数。

四元数指数运算为四元数对数运算的逆运算:

exp(logq)=q\exp{(\log{\bold{q}})} = \bold{q}

四元数的指数运算中,有如下的映射关系:

(θ,n)exp(w,v)(\theta,\bold{n}) \xmapsto{\exp} (w,\bold{v})

3.10.3 四元数与标量相乘

kq=k[wv]=[kwkv]=k[w(xyz)]=[kw(kxkykz)]\begin{aligned} k\bold{q} &= k \begin{bmatrix} w &\bold{v} \end{bmatrix} = \begin{bmatrix} kw &k\bold{v} \end{bmatrix} \\[1.5ex] &= k\begin{bmatrix} w &(x &y &z) \end{bmatrix} = \begin{bmatrix} kw &(kx &ky &kz) \end{bmatrix} \end{aligned}

3.11 四元数求幂

四元数作为底数,记作 qt\bold{q}^t

实数求幂:

  • a0=1a^0 = 1a1=aa^1 = a ,其中 aa 为非零标量;
  • tt00 变到 11 时,ata^t11 变到 aa

四元数求幂的意义类似于实数求幂:

  • q0=[1, 0]\bold{q}^0 = [1,\space\bold{0}]q1=q\bold{q}^1 = \bold{q}
  • tt00 变到 11 时,qt\bold{q}^t[1, 0][1,\space\bold{0}] 变到 q\bold{q}

NOTE

  • 四元数表示一个角位移,同时 角位移的叠加 又是通过四元数相乘的形式;
  • 显然,导致在这样的变化过程中,[1, 0]q[1,\space\bold{0}] \to \bold{q} 是线性的。

可以通过对四元数求幂从角位移中抽取“一部分”,并且 tt 的取值可以超出[0, 1][0,\space 1]

例如

  • 四元数 q\bold{q} 代表一个角位移:
    • q1/3\bold{q}^{1/3} 代表的角位移是 q\bold{q}1/31/3 倍;
    • q2\bold{q}^2 代表的角位移是 q\bold{q}22 倍。
       
  • 假设四元数 q\bold{q} 代表绕 n\bold{n} 逆时针旋转 30°30 \degree
    • q1/3\bold{q}^{1/3} 代表绕 n\bold{n} 逆时针旋转 10°10 \degree
    • q2\bold{q}^2 代表绕 n\bold{n} 逆时针旋转 60°60 \degree
    • q1/3\bold{q}^{-1/3} 代表绕 n\bold{n} 顺时针旋转 10°10 \degree

特别地

四元数表示角位移时使用最短圆弧,不能 “ 绕圈 ” 。

假设四元数 q\bold{q} 代表绕 n\bold{n} 逆时针旋转 30°30 \degree

  • q8\bold{q}^{8} 代表的不是绕 n\bold{n} 逆时针旋转 240°240 \degree ,而是顺时针 120°120 \degree

显然,向一个方向旋转 240°240 \degree 与向相反的方向旋转 120°120 \degree 等价,即 q8\bold{q}^{8} 等价于 q4\bold{q}^{-4}

但是进一步的指数运算的代数公式 (as)t=ast(a^s)^t = a^{st} ,对四元数求幂就不适用:

例如:

{(q8)1/2q4(q8)1/2=q2\begin{cases} (\bold{q}^{8})^{1/2} \ne \bold{q}^{4} \\ (\bold{q}^{8})^{1/2} = \bold{q}^{-2} \end{cases}

(q8)1/2(\bold{q}^{8})^{1/2} 的结果不是绕 n\bold{n} 逆时针旋转 120°120 \degree ,而是绕 n\bold{n} 顺时针旋转 60°60 \degree

四元数求幂的数学定义

qt=exp(tlogq)\bold{q}^t = \exp{(t\log{\bold{q}})}

通过四元数的指数运算和对数运算的映射关系来体现其中系数 tt 与角位移之间的 “ 线性关系 ” :

(w,v)log(θ,n)t(tθ,n)exp(w,v)(w,\bold{v}) \xmapsto{\log} (\theta,\bold{n}) \xmapsto{t} (t\theta,\bold{n}) \xmapsto{\exp} (w',\bold{v'})

其结果 qt=(w,v)\bold{q}^t = (w',\bold{v'}) 表示的角位移即是原四元数 q=(w,v)\bold{q} = (w,\bold{v}) 表示的角位移的 tt 倍。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//---------------------------------------------------------------------------
// pow
//
// 四元数幂

Quaternion pow(const Quaternion& q, float exponent) {

// 检查单位四元数,防止除零
if (fabs(q.w) > .9999f) {
return q;
}

// 提取半角 alpha(alpha = theta/2)
float alpha = acos(q.w);

// 计算新 alpha 值
float newAlpha = alpha * exponent;

// 计算新 w 值
Quaternion result;
result.w = cos(newAlpha);

// 计算新 xyz 值
float mult = sin(newAlpha) / sin(alpha);
result.x = q.x * mult;
result.y = q.y * mult;
result.z = q.z * mult;

// 返回
return result;
}

3.12 四元数插值 —— “slerp”

球面线性插值Spherical Linear Interpolation):在两个四元数间进行平滑插值。

  • slerpslerp 是一种三元运算,前两个参数是两个四元数,将在他们中间插值,设这两个 “ 开始 ” 和 “ 结束 ” 四元数分别为 q0\bold{q}_0q1\bold{q}_1
  • 插值参数设为变量 tttt0011 之间变化。

slerp(q0, q1, t)slerp(\bold{q}_0,\space \bold{q}_1,\space t) :返回 q0\bold{q}_0q1\bold{q}_1 之间的插值方位。

标准线性插值公式

{Δa=a1a0lerp(a0,a1,t)=a0+tΔa\begin{cases} \Delta a = a_1 - a_0 \\[1.5ex] lerp(a_0,a_1,t) = a_0 + t \Delta a \end{cases}

标准线性插值公式从 a0a_0 开始,并加上 a0a_0a1a_1 之差的 tt 倍,有三个基本步骤:

  1. 计算两个值的差 Δa\Delta a
  2. 取得差的一部分 tΔat \Delta a
  3. 在初始值 a0a_0 上加上差的一部分。

球面线性插值公式

slerp(q0, q1, t)=(q1q01)tq0slerp(\bold{q}_0,\space \bold{q}_1,\space t) = (\bold{q}_1\bold{q}_0^{-1})^t\bold{q}_0

  1. 计算两个值的差 : Δq=q1q01\Delta\bold{q} = \bold{q}_1\bold{q}_0^{-1}
  2. 计算差的一部分 :即为 Δqt\Delta\bold{q}^t
  3. 在开始值上加上差的一部分 :使用四元数乘法组合角位移 Δqtq0\Delta\bold{q}^t\bold{q}_0

3.12.1 “ slerp ” 的一般实现思路 :

在 4D 空间中解释四元数,由于考虑的四元数都是单位四元数,所以可以将它们都看做是 “ 存在 ” 于一个 4D “ 球面 ” 上。

slerp 的基本思想 :沿着 4D 球面上连接两个四元数的弧插值。

screenShot.png

  • 可以把这种思想表现在平面上,设两个 2D 向量 v0\bold{v}_0v1\bold{v}_1 ,都是单位向量;
  • 计算 vt\bold{v}_t ,即沿 v0\bold{v}_0vt\bold{v}_t 弧的平滑插值;
  • ω\omegav0\bold{v}_0vt\bold{v}_t 弧所截的角,而 vt\bold{v}_t 就是沿弧旋转 tωt\omega 的结果。

vt\bold{v}_t 表达成 v0\bold{v}_0v1\bold{v}_1 的线性组合,即存在两个非零常数 k0k_0k1k_1 ,使得:

v0=k0v0+k1v1\bold{v}_0 = k_0 \bold{v}_0 + k_1 \bold{v}_1

screenShot.png

可以用基本几何学求出 k0k_0k1k_1

{k1=sintωsinωk0=sin(1t)ωsinω\begin{cases} k_1 = \cfrac{\sin{t\omega}}{\sin{\omega}} \\[3ex] k_0 = \cfrac{\sin{(1 - t)\omega}}{\sin{\omega}} \end{cases}

vt\bold{v}_t 可以表示为:

vt=k0v0+k1v1vt=sin(1t)ωsinωv0+sintωsinωv1\begin{aligned} & \bold{v}_t = k_0 \bold{v}_0 + k_1 \bold{v}_1 \\[3ex] & \bold{v}_t = \cfrac{\sin{(1 - t)\omega}}{\sin{\omega}} \bold{v}_0 + \cfrac{\sin{t\omega}}{\sin{\omega}} \bold{v}_1 \end{aligned}

而四元数的 slerp 有类似形式:

slerp(q0, q1, t)=sin(1t)ωsinωq0+sintωsinωq1slerp(\bold{q}_0,\space \bold{q}_1,\space t) = \cfrac{\sin{(1 - t)\omega}}{\sin{\omega}} \bold{q}_0 + \cfrac{\sin{t\omega}}{\sin{\omega}} \bold{q}_1

可以用点乘来计算两个四元数间的 “ 角度 ω\omega ” ,点乘的结果为 cosω\cos{\omega}

NOTE

  1. 四元数 q\bold{q}q-\bold{q} 代表相同的方位,但由于 4D 球面不是欧氏空间的直接扩展,导致它们作为 slerp 的参数时可能导致不一样的结果。
    解决方法 :选择 q0\bold{q}_0q1\bold{q}_1 的符号使得点乘 q0q1\bold{q}_0 \cdot \bold{q}_1 的结果是非负。
     
  2. 要考虑的是如果 q0\bold{q}_0q1\bold{q}_1 非常接近,sinθ\sin{\theta} 会非常小乃至趋于 00 ,这会导致除法出现问题。
    解决方法 :当 sinθ\sin{\theta} 非常小时,使用 简单的线性插值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
//---------------------------------------------------------------------------
// slerp
//
// 球面线性插值

Quaternion slerp(const Quaternion& q0, const Quaternion& q1, float t) {

// 检查出界的参数,如果检查到,返回边界点
if (t <= 0.0f) return q0;
if (t >= 1.0f) return q1;

// 用点乘计算四元数夹角的 cos 值
float cosOmega = dotProduct(q0, q1);

// 如果点乘为负,使用 -q1
// 四元数 q 和 -q 代表相同的旋转,但可能产生不同的 slerp 运算
// 我们要选择正确的一个以便用锐角进行旋转
float q1w = q1.w;
float q1x = q1.x;
float q1y = q1.y;
float q1z = q1.z;
if (cosOmega < 0.0f) {
q1w = -q1w;
q1x = -q1x;
q1y = -q1y;
q1z = -q1z;
cosOmega = -cosOmega;
}

// 我们用的是两个单位四元数,所以点乘结果应该 <= 1.0
assert(cosOmega < 1.1f);

// 计算插值片,注意检查非常接近的情况
float k0, k1;
if (cosOmega > 0.9999f) {

// 非常接近,即线性插值,防止除零
k0 = 1.0f - t;
k1 = t;

}
else {

// 用三角公式 sin^2(omega) + cos^2(omega) = 1 计算 sin 值
float sinOmega = sqrt(1.0f - cosOmega * cosOmega);

// 根据 sin 和 cos 值计算角度
float omega = atan2(sinOmega, cosOmega);

// 计算分母的倒数,这样只需要除一次
float oneOverSinOmega = 1.0f / sinOmega;

// 计算插值变量
k0 = sin((1.0f - t) * omega) * oneOverSinOmega;
k1 = sin(t * omega) * oneOverSinOmega;
}

// 插值
Quaternion result;
result.x = k0 * q0.x + k1 * q1x;
result.y = k0 * q0.y + k1 * q1y;
result.z = k0 * q0.z + k1 * q1z;
result.w = k0 * q0.w + k1 * q1w;

// 返回
return result;
}

3.13 四元数样条 —— “squad”

slerpslerp 提供了两个方位间的插值。当有多于两个的方位序列(它描述了我们想要经过的插值“路径”),可以在“控制点”之间使用 slerpslerp

类似于基本几何学中的线性插值,控制点之间是以直线连接,这会导致控制点上会有 不连续性

使用 squadsquad (Spherical and Quadrangle) 描绘控制点间的路径。

控制点 由四元数序列所定义:

q0,q2,q3,,qn2,qn1,qn\bold{q}_0,\bold{q}_2,\bold{q}_3,\cdots,\bold{q}_{n-2},\bold{q}_{n-1},\bold{q}_{n}

引进一个“辅助”四元数 si\bold{s}_i ,将其作为临时控制点:

si=exp(log(qi+1qi1)+log(qi1qi1)4)qi=(Δq[ii1]Δq[ii+1])14qi\begin{aligned} \bold{s}_i &= \exp{\left( -\cfrac{\log{(\bold{q}_{i+1}\bold{q}_{i}^{-1})} + \log{(\bold{q}_{i-1}\bold{q}_{i}^{-1})} }{4} \right)}\bold{q}_{i} \\[3ex] &= (\Delta \bold{q}_{[i \to i-1]} \Delta \bold{q}_{[i \to i+1]} )^{-\frac{1}{4}} \bold{q}_{i} \end{aligned}

NOTE
可知 si\bold{s}_i 是通过 qi1\bold{q}_{i-1}qi+1\bold{q}_{i+1} 计算出的,因此 s1\bold{s}_1sn\bold{s}_n 是未定义的。

换言之,曲线从 q2\bold{q}_{2} 延伸到 qn1\bold{q}_{n-1} ,而第一个 q1\bold{q}_{1} 和最后一个控制点 qn\bold{q}_{n} 仅用于控制中间的曲线,若需要曲线经过 q1\bold{q}_{1}qn\bold{q}_{n} 这两个点,则必须在头部和尾部增加虚控制点 q0\bold{q}_{0}qn+1\bold{q}_{n+1} ,最好的解决方法就是复制这两个控制点:

{q0=q1qn+1=qn\begin{cases} \bold{q}_{0} = \bold{q}_{1} \\ \bold{q}_{n+1} = \bold{q}_{n} \end{cases}

给定四个相邻的控制点 qi1,qi,qi+1,qi+2\bold{q}_{i-1},\bold{q}_{i},\bold{q}_{i+1},\bold{q}_{i+2}squadsquad 用于计算中间两点间的插值,即 qi,qi+1\bold{q}_{i},\bold{q}_{i+1}

“squad” 函数

同时还需要引入一个插值变量 hhhh00 变化到 11 时, squadsquad 描绘 qi\bold{q}_{i}qi+1\bold{q}_{i+1} 之间的曲线。

整条插值曲线能分段应用 squadsquad 方法:

squad(qi,qi+1,si,si+1,h)=slerp( slerp(qi,qi+1,h), slerp(si, si+1,h), 2h(1h) )squad(\bold{q}_{i},\bold{q}_{i+1},\bold{s}_{i},\bold{s}_{i+1},h) = slerp(\space slerp(\bold{q}_{i},\bold{q}_{i+1},h),\space slerp(\bold{s}_{i},\space \bold{s}_{i+1},h),\space 2h(1-h)\space )

参考资料

参考视频