Transform 实验室

矩阵变换:线性代数在计算机图形学中的应用

2024-10-22 wangjun 20 min read
T()

矩阵乘法即变换的复合

在线性代数中,矩阵乘法不仅是数字的运算,更是 线性变换的复合。对向量 v 先做变换 A 再做变换 B,等价于做变换 BA

B(A·v) = (BA)·v

这就是为什么 CSS transform 中 transform: rotate(45deg) scale(2)transform: scale(2) rotate(45deg) 效果不同 -- 矩阵乘法不满足交换律。

2D 变换矩阵

使用齐次坐标,2D 变换用 3×3 矩阵表示:

平移 Translation

┌ 1  0  tx ┐
│ 0  1  ty │
└ 0  0  1  ┘

缩放 Scale

┌ sx  0   0 ┐
│ 0   sy  0 │
└ 0   0   1 ┘

旋转 Rotation

┌ cos(θ)  -sin(θ)  0 ┐
│ sin(θ)   cos(θ)  0 │
└ 0        0       1 ┘

剪切 Shear

┌ 1   shx  0 ┐
│ shy  1   0 │
└ 0    0   1 ┘

3D 变换与齐次坐标

3D 变换使用 4×4 矩阵。齐次坐标 (x, y, z, w)w 分量使得透视投影可以用矩阵乘法表达:

┌ 1  0  0  0 ┐
│ 0  1  0  0 │
│ 0  0  1  0 │
└ 0  0  -1/d 1 ┘  // 透视投影矩阵

变换后 w' = 1 - z/d,除以 w' 实现透视除法,这就是透视投影的数学本质。

Canvas 实践:变换引擎

class Transform2D {
  constructor() {

    this.m = [1, 0, 0, 1, 0, 0];
  }

  translate(tx, ty) {
    this.m[4] += this.m[0] * tx + this.m[2] * ty;
    this.m[5] += this.m[1] * tx + this.m[3] * ty;
    return this;
  }

  rotate(angle) {
    const cos = Math.cos(angle);
    const sin = Math.sin(angle);
    const [a, b, c, d] = this.m;
    this.m[0] = a * cos + c * sin;
    this.m[1] = b * cos + d * sin;
    this.m[2] = c * cos - a * sin;
    this.m[3] = d * cos - b * sin;
    return this;
  }

  scale(sx, sy) {
    this.m[0] *= sx;
    this.m[1] *= sx;
    this.m[2] *= sy;
    this.m[3] *= sy;
    return this;
  }

  apply(x, y) {
    return {
      x: this.m[0] * x + this.m[2] * y + this.m[4],
      y: this.m[1] * x + this.m[3] * y + this.m[5]
    };
  }
}

矩阵分解

任意 2D 仿射矩阵 [a, b, c, d, e, f] 可以分解为:

M = T(e, f) × R(θ) × Scale(sx, sy) × Shear(shx)

这个分解在动画插值中极为重要 -- 直接对矩阵元素做线性插值会导致变形,而对分解后的参数做插值则平滑自然。

线性代数不是抽象的符号游戏,它是图形学的语言。每一个旋转、缩放、投影,都是矩阵的一次乘法。