Skip to content

第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 = A

3.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  │
└          ┘              └                    ┘   └          ┘

关键要点

  1. 矩阵乘法不满足交换律,变换顺序很重要
  2. 推荐顺序:T × R × S(先缩放,再旋转,最后平移)
  3. 绕任意点变换:先移到原点,变换,再移回
  4. 使用齐次坐标可以统一处理平移和线性变换
  5. 逆矩阵用于撤销变换或坐标转换

下一章预告:在第4章中,我们将深入学习齐次坐标与投影,理解如何将3D场景投影到2D屏幕。


文档版本:v1.0
字数统计:约 11,000 字

如有转载或 CV 的请标注本站原文地址