Skip to content

138. API 查缺补漏:Quaternion、Matrix4

Published:

前面过了遍 MathUtils 和 Euler 的 api,这节过一下剩下的 Quaternion、Matrix4 的 api

Quaternion - 四元数

Quaternion 使用四元数表示旋转

前面我们用过:

const angle = THREE.MathUtils.degToRad(30);
const quaternion = new THREE.Quaternion();
quaternion.setFromAxisAngle(new THREE.Vector3(0, 0, 1), angle);
const euler = new THREE.Euler();

可以是绕某个轴旋转一定角度,然后转为 euler

它和欧拉角 eular 是可以相互转换的。

image.png

const euler = new THREE.Euler(THREE.MathUtils.degToRad(45), 0, 0);
const quaternion = new THREE.Quaternion();
quaternion.setFromEuler(euler);
console.log('From Euler:', quaternion);

image.png

那从 euler 转换成 quaternion 有什么好处呢?

主要是它可以做一些差值:

球面线性插值(Slerp)

这是四元数最重要的特性之一,可以平滑地在两个旋转之间插值:

const q1 = new THREE.Quaternion();
q1.setFromEuler(new THREE.Euler(0, 0, 0));

const q2 = new THREE.Quaternion();
q2.setFromEuler(new THREE.Euler(0, THREE.MathUtils.degToRad(90), 0));

const q = new THREE.Quaternion();
q.slerpQuaternions(q1, q2, 0.5);
console.log('Slerp result:', q);

这里第一个四元数是 0 度,第二个四元数是绕 y 旋转 90 度

那两者之间的中间角度呢?

可以通过 slerp 进行插值,拿 0.5 比例的角度值:

image.png

这样可能看不出是中间的值,我们转成 euler 然后再转角度:

const e = new THREE.Euler();
e.setFromQuaternion(q);
console.log('angle y:', THREE.MathUtils.radToDeg(e.y));

image.png

可以看到,是 45 度

这就是通过 Quaternion 计算的两个角度之间的插值。

那如果 30% 位置是什么角度呢?

image.png

改成 0.3

image.png

可以看到是 27 度

计算两个向量夹角

有了两个 Vector3 向量,计算这两个向量之间的角度,是不是一个很常见的需求?

这种就可以通过四元数 Quaternion 实现:

计算从向量 v1 到向量 v2 的旋转:

const v1 = new THREE.Vector3(1, 0, 0);
const v2 = new THREE.Vector3(0, 1, 0);
const quaternion = new THREE.Quaternion();
quaternion.setFromUnitVectors(v1, v2);
console.log('From vectors:', quaternion);

image.png

总之,Euler 用来存储角度值的,但是要计算角度的插值、向量之间的夹角等,需要转成四元数 Quaternion 来计算。

Matrix4 - 4x4 矩阵

Matrix4 用于 3D 变换(平移、旋转、缩放),是 Three.js 中最重要的矩阵类。

当然,矩阵是比较底层的运算,了解即可,一般我们都是通过 eular、quaternion 计算和设置。

直接 new 的是单位矩阵:

// 创建单位矩阵
const matrix = new THREE.Matrix4();
console.log('单位矩阵:', matrix);

image.png

可以通过 set 方法设置值:

matrix.set(
    2, 0, 0, 0,
    0, 2, 0, 0,
    0, 0, 2, 0,
    0, 0, 0, 2
);
console.log(matrix);

image.png

也可以基于矩阵实现平移、旋转、缩放等功能。

其实我们修改 tanslate、scale、rotation 的底层都是矩阵运算:

平移、旋转、缩放

创建一个新的立方体:

创建 mesh2.js

import * as THREE from 'three';

const geometry = new THREE.BoxGeometry(100, 100, 100);
const material = new THREE.MeshPhongMaterial({
    color: 'orange'
})
const mesh = new THREE.Mesh(geometry, material);

export default mesh;

引入下:

image.png

image.png

这次我们通过矩阵来对它做变换:

image.png

const translateMatrix = new THREE.Matrix4().makeTranslation(100, 0, 0);
const rotateMatrix = new THREE.Matrix4().makeRotationFromEuler(
    new THREE.Euler(THREE.MathUtils.degToRad(45), 0, 0)
);
const scaleMatrix = new THREE.Matrix4().makeScale(1, 3, 1);

// 组合矩阵:注意顺序是从右到左应用
// 先缩放,再旋转,最后平移
const matrix = new THREE.Matrix4();
matrix.multiplyMatrices(translateMatrix, rotateMatrix); 
matrix.multiplyMatrices(matrix, scaleMatrix); 

我们先创建了平移矩阵,然后创建了旋转矩阵,之后创建了放缩矩阵。

组合矩阵的话需要用乘法,矩阵是从右往左生效,也就是最后的乘数先生效。

所以先放缩、再旋转、再位移。

看下效果:

2025-11-02 13.23.13.gif

可以看到,位移、旋转、缩放都生效了。

我们做各种变换,底层都是矩阵乘法,就是这个意思。

当然,这里你可以这样写:

image.png

translateMatrix.multiply(rotateMatrix).multiply(scaleMatrix);
mesh.applyMatrix4(translateMatrix);

连乘就行,效果一样:

2025-11-02 13.25.49.gif

这里的旋转我们是用的 Euler,那同理,换成 Quaternion 等方式也是一样的:

image.png

const q1 = new THREE.Quaternion();
q1.setFromEuler(new THREE.Euler(0, 0, 0));

const q2 = new THREE.Quaternion();
q2.setFromEuler(new THREE.Euler(0, THREE.MathUtils.degToRad(90), 0));

const q = new THREE.Quaternion();
q.slerpQuaternions(q1, q2, 0.3);

const rotateMatrix2 = new THREE.Matrix4();
rotateMatrix2.makeRotationFromQuaternion(q);

比如我们用插值计算了两个角度之间的四元数,把它设置到矩阵,用 makeRotationFromQuaternion

然后再用这个矩阵来旋转:

2025-11-02 13.32.08.gif

也同样可以生效。

或者用 makeRotationAxis 的 api,绕某个轴旋转的角度转成矩阵:

image.png

const rotateMatrix3 = new THREE.Matrix4();

const axis = new THREE.Vector3(0, 0, 1);
rotateMatrix3.makeRotationAxis(axis, Math.PI / 4);

translateMatrix.multiply(rotateMatrix3).multiply(scaleMatrix);

2025-11-02 13.34.14.gif

四元数也有这个 setFromAxisAngle 的 api,你也可以用 Quaternion 计算角度,最后转成旋转矩阵。

总之,Eular、Quarternion 等最终都会转为旋转矩阵,设置到 Matrix4 的矩阵,然后应用到物体上。

矩阵还支持除了乘法的更多运算:

const m1 = new THREE.Matrix4();
const m2 = new THREE.Matrix4();

// 矩阵乘法 m1 = m1 * m2
m1.multiply(m2);

// 预乘(右乘)m1 = m2 * m1
m1.premultiply(m2);

// 矩阵相加
m1.add(m2);

// 矩阵转置
m1.transpose();

// 求逆矩阵
m1.invert();

// 计算行列式
const determinant = m1.determinant();

这些涉及很多数学知识,就不展开了。

大家会用矩阵乘法来应用变换就行。

分解矩阵

有了 Matrix4 的变换矩阵,可不可以从中提取出位移、旋转、缩放 的矩阵呢?

可以的:

这里乘出来的矩阵还可以用 decompose 分解:

image.png

const position = new THREE.Vector3();
const quaternion = new THREE.Quaternion();
const scale = new THREE.Vector3();

translateMatrix.decompose(position, quaternion, scale);
console.log('Position:', position);
console.log('Rotation:', quaternion);
console.log('Scale:', scale);

image.png

可以看到分解结果也是对的。

案例代码上传了小册仓库

总结

这节我们过了一遍 Quaternion 和 Matrix4 的 api。

Euler 只是存储角度,而 Quaternion 可以对角度做插值、计算两个向量夹角等。

而不管是平移、旋转(Euler、Quaternion)、放缩,最终都是设置到 Matrix4,然后应用到物体

理解了 Euler、Quaternion、Matrix4 之间的关系,再做旋转、放缩、平移等变换的时候就更灵活了。

评论