19 · Matrix3:行列式、求逆与转置

返回系列目录 · 上一章 · 下一章

本章代码默认 import * as THREE from 'three';。正向变换回答“本地点去了哪里”,逆变换回答“这个外部点原来在哪里”。当变换把一个维度压扁时,第二个问题可能没有唯一答案。

determinant():空间有没有被压扁

无参数,返回数字,不修改矩阵。对于三维线性变换,行列式是有向体积比例;对末行为 (0,0,1) 的二维仿射变换,它是有向面积比例。

det⁡[abcdefghi]=a(ei−fh)−b(di−fg)+c(dh−eg). \det\begin{bmatrix}a&b&c\\d&e&f\\g&h&i\end{bmatrix} =a(ei-fh)-b(di-fg)+c(dh-eg).

diag(2,3,4) 的行列式为 24,单位立方体变成体积 24 的平行六面体。diag(-2,3,4) 为 -24,同时翻转空间定向;diag(2,0,4) 为零,所有点落到一个平面,无法从结果恢复原来的 Y。

不要把负值当成异常:镜像变换合法且可逆。也不要只凭“行列式很小”判断不稳定,例如统一缩放 0.001 的行列式是 1e-9,但各轴比例一致;diag(1e12,1e-12,1) 的行列式为 1,却可能涉及严重尺度差异。阈值需要考虑矩阵尺度与业务范围。

invert():恢复变换前的坐标

无参数,原地计算逆矩阵,返回 this。若 M 可逆,则 M⁻¹ M = I。例如 diag(2,3,1) 的逆是 diag(1/2,1/3,1),把 (4,9,1) 还原为 (2,3,1)。

const forward = new THREE.Matrix3().makeTranslation(3, 2);
forward.multiply(new THREE.Matrix3().makeScale(2, 3));
const inverse = forward.clone().invert();
const screenPoint = new THREE.Vector2(7, 11);
console.log(screenPoint.applyMatrix3(inverse).toArray()); // [2, 3]

这里先缩放再平移;还原时先撤销平移再撤销缩放:(AB)⁻¹ = B⁻¹ A⁻¹。这是平面图编辑器把点击位置转换回图纸坐标的核心步骤。

r185 对精确零行列式返回零矩阵,不抛异常,也不返回 null。 因此不要把“调用成功”当成可逆性的证明。近奇异矩阵不会自动拒绝,可能放大误差;缩放滑块应结合需求设置正的最小值,或者明确禁止在退化状态下做逆向拾取。验证时检查 inverse * forward 接近单位矩阵,并用业务坐标做往返测试。

transpose():交换行与列

无参数,原地把 Mij 和 Mji 交换,返回 this。例如:

[123456789]T=[147258369]. \begin{bmatrix}1&2&3\\4&5&6\\7&8&9\end{bmatrix}^{T} =\begin{bmatrix}1&4&7\\2&5&8\\3&6&9\end{bmatrix}.

转置既不是普遍的求逆,也不是切换 elements 存储约定。矩阵仍按列主序保存,只是表达了不同的线性变换。只有正交矩阵(包括纯旋转,以及某些反射)满足 M⁻¹=Mᵀ;非均匀缩放和剪切不满足。

const rotation = new THREE.Matrix3().makeRotation(Math.PI / 3);
const reverseRotation = rotation.clone().transpose();
const point = new THREE.Vector2(1, 0);
point.applyMatrix3(rotation).applyMatrix3(reverseRotation);
// point 约等于 (1, 0),因为 rotation 是正交矩阵。

transposeIntoArray(array)

接受目标数组,把转置矩阵的列主序元素写入索引 0…8。原矩阵不变,返回值仍为原 Matrix3,不是数组。 不接受 offset。

const matrix = new THREE.Matrix3().set(1, 2, 3, 4, 5, 6, 7, 8, 9);
const values = [];
const returned = matrix.transposeIntoArray(values);
console.log(values); // [1, 2, 3, 4, 5, 6, 7, 8, 9]
console.log(returned === matrix); // true

这组数也恰好是原矩阵的逐行展开,适合给采用逐行布局的外部程序准备数据。目标数组必须与 matrix.elements 分离;传入同一个数组会在读写交错中覆盖尚未读取的数据,不是可靠的原地转置方式。向 Three.js 的 mat3 uniform 传 Matrix3 本身即可,不需要调用此方法。

实践:识别“逆变换错了”究竟错在哪里

对图纸编辑器依次检查:输入是否已经减去 canvas 页面偏移;缩放和平移是否与绘制顺序相同;矩阵是否沿用过期参数;是否错误地原地 invert() 了绘制还需要的正向矩阵。用三个不共线的已知点做往返检查,比只观察一个点可靠:一个错误矩阵也可能恰好固定原点。

练习

  1. 计算二维先镜像 X 再放大 Y 三倍的行列式,解释面积和绕序分别如何变化。
  2. 对包含平移、旋转、缩放的二维矩阵验证三点往返误差。
  3. 把缩放 X 改为零,观察 r185 的逆矩阵结果;为编辑器设计明确的不可编辑状态。
  4. 用一个剪切矩阵证明“转置就是逆矩阵”不成立。