跳到主要内容
transform-math · 代码
查看代码目录
Graphics Learning Lab Transform Math / Source

Observe · Change · Recompute · Explain

每个公式都要对应一个可观察的约束

Read-only · exact teaching model

代码页按 quaternion、matrix、homogeneous、skinning、Bezier 和 IK 顺序阅读,并与实验画面保持一致。

这一课怎么学

  1. 观察
  2. 预测
  3. 改一个量
  4. 看证据
  5. 再读代码
Quaternion

旋转先保持约束

单位四元数改变方向,不改变向量长度。

// SECTION:quaternion
function quaternionFromAxisAngle(axis, angle) {
  const half = angle * 0.5;
  const s = Math.sin(half);
  return axis === 'y'
    ? { x: 0, y: s, z: 0, w: Math.cos(half) }
    : { x: s, y: 0, z: 0, w: Math.cos(half) };
}

Affine matrix

把变换串起来

平移、缩放与旋转进入同一矩阵。

// SECTION:matrix
function composeAffine2D(translation, scale, angle) {
  const c = Math.cos(angle), s = Math.sin(angle);
  return [scale.x * c, -scale.y * s, translation.x,
          scale.x * s, scale.y * c, translation.y,
          0, 0, 1];
}

Homogeneous divide

w 决定投影

点和方向对齐不同的齐次坐标语义。

// SECTION:homogeneous
function transformPoint(matrix, point) {
  const w = matrix[6] * point.x + matrix[7] * point.y + matrix[8];
  return {
    x: (matrix[0] * point.x + matrix[1] * point.y + matrix[2]) / w,
    y: (matrix[3] * point.x + matrix[4] * point.y + matrix[5]) / w,
  };
}

Skinning weights

先变换再加权

权重和与 bind pose 决定蒙皮是否稳定。

// SECTION:skinning
function skin(point, boneMatrices, weights) {
  return weights.reduce((result, weight, index) => {
    return result + weight * transformPoint(boneMatrices[index], point);
  }, vec2(0, 0));
}

Bezier curve

控制点定义路径

曲线采样是连续路径上的参数化观察。

// SECTION:bezier
function cubicBezier(p0, p1, p2, p3, t) {
  const u = 1 - t;
  return u ** 3 * p0 + 3 * u ** 2 * t * p1
    + 3 * u * t ** 2 * p2 + t ** 3 * p3;
}

FABRIK / IK

约束下逼近目标

可达域、迭代次数和误差必须同时可见。

// SECTION:ik
function fabrik(root, target, lengths, iterations) {
  let joints = seedChain(root, target, lengths);
  for (let pass = 0; pass < iterations; pass += 1) {
    joints = backwardReach(joints, target, lengths);
    joints = forwardReach(joints, root, lengths);
  }
  return { joints, error: distance(joints.at(-1), target) };
}