计算机图形学中的四元数详解

390 阅读3分钟

一、四元数基础概念

在计算机图形学中,四元数是一种用于表示旋转和方向的强大数学工具。四元数由一个实部和三个虚部组成,形式为q = w + xi + yj + zk ,其中w是实部,x、y、z分别是虚部i、j、k的系数。虚部i、j、k满足特定的乘法规则:i * i = j * j = k * k = -1,i * j = k,j * k = i,k * i = j ,并且乘法不满足交换律,例如j * i = -k。

四元数与复数类似,但扩展到了四维空间,相比传统的欧拉角和旋转矩阵,四元数在表示旋转时具有诸多优势,如避免万向节死锁、更高效的插值运算等。

二、四元数的运算

1. 四元数的加法

两个四元数q1 = w1 + x1i + y1j + z1k和q2 = w2 + x2i + y2j + z2k相加,结果是一个新的四元数q = (w1 + w2) + (x1 + x2)i + (y1 + y2)j + (z1 + z2)k。在 JavaScript 中可以这样实现:

function quaternionAdd(q1, q2) {
    return {
        w: q1.w + q2.w,
        x: q1.x + q2.x,
        y: q1.y + q2.y,
        z: q1.z + q2.z
    };
}

2. 四元数的乘法

四元数乘法遵循上述虚部的乘法规则,通过展开各项并合并同类项得到结果。两个四元数q1和q2相乘的 JavaScript 实现如下:

function quaternionMultiply(q1, q2) {
    return {
        w: q1.w * q2.w - q1.x * q2.x - q1.y * q2.y - q1.z * q2.z,
        x: q1.w * q2.x + q1.x * q2.w + q1.y * q2.z - q1.z * q2.y,
        y: q1.w * q2.y - q1.x * q2.z + q1.y * q2.w + q1.z * q2.x,
        z: q1.w * q2.z + q1.x * q2.y - q1.y * q2.x + q1.z * q2.w
    };
}

3. 四元数的共轭

四元数q = w + xi + yj + zk的共轭q* = w - xi - yj - zk,即虚部取反。在 JavaScript 中:

function quaternionConjugate(q) {
    return {
        w: q.w,
        x: -q.x,
        y: -q.y,
        z: -q.z
    };
}

4. 四元数的模

四元数q的模||q|| = √(w² + x² + y² + z²),表示四元数的大小。计算模的 JavaScript 代码如下:

function quaternionMagnitude(q) {
    return Math.sqrt(q.w * q.w + q.x * q.x + q.y * q.y + q.z * q.z);
}

5. 四元数的归一化

归一化四元数是将其四元数的模变为 1,通过将四元数的每个分量除以它的模来实现。归一化的 JavaScript 函数:

function quaternionNormalize(q) {
    const magnitude = quaternionMagnitude(q);
    return {
        w: q.w / magnitude,
        x: q.x / magnitude,
        y: q.y / magnitude,
        z: q.z / magnitude
    };
}

三、四元数表示旋转

在计算机图形学中,四元数常用于表示三维空间中的旋转。绕单位向量(x, y, z)旋转角度θ的四元数可以表示为:

q = cos(θ/2) + x * sin(θ/2) * i + y * sin(θ/2) * j + z * sin(θ/2) * k

将一个三维向量v用四元数q进行旋转,需要先将向量v转换为纯四元数p = 0 + vx * i + vy * j + vz * k ,然后通过计算q * p * q*得到旋转后的向量对应的四元数,其虚部即为旋转后的三维向量。以下是用 JavaScript 实现向量旋转的代码:

function rotateVectorByQuaternion(v, q) {
    const p = {w: 0, x: v.x, y: v.y, z: v.z};
    const qp = quaternionMultiply(q, p);
    const qpq = quaternionMultiply(qp, quaternionConjugate(q));
    return {x: qpq.x, y: qpq.y, z: qpq.z};
}

四、四元数插值

在动画和计算机图形应用中,常常需要在两个旋转之间进行平滑过渡,这就需要用到四元数插值。常见的插值方法是球面线性插值(Slerp)。

Slerp 在两个四元数q1和q2之间,根据插值因子t(0 到 1 之间)计算插值结果。其基本思想是在四维空间的超球面上沿着最短路径进行插值。JavaScript 实现 Slerp 的代码如下:

function slerp(q1, q2, t) {
    let dot = q1.w * q2.w + q1.x * q2.x + q1.y * q2.y + q1.z * q2.z;
    if (dot < 0) {
        q2 = {w: -q2.w, x: -q2.x, y: -q2.y, z: -q2.z};
        dot = -dot;
    }
    const k0 = 1 - t;
    const k1 = t;
    if (dot > 0.9995) {
        return {
            w: k0 * q1.w + k1 * q2.w,
            x: k0 * q1.x + k1 * q2.x,
            y: k0 * q1.y + k1 * q2.y,
            z: k0 * q1.z + k1 * q2.z
        };
    }
    const theta = Math.acos(dot);
    const sinTheta = Math.sin(theta);
    return {
        w: Math.sin((1 - t) * theta) / sinTheta * q1.w + Math.sin(t * theta) / sinTheta * q2.w,
        x: Math.sin((1 - t) * theta) / sinTheta * q1.x + Math.sin(t * theta) / sinTheta * q2.x,
        y: Math.sin((1 - t) * theta) / sinTheta * q1.y + Math.sin(t * theta) / sinTheta * q2.y,
        z: Math.sin((1 - t) * theta) / sinTheta * q1.z + Math.sin(t * theta) / sinTheta * q2.z
    };
}

通过以上内容,你已经对四元数在计算机图形学中的概念、运算、旋转表示和插值有了深入了解。在实际应用中,四元数可以用于游戏开发中的角色动画、3D 建模软件中的物体旋转等场景,发挥其高效和稳定的优势 。