第3章:矩阵与变换
3.1 矩阵基础
3.1.1 什么是矩阵
矩阵(Matrix)是由数字按行和列排列成的矩形阵列。在计算机图形学中,矩阵是实现几何变换的核心工具。
矩阵的基本形式
一个 m×n 矩阵有 m 行和 n 列:
列1 列2 列3
┌ ┐
行1 │ a b c │
行2 │ d e f │
└ ┘
这是一个 2×3 矩阵
常见的矩阵尺寸:
2×2 矩阵(2D 变换):
┌ ┐
│ a b │
│ c d │
└ ┘
3×3 矩阵(2D 齐次/3D 变换):
┌ ┐
│ a b c │
│ d e f │
│ g h i │
└ ┘
4×4 矩阵(3D 齐次变换):
┌ ┐
│ a b c d│
│ e f g h│
│ i j k l│
│ m n o p│
└ ┘3.1.2 矩阵的数学表示
矩阵元素的索引
┌ ┐
│ m₁₁ m₁₂ m₁₃│
M = │ m₂₁ m₂₂ m₂₃│
│ m₃₁ m₃₂ m₃₃│
└ ┘
m_ij 表示第 i 行第 j 列的元素
注意:
- 数学中通常从1开始索引
- 编程中通常从0开始索引
编程索引:
┌ ┐
│ m₀₀ m₀₁ m₀₂│
M = │ m₁₀ m₁₁ m₁₂│
│ m₂₀ m₂₁ m₂₂│
└ ┘3.1.3 特殊矩阵
单位矩阵(Identity Matrix)
对角线为1,其余为0的方阵:
2×2 单位矩阵: 3×3 单位矩阵:
┌ ┐ ┌ ┐
│ 1 0 │ │ 1 0 0 │
│ 0 1 │ │ 0 1 0 │
└ ┘ │ 0 0 1 │
└ ┘
性质:任何矩阵乘以单位矩阵等于自身
M × I = I × M = M
零矩阵:
所有元素都是0的矩阵
┌ ┐
│ 0 0 │
│ 0 0 │
└ ┘
对角矩阵:
只有对角线上有非零元素
┌ ┐
│ a 0 0 │
│ 0 b 0 │
│ 0 0 c │
└ ┘3.1.4 JavaScript 中的矩阵表示
javascript
// 方式1:二维数组(直观,但访问慢)
const matrix2D = [
[1, 0, 0],
[0, 1, 0],
[0, 0, 1]
];
// 方式2:一维数组(GPU 友好,WebGL 使用此方式)
// 列主序(Column-major)- OpenGL/WebGL 使用
const matrixColumnMajor = new Float32Array([
1, 0, 0, // 第1列
0, 1, 0, // 第2列
0, 0, 1 // 第3列
]);
// 行主序(Row-major)- DirectX 使用
const matrixRowMajor = new Float32Array([
1, 0, 0, // 第1行
0, 1, 0, // 第2行
0, 0, 1 // 第3行
]);
// 方式3:类封装
class Matrix3 {
constructor() {
// 使用列主序存储
this.elements = new Float32Array([
1, 0, 0,
0, 1, 0,
0, 0, 1
]);
}
// 获取元素 (row, col)
get(row, col) {
return this.elements[col * 3 + row];
}
// 设置元素
set(row, col, value) {
this.elements[col * 3 + row] = value;
}
}3.2 矩阵乘法
3.2.1 矩阵乘法规则
矩阵乘法是图形学中最重要的运算之一,它用于组合多个变换:
矩阵乘法的条件
A(m×n) × B(n×p) = C(m×p)
- A 的列数必须等于 B 的行数
- 结果矩阵的维度是 A 的行数 × B 的列数
乘法计算过程:
C_ij = Σ(A_ik × B_kj) (对 k 求和)
即:C 的第 i 行第 j 列元素 = A 的第 i 行 · B 的第 j 列
2×2 矩阵乘法示例:
┌ ┐ ┌ ┐ ┌ ┐
│ a b │ × │ e f │ = │ ae+bg af+bh │
│ c d │ │ g h │ │ ce+dg cf+dh │
└ ┘ └ ┘ └ ┘
具体数值例子:
┌ ┐ ┌ ┐ ┌ ┐
│ 1 2 │ × │ 5 6 │ = │ 1×5+2×7 1×6+2×8 │
│ 3 4 │ │ 7 8 │ │ 3×5+4×7 3×6+4×8 │
└ ┘ └ ┘ └ ┘
= ┌ ┐
│ 19 22 │
│ 43 50 │
└ ┘3.2.2 矩阵乘法的性质
矩阵乘法的重要性质
1. 不满足交换律
A × B ≠ B × A (一般情况下)
这在图形学中很重要!
先旋转后平移 ≠ 先平移后旋转
2. 满足结合律
(A × B) × C = A × (B × C)
这允许我们预计算变换矩阵:
MVP = Model × View × Projection
3. 满足分配律
A × (B + C) = A×B + A×C
4. 单位矩阵
A × I = I × A = A3.2.3 矩阵与向量乘法
矩阵变换向量
2×2 矩阵变换 2D 向量:
┌ ┐ ┌ ┐ ┌ ┐
│ a b │ × │ x │ = │ ax + by │
│ c d │ │ y │ │ cx + dy │
└ ┘ └ ┘ └ ┘
3×3 矩阵变换 3D 向量:
┌ ┐ ┌ ┐ ┌ ┐
│ a b c │ │ x │ │ ax + by + cz │
│ d e f │ × │ y │ = │ dx + ey + fz │
│ g h i │ │ z │ │ gx + hy + iz │
└ ┘ └ ┘ └ ┘3.2.4 代码实现
javascript
/**
* 3x3 矩阵类
*/
class Matrix3 {
constructor() {
// 列主序存储 3x3 矩阵
this.elements = new Float32Array([
1, 0, 0,
0, 1, 0,
0, 0, 1
]);
}
// 设置为单位矩阵
identity() {
this.elements.set([
1, 0, 0,
0, 1, 0,
0, 0, 1
]);
return this;
}
// 矩阵乘法:this × other
multiply(other) {
const a = this.elements;
const b = other.elements;
const result = new Matrix3();
const r = result.elements;
// 列主序乘法
r[0] = a[0]*b[0] + a[3]*b[1] + a[6]*b[2];
r[1] = a[1]*b[0] + a[4]*b[1] + a[7]*b[2];
r[2] = a[2]*b[0] + a[5]*b[1] + a[8]*b[2];
r[3] = a[0]*b[3] + a[3]*b[4] + a[6]*b[5];
r[4] = a[1]*b[3] + a[4]*b[4] + a[7]*b[5];
r[5] = a[2]*b[3] + a[5]*b[4] + a[8]*b[5];
r[6] = a[0]*b[6] + a[3]*b[7] + a[6]*b[8];
r[7] = a[1]*b[6] + a[4]*b[7] + a[7]*b[8];
r[8] = a[2]*b[6] + a[5]*b[7] + a[8]*b[8];
return result;
}
// 矩阵乘以向量
transformVector(x, y) {
const e = this.elements;
return {
x: e[0]*x + e[3]*y + e[6],
y: e[1]*x + e[4]*y + e[7]
};
}
// 矩阵乘以点(带齐次坐标)
transformPoint(x, y) {
const e = this.elements;
const w = e[2]*x + e[5]*y + e[8];
return {
x: (e[0]*x + e[3]*y + e[6]) / w,
y: (e[1]*x + e[4]*y + e[7]) / w
};
}
}3.3 2D 变换
3.3.1 缩放变换
缩放矩阵
将点 (x, y) 缩放 sx 和 sy 倍:
(x', y') = (sx × x, sy × y)
矩阵形式:
┌ ┐ ┌ ┐ ┌ ┐
│ sx 0 │ × │ x │ = │ sx × x │
│ 0 sy │ │ y │ │ sy × y │
└ ┘ └ ┘ └ ┘
几何效果:
原图形: 缩放 (2, 1.5) 后:
┌───┐ ┌──────────┐
│ │ │ │
│ │ ──► │ │
│ │ │ │
└───┘ └──────────┘
特殊情况:
- sx = sy = 1:无变化
- sx = sy > 1:均匀放大
- sx = sy < 1:均匀缩小
- sx ≠ sy:非均匀缩放(会改变比例)
- sx 或 sy 为负:镜像翻转3.3.2 旋转变换
旋转矩阵
将点 (x, y) 绕原点逆时针旋转角度 θ:
推导:
设点 P 的极坐标为 (r, α):
x = r cos(α)
y = r sin(α)
旋转 θ 后:
x' = r cos(α + θ) = r(cos(α)cos(θ) - sin(α)sin(θ))
= x cos(θ) - y sin(θ)
y' = r sin(α + θ) = r(sin(α)cos(θ) + cos(α)sin(θ))
= x sin(θ) + y cos(θ)
旋转矩阵:
┌ ┐ ┌ ┐ ┌ ┐
│ cos(θ) -sin(θ) │ × │ x │ = │ x·cos(θ) - y·sin(θ) │
│ sin(θ) cos(θ) │ │ y │ │ x·sin(θ) + y·cos(θ) │
└ ┘ └ ┘ └ ┘
几何效果:
y y
▲ ▲
│ P' │
│ ╱ │
│ ╱ θ │
│ ╱──╲ │
│╱ P │
────┼──────►x ────┼──────►x
│ │
常用旋转角度:
θ = 90°: θ = 180°:
┌ ┐ ┌ ┐
│ 0 -1│ │-1 0 │
│ 1 0│ │ 0 -1 │
└ ┘ └ ┘3.3.3 平移变换
平移变换
将点 (x, y) 平移 (tx, ty):
x' = x + tx
y' = y + ty
问题:平移不能用 2×2 矩阵表示!
┌ ┐ ┌ ┐ ┌ ┐
│ ? ? │ × │ x │ ≠ │ x+tx │
│ ? ? │ │ y │ │ y+ty │
└ ┘ └ ┘ └ ┘
解决方案:使用齐次坐标(3×3 矩阵)
┌ ┐ ┌ ┐ ┌ ┐
│ 1 0 tx │ │ x │ │ x+tx │
│ 0 1 ty │ × │ y │ = │ y+ty │
│ 0 0 1 │ │ 1 │ │ 1 │
└ ┘ └ ┘ └ ┘
几何效果:
┌───┐ ┌───┐
│ │ 平移(3,2) │ │
│ │ ─────────► │ │
│ A │ │ A'│
└───┘ └───┘
原位置 新位置3.3.4 切变变换
切变(Shear)变换
水平切变(沿 x 轴):
x' = x + ky
y' = y
┌ ┐
│ 1 k │
│ 0 1 │
└ ┘
垂直切变(沿 y 轴):
x' = x
y' = kx + y
┌ ┐
│ 1 0 │
│ k 1 │
└ ┘
几何效果:
原图形: 水平切变后:
┌───────┐ ╱───────╱
│ │ ╱ ╱
│ │ ──► ╱ ╱
│ │ ╱ ╱
└───────┘ ╱───────╱3.3.5 代码实现
javascript
class Matrix3 {
// ... 前面的代码 ...
// 创建缩放矩阵
static scale(sx, sy) {
const m = new Matrix3();
m.elements[0] = sx;
m.elements[4] = sy;
return m;
}
// 创建旋转矩阵(角度为弧度)
static rotate(angle) {
const m = new Matrix3();
const c = Math.cos(angle);
const s = Math.sin(angle);
m.elements[0] = c;
m.elements[1] = s;
m.elements[3] = -s;
m.elements[4] = c;
return m;
}
// 创建平移矩阵
static translate(tx, ty) {
const m = new Matrix3();
m.elements[6] = tx;
m.elements[7] = ty;
return m;
}
// 创建水平切变矩阵
static shearX(k) {
const m = new Matrix3();
m.elements[3] = k;
return m;
}
// 创建垂直切变矩阵
static shearY(k) {
const m = new Matrix3();
m.elements[1] = k;
return m;
}
// 组合:缩放
scale(sx, sy) {
return Matrix3.scale(sx, sy).multiply(this);
}
// 组合:旋转
rotate(angle) {
return Matrix3.rotate(angle).multiply(this);
}
// 组合:平移
translate(tx, ty) {
return Matrix3.translate(tx, ty).multiply(this);
}
}
// 使用示例
const transform = new Matrix3()
.translate(100, 100) // 先平移
.rotate(Math.PI / 4) // 再旋转 45°
.scale(2, 2); // 最后缩放
const point = transform.transformPoint(0, 0);
console.log(point); // { x: 100, y: 100 }3.4 3D 变换
3.4.1 3D 缩放
3D 缩放矩阵
┌ ┐
│ sx 0 0 0│
│ 0 sy 0 0│
│ 0 0 sz 0│
│ 0 0 0 1│
└ ┘
将点 (x, y, z) 变换为 (sx·x, sy·y, sz·z)3.4.2 3D 旋转
绕 X 轴旋转:
y y
▲ ▲ z'
│ │ ╱
│ │ ╱
│ ──► │ ╱ θ
└──────►z └╱──────►z
┌ ┐
│ 1 0 0 0│
│ 0 cos(θ) -sin(θ) 0│
│ 0 sin(θ) cos(θ) 0│
│ 0 0 0 1│
└ ┘
绕 Y 轴旋转:
y y
▲ ▲
│ │
│ │
│ ──► │
x◄───┘ └───►x'
╱ ╲ ╱
z z
┌ ┐
│ cos(θ) 0 sin(θ) 0│
│ 0 1 0 0│
│-sin(θ) 0 cos(θ) 0│
│ 0 0 0 1│
└ ┘
绕 Z 轴旋转:
y y'
▲ ╱
│ ╱
│ ╱ θ
│╱
└──────►x
┌ ┐
│ cos(θ) -sin(θ) 0 0│
│ sin(θ) cos(θ) 0 0│
│ 0 0 1 0│
│ 0 0 0 1│
└ ┘3.4.3 3D 平移
3D 平移矩阵
┌ ┐
│ 1 0 0 tx│
│ 0 1 0 ty│
│ 0 0 1 tz│
│ 0 0 0 1 │
└ ┘
将点 (x, y, z) 变换为 (x+tx, y+ty, z+tz)3.4.4 代码实现
javascript
/**
* 4x4 矩阵类(用于 3D 变换)
*/
class Matrix4 {
constructor() {
// 列主序存储
this.elements = new Float32Array([
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
]);
}
identity() {
this.elements.set([
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
]);
return this;
}
// 缩放
static scale(sx, sy, sz) {
const m = new Matrix4();
m.elements[0] = sx;
m.elements[5] = sy;
m.elements[10] = sz;
return m;
}
// 绕 X 轴旋转
static rotateX(angle) {
const m = new Matrix4();
const c = Math.cos(angle);
const s = Math.sin(angle);
m.elements[5] = c;
m.elements[6] = s;
m.elements[9] = -s;
m.elements[10] = c;
return m;
}
// 绕 Y 轴旋转
static rotateY(angle) {
const m = new Matrix4();
const c = Math.cos(angle);
const s = Math.sin(angle);
m.elements[0] = c;
m.elements[2] = -s;
m.elements[8] = s;
m.elements[10] = c;
return m;
}
// 绕 Z 轴旋转
static rotateZ(angle) {
const m = new Matrix4();
const c = Math.cos(angle);
const s = Math.sin(angle);
m.elements[0] = c;
m.elements[1] = s;
m.elements[4] = -s;
m.elements[5] = c;
return m;
}
// 平移
static translate(tx, ty, tz) {
const m = new Matrix4();
m.elements[12] = tx;
m.elements[13] = ty;
m.elements[14] = tz;
return m;
}
// 矩阵乘法
multiply(other) {
const a = this.elements;
const b = other.elements;
const result = new Matrix4();
const r = result.elements;
for (let col = 0; col < 4; col++) {
for (let row = 0; row < 4; row++) {
r[col * 4 + row] =
a[0 * 4 + row] * b[col * 4 + 0] +
a[1 * 4 + row] * b[col * 4 + 1] +
a[2 * 4 + row] * b[col * 4 + 2] +
a[3 * 4 + row] * b[col * 4 + 3];
}
}
return result;
}
// 变换点
transformPoint(x, y, z) {
const e = this.elements;
const w = e[3]*x + e[7]*y + e[11]*z + e[15];
return {
x: (e[0]*x + e[4]*y + e[8]*z + e[12]) / w,
y: (e[1]*x + e[5]*y + e[9]*z + e[13]) / w,
z: (e[2]*x + e[6]*y + e[10]*z + e[14]) / w
};
}
}3.5 逆矩阵
3.5.1 逆矩阵的概念
逆矩阵
如果矩阵 A 存在逆矩阵 A⁻¹,则:
A × A⁻¹ = A⁻¹ × A = I(单位矩阵)
几何意义:
逆矩阵代表"撤销"原变换
例如:
- 旋转 θ 的逆是旋转 -θ
- 平移 (tx, ty) 的逆是平移 (-tx, -ty)
- 缩放 (sx, sy) 的逆是缩放 (1/sx, 1/sy)
应用场景:
- 从世界坐标转回局部坐标
- 计算相机的视图矩阵
- 碰撞检测中的坐标转换3.5.2 各种变换的逆矩阵
平移的逆:
T(tx, ty)⁻¹ = T(-tx, -ty)
┌ ┐⁻¹ ┌ ┐
│ 1 0 tx │ │ 1 0 -tx │
│ 0 1 ty │ = │ 0 1 -ty │
│ 0 0 1 │ │ 0 0 1 │
└ ┘ └ ┘
旋转的逆:
R(θ)⁻¹ = R(-θ) = Rᵀ(转置)
┌ ┐⁻¹ ┌ ┐
│ cos(θ) -sin(θ) │ │ cos(θ) sin(θ) │
│ sin(θ) cos(θ) │ = │ -sin(θ) cos(θ) │
└ ┘ └ ┘
缩放的逆:
S(sx, sy)⁻¹ = S(1/sx, 1/sy)
┌ ┐⁻¹ ┌ ┐
│ sx 0 │ │ 1/sx 0 │
│ 0 sy │ = │ 0 1/sy │
└ ┘ └ ┘3.5.3 2×2 矩阵求逆
2×2 矩阵求逆公式
对于矩阵 A = ┌ ┐
│ a b │
│ c d │
└ ┘
行列式:det(A) = ad - bc
如果 det(A) ≠ 0,则:
A⁻¹ = (1/det(A)) × ┌ ┐
│ d -b │
│ -c a │
└ ┘
例子:
┌ ┐
│ 2 1 │ → det = 2×3 - 1×1 = 5
│ 1 3 │
└ ┘
逆矩阵 = (1/5) × ┌ ┐ ┌ ┐
│ 3 -1 │ = │ 0.6 -0.2 │
│ -1 2 │ │ -0.2 0.4 │
└ ┘ └ ┘3.5.4 代码实现
javascript
class Matrix3 {
// ... 前面的代码 ...
// 计算行列式
determinant() {
const e = this.elements;
return e[0] * (e[4] * e[8] - e[5] * e[7])
- e[3] * (e[1] * e[8] - e[2] * e[7])
+ e[6] * (e[1] * e[5] - e[2] * e[4]);
}
// 求逆矩阵
inverse() {
const det = this.determinant();
if (Math.abs(det) < 1e-10) {
console.warn('Matrix is not invertible');
return null;
}
const e = this.elements;
const invDet = 1 / det;
const result = new Matrix3();
const r = result.elements;
// 伴随矩阵除以行列式
r[0] = (e[4] * e[8] - e[5] * e[7]) * invDet;
r[1] = (e[2] * e[7] - e[1] * e[8]) * invDet;
r[2] = (e[1] * e[5] - e[2] * e[4]) * invDet;
r[3] = (e[5] * e[6] - e[3] * e[8]) * invDet;
r[4] = (e[0] * e[8] - e[2] * e[6]) * invDet;
r[5] = (e[2] * e[3] - e[0] * e[5]) * invDet;
r[6] = (e[3] * e[7] - e[4] * e[6]) * invDet;
r[7] = (e[1] * e[6] - e[0] * e[7]) * invDet;
r[8] = (e[0] * e[4] - e[1] * e[3]) * invDet;
return result;
}
// 快速求仿射变换的逆(假设最后一行是 [0, 0, 1])
inverseAffine() {
const e = this.elements;
const det = e[0] * e[4] - e[1] * e[3];
if (Math.abs(det) < 1e-10) {
return null;
}
const invDet = 1 / det;
const result = new Matrix3();
const r = result.elements;
r[0] = e[4] * invDet;
r[1] = -e[1] * invDet;
r[3] = -e[3] * invDet;
r[4] = e[0] * invDet;
// 计算平移部分的逆
r[6] = -(r[0] * e[6] + r[3] * e[7]);
r[7] = -(r[1] * e[6] + r[4] * e[7]);
return result;
}
}3.6 变换组合
3.6.1 变换顺序的重要性
变换顺序很重要!
矩阵乘法不满足交换律,所以变换的顺序会影响最终结果。
例子:先旋转后平移 vs 先平移后旋转
场景:
- 原点有一个正方形
- 旋转 45°
- 平移 (5, 0)
情况1:先旋转后平移
T × R × P
1. 旋转:正方形绕原点旋转 45°
2. 平移:整个场景向右移动
旋转后 平移后
◇ ◇
╱╲ ╱╲
╱ ╲ ╱ ╲
╱ ╲ ╱ ╲
◇ ◇ ───► ◇ ◇
╲ ╱ ╲ ╱
╲ ╱ ╲ ╱
╲╱ ╲╱
(原点) (5,0)附近
情况2:先平移后旋转
R × T × P
1. 平移:正方形移到 (5, 0)
2. 旋转:整个场景绕原点旋转(包括平移后的正方形)
平移后 旋转后
◇
╱╲
┌──┐ ╱ ╲
│ │ ╱ ╲
│ │ ───► ◇ ◇
└──┘ ╲ ╱
(5,0) ╲ ╱
╲╱
(旋转到其他位置)3.6.2 标准变换顺序
推荐的变换顺序
对于物体的变换,通常按以下顺序组合:
最终矩阵 = T × R × S × P
其中:
- S(Scale):缩放
- R(Rotate):旋转
- T(Translate):平移
- P:原始点
注意:矩阵从右向左作用于点!
这样做的好处:
1. 先缩放:改变物体大小
2. 再旋转:确定物体朝向
3. 最后平移:放到世界坐标中的位置
代码示例:
// 先缩放,再旋转,最后平移
const transform = Matrix3.translate(100, 200)
.multiply(Matrix3.rotate(Math.PI / 4))
.multiply(Matrix3.scale(2, 2));
// 变换点
const result = transform.transformPoint(x, y);3.6.3 绕任意点变换
绕任意点旋转
要绕点 (px, py) 旋转角度 θ:
1. 平移到原点:T(-px, -py)
2. 旋转:R(θ)
3. 平移回去:T(px, py)
最终矩阵 = T(px, py) × R(θ) × T(-px, -py)
图示:
原位置 移到原点 旋转后 移回去
P ◇ P'
│ ◇ ╱│╲ ╱│
│ ╱│╲ ╱ │ ╲ ╱ │
──┼── ──┼── ──┼── ──┼──
│ ╲│╱ ╲ │ ╱ ╲ │
│ ◇ ╲│╱ ╲│
◇ ◇ ◇
绕任意点缩放也是类似的流程:
T(px, py) × S(sx, sy) × T(-px, -py)3.6.4 代码实现
javascript
class Matrix3 {
// ... 前面的代码 ...
// 绕任意点旋转
static rotateAround(angle, cx, cy) {
return Matrix3.translate(cx, cy)
.multiply(Matrix3.rotate(angle))
.multiply(Matrix3.translate(-cx, -cy));
}
// 绕任意点缩放
static scaleAround(sx, sy, cx, cy) {
return Matrix3.translate(cx, cy)
.multiply(Matrix3.scale(sx, sy))
.multiply(Matrix3.translate(-cx, -cy));
}
// 从分解的变换参数创建矩阵
static compose(tx, ty, rotation, sx, sy) {
const c = Math.cos(rotation);
const s = Math.sin(rotation);
const m = new Matrix3();
const e = m.elements;
// 组合 T × R × S
e[0] = c * sx;
e[1] = s * sx;
e[3] = -s * sy;
e[4] = c * sy;
e[6] = tx;
e[7] = ty;
return m;
}
// 分解矩阵为变换参数
decompose() {
const e = this.elements;
// 提取平移
const tx = e[6];
const ty = e[7];
// 提取缩放
const sx = Math.sqrt(e[0] * e[0] + e[1] * e[1]);
const sy = Math.sqrt(e[3] * e[3] + e[4] * e[4]);
// 提取旋转
const rotation = Math.atan2(e[1], e[0]);
// 检查是否有负缩放(翻转)
const det = e[0] * e[4] - e[1] * e[3];
const sxFinal = sx;
const syFinal = det < 0 ? -sy : sy;
return {
tx, ty,
rotation,
sx: sxFinal,
sy: syFinal
};
}
}
// 使用示例
// 绕点 (50, 50) 旋转 45°
const rotateAroundCenter = Matrix3.rotateAround(Math.PI / 4, 50, 50);
// 组合变换:缩放 2 倍,旋转 30°,平移到 (100, 100)
const combined = Matrix3.compose(100, 100, Math.PI / 6, 2, 2);
// 分解矩阵
const parts = combined.decompose();
console.log(parts);
// { tx: 100, ty: 100, rotation: 0.523..., sx: 2, sy: 2 }3.7 本章小结
核心概念
| 概念 | 说明 | 应用 |
|---|---|---|
| 矩阵 | m×n 数字阵列 | 表示线性变换 |
| 单位矩阵 | 对角线为1,其余为0 | 恒等变换 |
| 矩阵乘法 | 行×列求和 | 组合变换 |
| 缩放矩阵 | 对角线为缩放因子 | 改变大小 |
| 旋转矩阵 | cos/sin 组合 | 旋转物体 |
| 平移矩阵 | 最后一列为平移量 | 移动位置 |
| 逆矩阵 | A⁻¹,使 AA⁻¹=I | 撤销变换 |
常用变换矩阵
2D 缩放: 2D 旋转: 2D 平移:
┌ ┐ ┌ ┐ ┌ ┐
│ sx 0 0 │ │ cos(θ) -sin(θ) 0 │ │ 1 0 tx │
│ 0 sy 0 │ │ sin(θ) cos(θ) 0 │ │ 0 1 ty │
│ 0 0 1 │ │ 0 0 1 │ │ 0 0 1 │
└ ┘ └ ┘ └ ┘关键要点
- 矩阵乘法不满足交换律,变换顺序很重要
- 推荐顺序:T × R × S(先缩放,再旋转,最后平移)
- 绕任意点变换:先移到原点,变换,再移回
- 使用齐次坐标可以统一处理平移和线性变换
- 逆矩阵用于撤销变换或坐标转换
下一章预告:在第4章中,我们将深入学习齐次坐标与投影,理解如何将3D场景投影到2D屏幕。
文档版本:v1.0
字数统计:约 11,000 字
