四元数到矩阵 Quaternion to Matrix

整理四元数与旋转矩阵之间的转换,以及矩阵和四元数表示旋转的取舍。

四元数到矩阵 Quaternion to Matrix

将代表旋转的 单位四元数 转换为 同样代表旋转的 3×33\times 3 矩阵可能是常见的需求,3×33\times 3 旋转矩阵可以看成由三个 列向量 x,y,z\vec{x},\vec{y},\vec{z} 轴基向量世界坐标 中的方向组成,而最直接的做法就是将这三个 标准基向量 (1,0,0),(0,1,0),(0,0,1)(1,0,0),(0,1,0),(0,0,1) 分别用四元数旋转,得到的新向量作为矩阵的三列

Matrix3 ToMatrix_NonOptimal(Quaternion quat) 
{
    // 1) 定义三条基向量
    Vector3 right   = Vector3(1, 0, 0);   // x 轴
    Vector3 up      = Vector3(0, 1, 0);   // y 轴
    Vector3 forward = Vector3(0, 0, 1);   // z 轴
 
    // 2) 用四元数旋转三条向量
    right   = Mul(right, quat);
    up      = Mul(up, quat);
    forward = Mul(forward, quat);
 
    // 3) 把结果装进矩阵
    return Matrix3(
        right.x,   up.x,   forward.x,   // 第一行
        right.y,   up.y,   forward.y,   // 第二行
        right.z,   up.z,   forward.z    // 第三行
    );
}
  • Mul(v, quat) 表示把向量 v\vec{v} 通过四元数 quat 进行旋转

也可以构造完整的 4×44\times 4 变换矩阵

Matrix4 ToMatrix(Quaternion q) {
    Vector3 r = q * Vector3(1, 0, 0); // 右轴  (Right)
    Vector3 u = q * Vector3(0, 1, 0); // 上轴  (Up)
    Vector3 f = q * Vector3(0, 0, 1); // 前轴  (Forward)
 
    // OpenGL 列主序 4×4 矩阵(平移列保持 [0 0 0 1])
    return Matrix4(
        r.x, r.y, r.z, 0,
        u.x, u.y, u.z, 0,
        f.x, f.y, f.z, 0,
        0  , 0  , 0  , 1
    );
}

从矩阵到四元数 From Matrix to Quaternion

我们也可以反向操作,从 旋转矩阵 构建对应的 四元数

Quaternion FromMatrix(Matrix4 m) 
{
    // 1) 从矩阵里取出“上轴”和“前轴”列向量,并标准化
    Vector3 up      = Normalized(Vector3(m[0],  m[1],  m[2]));
    Vector3 forward = Normalized(Vector3(m[8],  m[9],  m[10]));
 
    // 2) 通过叉乘重建“右轴”,并再一次正交化 up
    Vector3 right = Cross(up, forward);
    up = Cross(forward, right);
 
    // 3) 根据 forward/up 做一次 look-at,得到旋转四元数
    return lookAt(forward, up);
}

理论上一个纯旋转矩阵的列向量应互相正交且单位长度,但在数值误差或包含缩放的情况下可能不再正交,所以上图用 CrossNormalized 做一次正交化,获取到想要的目标 forwardup 以后,就可以使用四元数 lookAt 创造一个将 局部坐标系forward (0,0,1) 和 up (0,1,0) 旋转至 目标 的四元数

矩阵 vs 四元数 表示旋转

维度旋转矩阵 (R)四元数 (q)
存储量9 个浮点数 (或 12 个若含齐次列)4 个浮点数
约束列向量需保持正交且单位长度 (R · Rᵀ = I, det R = 1)需保持归一化 (‖q‖ = 1);q 与 −q 表示同一姿态
变换向量代价v' = R·v —— 9 乘 + 6 加v' = q v q⁻¹
组合(叠加)旋转矩阵乘:R_new = R2 · R1 (约 27 乘)四元数乘:q_new = q2 * q1 (约 16 乘)
差值插值需先正交化或用 SVD;LERP 会失真可用 SLERP / NLERP,自然保持单位长度、无万向锁
数值稳定连续相乘误差会破坏正交,需要定期正交化连续相乘误差只影响长度,归一化即可
包含缩放/投影可以同时携带非均匀缩放、剪切 (但会破坏正交)只能表示纯旋转
直观性易理解:列=三个轴的方向;可直接观察抽象;但几何意义明确 (旋转轴+半角)
奇异/万向锁无;覆盖全 SO(3) 唯一无;但 q 与 –q 双覆盖
硬件/API 支持GPU 直接接受矩阵、可与齐次变换合并GPU 常先转矩阵上传
常见用途顶点着色、骨骼变换、齐次坐标链姿态累积、插值动画、物理仿真、摄像机轨迹

实践做法:常见 “内部四元数、外部矩阵”——内部算法/动画用四元数,真正绘制前一次性转成矩阵上传即可

四元数转换为矩阵的更优化方法

虽然对 单位矩阵 的每个轴做旋转的方法可以很直观的将 四元数 代表的旋转转换为 旋转矩阵,实际上也很容易,但是它在计算上并不高效,它需要执行多次的四元数旋转向量的操作,我们有办法更高效的执行这个转换过程

我们知道,四元数旋转向量的过程是

v=qvqv' = qvq^\ast
  • vv' 是旋转后的向量 (纯四元数)
  • vv 是原向量 (纯四元数)
  • qqqq^\ast 互为 共轭

q=(qw,qx,qy,qz),q=(qw,qx,qy,qz)=(qw,qx,qy,qz),v=(vw,vx,vy,vz)q = (q_w,q_x,q_y,q_z), \quad q^\ast = (q_w^\ast,q_x^\ast,q_y^\ast,q_z^\ast) = (q_w,-q_x,-q_y,-q_z), \quad v = (v_w,v_x,v_y,v_z)

四元数乘法表

虚部i\mathbf{i}j\mathbf{j}k\mathbf{k}
i\mathbf{i}1-1k\mathbf{k}-j\mathbf{j}
j\mathbf{j}-k\mathbf{k}1-1i\mathbf{i}
k\mathbf{k}j\mathbf{j}-i\mathbf{i}1-1

我们早在第一章就介绍过,四元数乘法可以写成 矩阵形式

qv=(qw,qx,qy,qz)(vw,vx,vy,vz)=(qw+qxi+qyj+qzk)(vw+vxi+vyj+vzk)=qwvw+qwvxi+qwvyj+qwvzk+=qxvwi+qxvxi2+qxvyij+qxvzik+=qyvwj+qyvxji+qyvyj2+qyvzjk+=qzvwk+qzvxki+qzvykj+qzvzk2=qwvw+qwvxi+qwvyj+qwvzk+=qxvwiqxvx+qxvykqxvzj+=qyvwjqyvxkqyvy+qyvzi+=qzvwk+qzvxjqzvyiqzvz=qwvwqxvxqyvyqzvz+=(qwvx+qxvw+qyvzqzvy)i+=(qwvyqxvz+qyvw+qzvx)j+=(qwvz+qxvyqyvx+qzvw)k+=qwvwqxvxqyvyqzvz+=(qxvw+qwvxqzvy+qyvz)i+=(qyvw+qzvx+qwvyqxvz)j+=(qzvwqyvx+qxvy+qwvz)k+=[qwqxqyqzqxqwqzqyqyqzqwqxqzqyqxqw][vwvxvyvz]\begin{align*} qv &= (q_w,q_x,q_y,q_z)(v_w,v_x,v_y,v_z) \\ &= (q_w + q_x\mathbf{i} + q_y\mathbf{j} + q_z\mathbf{k})(v_w + v_x\mathbf{i} + v_y\mathbf{j} + v_z\mathbf{k}) \\ &= q_wv_w + q_wv_x\mathbf{i} + q_wv_y\mathbf{j} + q_wv_z\mathbf{k} + \\ &\phantom{=} q_xv_w\mathbf{i} + q_xv_x\mathbf{i}^2 + q_xv_y\mathbf{i}\mathbf{j} + q_xv_z\mathbf{i}\mathbf{k} + \\ &\phantom{=} q_yv_w\mathbf{j} + q_yv_x\mathbf{j}\mathbf{i} + q_yv_y\mathbf{j}^2 + q_yv_z\mathbf{j}\mathbf{k} + \\ &\phantom{=} q_zv_w\mathbf{k} + q_zv_x\mathbf{k}\mathbf{i} + q_zv_y\mathbf{k}\mathbf{j} + q_zv_z\mathbf{k}^2 \\[5pt] &= q_wv_w + q_wv_x\mathbf{i} + q_wv_y\mathbf{j} + q_wv_z\mathbf{k} + \\ &\phantom{=} q_xv_w\mathbf{i} - q_xv_x + q_xv_y\mathbf{k} - q_xv_z\mathbf{j} + \\ &\phantom{=} q_yv_w\mathbf{j} - q_yv_x\mathbf{k} - q_yv_y + q_yv_z\mathbf{i} + \\ &\phantom{=} q_zv_w\mathbf{k} + q_zv_x\mathbf{j} - q_zv_y\mathbf{i} - q_zv_z \\[5pt] &= q_wv_w - q_xv_x - q_yv_y - q_zv_z + \\ &\phantom{=} (q_wv_x + q_xv_w + q_yv_z - q_zv_y)\mathbf{i} + \\ &\phantom{=} (q_wv_y - q_xv_z + q_yv_w + q_zv_x)\mathbf{j} + \\ &\phantom{=} (q_wv_z + q_xv_y - q_yv_x + q_zv_w)\mathbf{k} + \\[5pt] &= q_wv_w - q_x\textcolor{red}{v_x} - q_y\textcolor{green}{v_y} - q_z\textcolor{#87CEFA}{v_z} + \\ &\phantom{=} (q_xv_w + q_w\textcolor{red}{v_x} - q_z\textcolor{green}{v_y} + q_y\textcolor{#87CEFA}{v_z})\mathbf{i} + \\ &\phantom{=} (q_yv_w + q_z\textcolor{red}{v_x} + q_w\textcolor{green}{v_y} - q_x\textcolor{#87CEFA}{v_z})\mathbf{j} + \\ &\phantom{=} (q_zv_w - q_y\textcolor{red}{v_x} + q_x\textcolor{green}{v_y} + q_w\textcolor{#87CEFA}{v_z})\mathbf{k} + \\[5pt] &= \begin{bmatrix} q_w & -q_x & -q_y & -q_z \\ q_x & q_w & -q_z & q_y \\ q_y & q_z & q_w & -q_x \\ q_z & -q_y & q_x & q_w \end{bmatrix} \begin{bmatrix} v_w \\ \textcolor{red}{v_x} \\ \textcolor{green}{v_y} \\ \textcolor{#87CEFA}{v_z} \end{bmatrix} \end{align*}

同理

vq=(vw,vx,vy,vz)(qw,qx,qy,qz)=(vw,vx,vy,vz)(qw,qx,qy,qz)=(vw+vxi+vyj+vzk)(qwqxiqyjqzk)=vwqwvwqxivwqyjvwqzk+=vxqwivxqxi2vxqyijvxqzik+=vyqwjvyqxjivyqyj2vyqzjk+=vzqwkvzqxkivzqykjvzqzk2=vwqwvwqxivwqyjvwqzk+=vxqwi+vxqxvxqyk+vxqzj+=vyqwj+vyqxk+vyqyvyqzi+=vzqwkvzqxj+vzqyi+vzqz=vwqw+vxqx+vyqy+vzqz+=(vwqx+vxqwvyqz+vzqy)i+=(vwqy+vxqz+vyqwvzqx)j+=(vwqzvxqy+vyqx+vzqw)k=vwqw+vxqx+vyqy+vzqz+=(vwqx+vxqwvyqz+vzqy)i+=(vwqy+vxqz+vyqwvzqx)j+=(vwqzvxqy+vyqx+vzqw)k=[qwqxqyqzqxqwqzqyqyqzqwqxqzqyqxqw][vwvxvyvz]\begin{align*} vq^\ast &= (v_w,v_x,v_y,v_z)(q_w^\ast,q_x^\ast,q_y^\ast,q_z^\ast) \\ &= (v_w,v_x,v_y,v_z)(q_w,-q_x,-q_y,-q_z) \\ &= (v_w + v_x\mathbf{i} + v_y\mathbf{j} + v_z\mathbf{k})(q_w - q_x\mathbf{i} - q_y\mathbf{j} - q_z\mathbf{k}) \\ &= v_wq_w -v_wq_x\mathbf{i} - v_wq_y\mathbf{j} - v_wq_z\mathbf{k} + \\ &\phantom{=} v_xq_w\mathbf{i} - v_xq_x\mathbf{i}^2 - v_xq_y\mathbf{i}\mathbf{j} - v_xq_z\mathbf{i}\mathbf{k} + \\ &\phantom{=} v_yq_w\mathbf{j} - v_yq_x\mathbf{j}\mathbf{i} - v_yq_y\mathbf{j}^2 - v_yq_z\mathbf{j}\mathbf{k} + \\ &\phantom{=} v_zq_w\mathbf{k} - v_zq_x\mathbf{k}\mathbf{i} - v_zq_y\mathbf{k}\mathbf{j} - v_zq_z\mathbf{k}^2 \\[5pt] &= v_wq_w - v_wq_x\mathbf{i} - v_wq_y\mathbf{j} - v_wq_z\mathbf{k} + \\ &\phantom{=} v_xq_w\mathbf{i} + v_xq_x - v_xq_y\mathbf{k} + v_xq_z\mathbf{j} + \\ &\phantom{=} v_yq_w\mathbf{j} + v_yq_x\mathbf{k} + v_yq_y - v_yq_z\mathbf{i} + \\ &\phantom{=} v_zq_w\mathbf{k} - v_zq_x\mathbf{j} + v_zq_y\mathbf{i} + v_zq_z \\[5pt] &= v_wq_w + v_xq_x + v_yq_y + v_zq_z + \\ &\phantom{=} (-v_wq_x + v_xq_w - v_yq_z + v_zq_y)\mathbf{i} + \\ &\phantom{=} (-v_wq_y + v_xq_z + v_yq_w - v_zq_x)\mathbf{j} + \\ &\phantom{=} (-v_wq_z - v_xq_y + v_yq_x + v_zq_w)\mathbf{k} \\[5pt] &= v_wq_w + \textcolor{red}{v_x}q_x + \textcolor{green}{v_y}q_y + \textcolor{#87CEFA}{v_z}q_z + \\ &\phantom{=} (-v_wq_x + \textcolor{red}{v_x}q_w - \textcolor{green}{v_y}q_z + \textcolor{#87CEFA}{v_z}q_y)\mathbf{i} + \\ &\phantom{=} (-v_wq_y + \textcolor{red}{v_x}q_z + \textcolor{green}{v_y}q_w - \textcolor{#87CEFA}{v_z}q_x)\mathbf{j} + \\ &\phantom{=} (-v_wq_z - \textcolor{red}{v_x}q_y + \textcolor{green}{v_y}q_x + \textcolor{#87CEFA}{v_z}q_w)\mathbf{k} \\[5pt] &= \begin{bmatrix} q_w & q_x & q_y & q_z \\ -q_x & q_w & -q_z & q_y \\ -q_y & q_z & q_w & -q_x \\ -q_z & -q_y & q_x & q_w \end{bmatrix} \begin{bmatrix} v_w \\ \textcolor{red}{v_x} \\ \textcolor{green}{v_y} \\ \textcolor{#87CEFA}{v_z} \end{bmatrix} \end{align*}

所以我们得出,

qv=[qwqxqyqzqxqwqzqyqyqzqwqxqzqyqxqw][vwvxvyvz]qv = \begin{bmatrix} q_w & -q_x & -q_y & -q_z \\ q_x & q_w & -q_z & q_y \\ q_y & q_z & q_w & -q_x \\ q_z & -q_y & q_x & q_w \end{bmatrix} \begin{bmatrix} v_w \\ \textcolor{red}{v_x} \\ \textcolor{green}{v_y} \\ \textcolor{#87CEFA}{v_z} \end{bmatrix}

这是将 qvqv 写为 矩阵形式 的相乘,可以将 qq 所代表的矩阵定义为 左乘矩阵 L(q)\mathbf{L}(q),因为 qq 乘在 vv左边,那么可以得到

qv=L(q)vcolqv = \mathbf{L}(q)v_{\mathrm{col}}
  • vcolv_{\mathrm{col}} 是写为 列向量 形式的 纯四元数

同样,可以将 qq^\ast 所代表的矩阵定义为 右乘矩阵 R(q)\mathbf{R}(q^\ast),因为 qq^\ast 乘在 vv右边,我认为这是一个糟糕的名字,因为以矩阵乘法形式表示时,R(q)\mathbf{R}(q^\ast) 仍然乘在 列向量 的左边

vq=[qwqxqyqzqxqwqzqyqyqzqwqxqzqyqxqw][vwvxvyvz]=R(q)vcol\begin{align*} vq^\ast &= \begin{bmatrix} q_w & q_x & q_y & q_z \\ -q_x & q_w & -q_z & q_y \\ -q_y & q_z & q_w & -q_x \\ -q_z & -q_y & q_x & q_w \end{bmatrix} \begin{bmatrix} v_w \\ \textcolor{red}{v_x} \\ \textcolor{green}{v_y} \\ \textcolor{#87CEFA}{v_z} \end{bmatrix} \\ &= \mathbf{R}(q^\ast)v_{\mathrm{col}} \end{align*}
  • vcolv_{\mathrm{col}} 是写为 列向量 形式的 纯四元数

如此一来,我们得到

qvq=L(q)R(q)vcolqvq^\ast = \mathbf{L}(q)\mathbf{R}(q^\ast) v_{\mathrm{col}}

只要计算出 L(q)R(q)\mathbf{L}(q)\mathbf{R}(q^\ast) 就可以将表示旋转的 四元数 转换为 旋转矩阵 形式

L(q)R(q)=[qwqxqyqzqxqwqzqyqyqzqwqxqzqyqxqw][qwqxqyqzqxqwqzqyqyqzqwqxqzqyqxqw]=[w2+x2+y2+z20000w2+x2y2z22xy2wz2xz+2wy02xy+2wzw2x2+y2z22yz2wx02xz2wy2yz+2wxw2x2y2+z2]\begin{align*} \mathbf{L}(q)\mathbf{R}(q^\ast) &= \begin{bmatrix} q_w & -q_x & -q_y & -q_z \\ q_x & q_w & -q_z & q_y \\ q_y & q_z & q_w & -q_x \\ q_z & -q_y & q_x & q_w \end{bmatrix} \begin{bmatrix} q_w & q_x & q_y & q_z \\ -q_x & q_w & -q_z & q_y \\ -q_y & q_z & q_w & -q_x \\ -q_z & -q_y & q_x & q_w \end{bmatrix} \\ &= \boxed{ \begin{bmatrix} w^{2}+x^{2}+y^{2}+z^{2} & 0 & 0 & 0\\[4pt] 0 & w^{2}+x^{2}-y^{2}-z^{2} & 2xy-2wz & 2xz+2wy\\[4pt] 0 & 2xy+2wz & w^{2}-x^{2}+y^{2}-z^{2} & 2yz-2wx\\[4pt] 0 & 2xz-2wy & 2yz+2wx & w^{2}-x^{2}-y^{2}+z^{2} \end{bmatrix} } \end{align*}

注意有些推导可能最终是下面这样

[w2+x2y2z22xy2wz2xz+2wy02xy+2wzw2x2+y2z22yz2wx02xz2wy2yz+2wxw2x2y2+z20000w2+x2+y2+z2]\begin{bmatrix} w^{2}+x^{2}-y^{2}-z^{2} & 2xy-2wz & 2xz+2wy & 0 \\[4pt] 2xy+2wz & w^{2}-x^{2}+y^{2}-z^{2} & 2yz-2wx & 0 \\[4pt] 2xz-2wy & 2yz+2wx & w^{2}-x^{2}-y^{2}+z^{2} & 0 \\[4pt] 0 & 0 & 0 & w^{2}+x^{2}+y^{2}+z^{2} \end{bmatrix}

这和我们的推导结果在逻辑上并没有什么不同,只是我们使用的是 (w,x,y,z)(w,x,y,z),而这个结果则使用 (x,y,z,w)(x,y,z,w)

所以 四元数 形式表示的旋转 转换为对应的 矩阵 形式表示,也可以如下实现

Matrix4 ToMatrix(Quaternion q) {
    float ww = q.w * q.w;
    float xx = q.x * q.x;
    float yy = q.y * q.y;
    float zz = q.z * q.z;
 
    float wx = q.w * q.x;
    float wy = q.w * q.y;
    float wz = q.w * q.z;
 
    float xy = q.x * q.y;
    float xz = q.x * q.z;
 
    float yz = q.y * q.z;
 
    return Matrix4(
        ww + xx - yy - zz, 2 * xy - 2 * wz, 2 * xz + 2 * wy, 0,
        2 * xy + 2 * wz, ww - xx + yy - zz, 2 * yz - 2 * wx, 0,
        2 * xz - 2 * wy, 2 * yz + 2 * wx, ww - xx - yy + zz, 0,
        0, 0, 0, ww + xx + yy + zz
    );
 
    // Just so there is no confusion, the above constructor takes arguments
    // in row order, but stores them in column order (transposed).
    // See the non-optimal example for memory layout.
}

如果要转换为 3×33\times 3 旋转矩阵 而不是 4×44\times 4 的矩阵,只需要丢弃 最后一行最后一列,也就是很多 0 的那行和列

这种方法相比起 旋转基向量 来将 四元数 转换为 矩阵,计算上更为高效,在工程中几乎总是使用这种 公式展开法,因为它

  • (没有 3 次四元数-向量乘法)
  • (不需要显式归一化)
  • 结果自然正交(如果输入四元数是单位的,而表示旋转的四元数应当是单位四元数,否则说明存在问题)