四元数: q = [ q x q y q z q w ] \Large q = [q_x\space\space q_y\space\space q_z\space\space q_w] q=[qx qy qz qw]。四元数看起来就是一个四维矢量,但是行为上是有区别的。
注意下面三维矢量,惯例加粗,四元数就不加粗了
记住:能代表三维旋转的只能是单位四元数( q x 2 + q y 2 + q z 2 + q w 2 = 1 q^2_x+q^2_y+q^2_z+q^2_w = 1 qx2+qy2+qz2+qw2=1)
单位四元数 = 三维矢量 q V \large q_V qV + 标量 q S \large q_S qS。
单位四元数可写成:
q
=
[
q
V
q
S
]
=
[
a
sin
θ
2
cos
θ
2
]
=
[
a
x
sin
θ
2
a
y
sin
θ
2
a
z
sin
θ
2
cos
θ
2
]
\large q=[\bold{q}_V\space\space q_S]=[\bold{a}\sin{\frac{\theta}{2}} \space\space\cos{\frac{\theta}{2}}]=[a_x\sin{\frac{\theta}{2}}\space\space a_y\sin{\frac{\theta}{2}}\space\space a_z\sin{\frac{\theta}{2}}\space\space \cos{\frac{\theta}{2}}]
q=[qV qS]=[asin2θ cos2θ]=[axsin2θ aysin2θ azsin2θ cos2θ]
注意,在数学定义中,四元数是由1个实部+3个虚部组成,即
q = a + b i + c j + d k ( a , b , c , d ∈ R ) \large q = a + bi+ cj + dk (a,b,c,d∈R) q=a+bi+cj+dk(a,b,c,d∈R)
因此,一般会写成标量在前,3个虚部为一组放在后面
q = [ q S q V ] q=[\large q_S\space\space \bold{q}_V] q=[qS qV]
但在游戏、图形领域,可能是出于内存布局的考虑,大部分都将实部放在最后一位,即
q = [ q V q S ] \large q=[\bold{q}_V\space\space q_S] q=[qV qS]
因为旋转要求是单位四元数,因此基本不会进行+ -
运算
乘法四元数最重要的运算法则。给定两个单位四元数 p 和 q ,分别代表旋转
P
\bold P
P和
Q
\bold Q
Q,则pq则代表两个旋转的合成旋转(先
Q
\bold Q
Q后
P
\bold P
P),四元数乘法不止一种,但是三维旋转应用相关的乘法称为格拉斯曼积(Grassmann Product):
p
q
=
[
(
p
S
q
V
+
q
s
p
V
+
p
V
×
q
V
)
(
p
S
q
S
−
p
V
⋅
q
V
)
\Large pq = [(p_S\bold{q}_V + q_s\bold{p}_V+\bold{p}_V\times\bold{q}_V)\space\space\space (p_Sq_S-\bold{p}_V·\bold{q}_V)
pq=[(pSqV+qspV+pV×qV) (pSqS−pV⋅qV) 注意,这里面矢量为四元数的前3个分量(x,y,z),标量是最后1个分量(w)
四元数的模
∣
∣
q
∣
∣
=
a
2
+
b
2
+
c
2
+
d
2
\large ||q|| = \sqrt{a^2+b^2+c^2+d^2}
∣∣q∣∣=a2+b2+c2+d2
共轭四元数:实部不变,虚部取反
q
∗
=
[
−
q
V
q
S
]
\large q^* = [-\bold q_V\quad q_S]
q∗=[−qVqS]
四元数的逆:模方分之伴随,由于我们是在讨论三维旋转,四元数都是单位长度的,因此
∣
q
∣
=
1
|q| =1
∣q∣=1
q
−
1
=
q
∗
∣
q
∣
2
=
q
∗
=
[
−
q
V
q
S
]
\large q^{-1}=\frac{q^*}{|q|^2}=q^*=[-\bold q_V\quad q_S]
q−1=∣q∣2q∗=q∗=[−qVqS]
这一结论是非常有价值的,因为它意味着计算逆四元数时,当知道四元数已被归一化,就
不用除以模平方了(相对费时)。同时也意味着,计算逆四元数比计算3×3逆矩阵快得
多,在某些情况下,我们可以利用这一特点优化引擎。
其他的一些运算性质,跟普通向量运算一样
互逆性: q − 1 q = q q − 1 = 1 \large q^{-1}q = qq^{-1}=1 q−1q=qq−1=1
积的共轭: ( p q ) ∗ = q ∗ p ∗ (pq)^*=q^*p^* (pq)∗=q∗p∗
积的逆: ( p q ) − 1 = q − 1 p − 1 (pq)^{-1}=q^{-1}p^{-1} (pq)−1=q−1p−1
积的转置: ( p q ) T = q T p T (pq)^{T}=q^{T}p^{T} (pq)T=qTpT
首先要把矢量重写为四元数形式。把标量项 q S q_S qS设为0。给定矢量 v \bold v v,可把它写成对应的四元数 v = [ v 0 ] = [ v x v y v z 0 ] v=[\bold v\quad 0]=[v_x\quad v_y\quad v_z\quad 0] v=[v0]=[vxvyvz0]
以四元数
q
q
q 旋转矢量
v
\bold v
v:
v
′
=
q
v
q
−
1
v'=qvq^{-1}
v′=qvq−1
因为
q
−
1
=
q
∗
q^{-1}=q^*
q−1=q∗,因此也可以用共轭四元数代替四元数的逆
v
′
=
q
v
q
∗
v'=qvq^{*}
v′=qvq∗
最后,提取
v
′
v'
v′的矢量部分就是旋转后的矢量
v
′
\bold v'
v′
多个旋转是可以进行拼接的,左右一直加就行了
注意,在游戏引擎中,四元数的使用场合很多,不光是传统的旋转变换,下面这种场景也用的特别多
案例: 一个游戏对象,在世界空间中以某个姿态摆放着,已知该物体当前的旋转状态为 Transform.Rotation =(roll, pitch, yaw)
(游戏引擎中一般还是用欧拉角来表示朝向状态,但是内部计算都是转成四元数的),现在,我想求它的三个方向向量 Forward
Up
和 Right
glm::vec3 = getObjectTransform().Rotation;
// 创建旋转矩阵,必须按顺序
glm::mat4 rotationMatrix(1.0f);
rotationMatrix = glm::rotate(rotationMatrix, glm::radians(yaw), glm::vec3(1.0f, 0.0f, 0.0f));
rotationMatrix = glm::rotate(rotationMatrix, glm::radians(pitch), glm::vec3(0.0f, 1.0f, 0.0f));
rotationMatrix = glm::rotate(rotationMatrix, glm::radians(roll), glm::vec3(0.0f, 0.0f, 1.0f));
// 应用旋转矩阵得到新的向量
glm::vec3 forward = glm::vec3(rotationMatrix * glm::vec4(0.0f, 0.0f, 1.0f, 0.0f));
glm::vec3 up = glm::vec3(rotationMatrix * glm::vec4(0.0f, 1.0f, 0.0f, 0.0f));
glm::vec3 right = glm::vec3(rotationMatrix * glm::vec4(1.0f, 0.0f, 0.0f, 0.0f));
Forward
Up
和Right
就能得到结果// glm::quat()会根据传入的欧拉角表示的三个值,计算半角正弦半角余弦,然后构造一个四元数
glm::quat rotationQuat = glm::quat(getObjectTransform().Rotation);
// 这是glm库的
glm::vec3 forward = glm::rotate(rotationQuat , glm::vec3(0.0f, 0.0f, 1.0f));
glm::vec3 up= glm::rotate(rotationQuat , glm::vec3(0.0f, 1.0f, 0.0f));
glm::vec3 right = glm::rotate(rotationQuat , glm::vec3(1.0f, 0.0f, 0.0f));
任何三维旋转都可以从3×3矩阵表达方式 R \bold R R和四元数表达方式 q q q 之间自由转换(可以直接用glm或者其他数学库)。
当我们想要将四元数和三维旋转矩阵相互转换时,以下是数学公式:
给定四元数
q
=
[
x
,
y
,
z
,
w
]
q = [x, y, z, w]
q=[x,y,z,w],对应的旋转矩阵
R
\bold R
R是:
R
=
[
1
−
2
y
2
−
2
z
2
2
x
y
−
2
z
w
2
x
z
+
2
y
w
2
x
y
+
2
z
w
1
−
2
x
2
−
2
z
2
2
y
z
−
2
x
w
2
x
z
−
2
y
w
2
y
z
+
2
x
w
1
−
2
x
2
−
2
y
2
]
R =
w = 1 + tr ( R ) 2 x = R 23 − R 32 4 w y = R 31 − R 13 4 w z = R 12 − R 21 4 w tr ( A ) = ∑ i = 1 n A i i w = \frac{\sqrt{1 + \text{tr}(R)}}{2} \\ \ \\ x = \frac{R_{23} - R_{32}}{4w} \\ \ \\y = \frac{R_{31} - R_{13}}{4w} \\ \ \\ z = \frac{R_{12} - R_{21}}{4w} \\ \ \\\text{tr}(A) = \sum_{i=1}^{n} A_{ii} w=21+tr(R) x=4wR23−R32 y=4wR31−R13 z=4wR12−R21 tr(A)=i=1∑nAii
为了生活,还是用第三方库吧,在glm里面可以用
// 将四元数转换为旋转矩阵
glm::mat4 rotationMatrix = glm::mat4_cast(quaternion);
// 将旋转矩阵转换为四元数
glm::quat quaternionFromMatrix = glm::quat_cast(rotationMatrix);
提一句,glm一般变换矩阵都是mat4,因为这是仿射变换,支持旋转、位移、缩放的。旋转矩阵第四行和第四列通常是(0, 0, 0, 1)。
就用线性插值(LERP)来插值四元数的每个分量就行了,
q
(
t
)
=
(
1
−
t
)
⋅
q
1
+
t
⋅
q
2
q(t) = (1 - t) \cdot q_1 + t \cdot q_2
q(t)=(1−t)⋅q1+t⋅q2
q
(
t
)
=
n
o
r
m
a
l
i
z
e
(
[
x
s
(
t
)
=
(
1
−
t
)
⋅
x
1
+
t
⋅
x
2
y
s
(
t
)
=
(
1
−
t
)
⋅
y
1
+
t
⋅
y
2
z
s
(
t
)
=
(
1
−
t
)
⋅
z
1
+
t
⋅
z
2
w
s
(
t
)
=
(
1
−
t
)
⋅
w
1
+
t
⋅
w
2
]
T
)
q(t)=normalize(
更准确的可以用SLERP(Spherical Linear Interpolation),计算量更大,效果好了一点点。