Skip to main content

第 0 周:数学基础回顾 —— 三角函数与单位圆

📅 学习计划

本周目标: 快速复习图形学中最核心的三角函数知识,理解单位圆、向量方向与旋转的数学本质。这部分是后续向量和矩阵运算的基石。

时间安排: 1 天(在开始第 1 周之前阅读)


一、图形学中必须掌握的三角函数公式

1. 基础定义(必须熟记)

单位圆定义(核心概念!)

什么是单位圆?

  • 单位圆 = 半径为 1 的圆,圆心在原点 (0, 0)
  • 叫"单位"是因为半径是 1(单位长度)

单位圆上的点

         y

| ● P(x, y)
| /|
| / |
| / | r = 1
| /θ |
-----+-----+----→ x
| (0,0)
|

关键理解

  • 圆上任意一点 P 的坐标就是 (cos(θ), sin(θ))
  • cos(θ) = 点的 x 坐标
  • sin(θ) = 点的 y 坐标
  • 因为半径 r = 1,所以:
    sin(θ) = y / r = y / 1 = y
    cos(θ) = x / r = x / 1 = x

为什么单位圆如此重要?

  1. 最直观的理解方式:看到角度 θ,就能立即知道 sin 和 cos 的值
  2. 图形学的基础:所有旋转、方向向量都基于单位圆
  3. 归一化向量:当你归一化一个向量 (dx, dy) 时,得到的 (dirX, dirY) 就是单位圆上的点!

具体例子

角度 0°   → 点 (1, 0)   → cos(0°) = 1,  sin(0°) = 0
角度 90° → 点 (0, 1) → cos(90°) = 0, sin(90°) = 1
角度 45° → 点 (0.707, 0.707) → cos(45°) = sin(45°) = √2/2 ≈ 0.707
角度 180° → 点 (-1, 0) → cos(180°) = -1, sin(180°) = 0

2. 向量与角度转换(核心!)

从向量到角度

// 已知向量 (dx, dy),求角度
const angle = Math.atan2(dy, dx); // 返回 [-π, π] 弧度
const angleDegrees = angle * 180 / Math.PI;

从角度到单位向量

// 已知角度,求单位方向向量
const angle = Math.PI / 4; // 45度
const dirX = Math.cos(angle);
const dirY = Math.sin(angle);
// 结果:(dirX, dirY) 是长度为 1 的单位向量

从角度到任意长度向量

// 已知角度和长度,求向量
const angle = Math.PI / 3; // 60度
const length = 5;
const dx = Math.cos(angle) * length;
const dy = Math.sin(angle) * length;

3. 实际案例:轨迹线文本标签偏移(完整解析)

场景:在轨迹线的最后一个点附近放置文本标签,让标签沿着轨迹方向偏移,避免与点重合。

代码实现(来自 LineRender.ts):

// 计算文字偏移量,避免与最后一个点重合
const offsetX = 0.7; // 沿轨迹方向的偏移距离
const offsetY = 0.5; // 垂直方向的额外偏移
const offsetZ = 0.05;

// 获取轨迹的最后两个点
const lastPoint = lineData[lineData.length - 1];
const prevPoint = lineData[lineData.length - 2];

// 步骤1:计算方向向量
const dx = lastPoint[0] - prevPoint[0]; // X 方向的变化量
const dy = lastPoint[1] - prevPoint[1]; // Y 方向的变化量

// 步骤2:计算向量长度(距离)
const length = Math.sqrt(dx * dx + dy * dy);

if (length > 0.01) { // 避免除零错误
// 步骤3:归一化方向向量(核心!)
const dirX = dx / length; // 单位向量的 x 分量 = cos(angle)
const dirY = dy / length; // 单位向量的 y 分量 = sin(angle)

// 步骤4:沿轨迹方向偏移
finalOffsetX = dirX * offsetX; // 沿轨迹方向在 X 轴的偏移
finalOffsetY = dirY * offsetX + offsetY; // 沿轨迹方向在 Y 轴的偏移 + 垂直偏移
}

// 步骤5:应用偏移
const textPosition = {
x: lastPoint[0] + finalOffsetX,
y: lastPoint[1] + finalOffsetY,
z: lastPoint[2] + offsetZ
};

为什么不用 sin/cos?

// ❌ 方法1:用 sin/cos(慢)
const angle = Math.atan2(dy, dx);
finalOffsetX = Math.cos(angle) * offsetX;
finalOffsetY = Math.sin(angle) * offsetX + offsetY;

// ✅ 方法2:直接归一化(快,当前代码)
const dirX = dx / length; // = cos(angle),但不需要算 angle
const dirY = dy / length; // = sin(angle),但不需要算 angle
finalOffsetX = dirX * offsetX;
finalOffsetY = dirY * offsetX + offsetY;

性能对比

  • atan2() 比除法慢 3-5 倍
  • sin()/cos() 比乘法慢 10-20 倍
  • 直接用归一化快 15-25 倍

4. 旋转公式(2D 旋转,必须掌握!)

点绕原点旋转

// 点 (x, y) 绕原点逆时针旋转 θ 角度
const x_new = x * Math.cos(θ) - y * Math.sin(θ);
const y_new = x * Math.sin(θ) + y * Math.cos(θ);

矩阵形式(Three.js 中常用)

// 旋转矩阵
const cos = Math.cos(angle);
const sin = Math.sin(angle);
const rotationMatrix = new THREE.Matrix3().set(
cos, -sin, 0,
sin, cos, 0,
0, 0, 1
);

5. 极坐标与直角坐标转换

直角坐标 → 极坐标

const x = 3, y = 4;
const r = Math.sqrt(x * x + y * y); // 距离
const θ = Math.atan2(y, x); // 角度

极坐标 → 直角坐标

const r = 5, θ = Math.PI / 4;  // 45度
const x = r * Math.cos(θ);
const y = r * Math.sin(θ);

6. 常用恒等式(了解即可)

// 平方和恒等式
Math.sin(θ) ** 2 + Math.cos(θ) ** 2 === 1

// 和角公式(旋转时可能用到)
sin(a + b) = sin(a) * cos(b) + cos(a) * sin(b)
cos(a + b) = cos(a) * cos(b) - sin(a) * sin(b)

二、图形学中的实际应用场景

1. 旋转物体

// 物体绕 Z 轴旋转
const angle = time * 0.01; // 随时间旋转
mesh.rotation.z = angle;
// 内部实现:使用 sin/cos 计算旋转矩阵

2. 圆形/弧形路径

// 生成圆形路径上的点
const radius = 10;
const centerX = 0, centerY = 0;
for (let angle = 0; angle < Math.PI * 2; angle += 0.1) {
const x = centerX + radius * Math.cos(angle);
const y = centerY + radius * Math.sin(angle);
// 添加点 (x, y)
}

3. 相机控制(Orbit Controls)

// 球坐标系 → 直角坐标
const radius = 10;
const azimuth = Math.PI / 4; // 水平角度
const elevation = Math.PI / 6; // 垂直角度

camera.position.x = radius * Math.cos(elevation) * Math.cos(azimuth);
camera.position.y = radius * Math.sin(elevation);
camera.position.z = radius * Math.cos(elevation) * Math.sin(azimuth);

4. 纹理坐标计算

// 将 2D 坐标映射到圆形纹理
const u = 0.5 + 0.5 * Math.cos(angle);
const v = 0.5 + 0.5 * Math.sin(angle);

三、性能优化技巧

1. 避免重复计算

// ❌ 不好:重复计算
for (let i = 0; i < 1000; i++) {
const x = Math.cos(angle) * radius;
const y = Math.sin(angle) * radius;
}

// ✅ 好:预先计算
const cosAngle = Math.cos(angle);
const sinAngle = Math.sin(angle);
for (let i = 0; i < 1000; i++) {
const x = cosAngle * radius;
const y = sinAngle * radius;
}

2. 使用向量而非角度(如你的代码)

// ❌ 慢:角度 → 向量
const angle = Math.atan2(dy, dx);
const offsetX = Math.cos(angle) * distance;
const offsetY = Math.sin(angle) * distance;

// ✅ 快:直接归一化向量
const length = Math.sqrt(dx * dx + dy * dy);
const offsetX = (dx / length) * distance;
const offsetY = (dy / length) * distance;

四、总结:图形学必掌握的核心公式

优先级1(必须熟练)

  1. 向量归一化dirX = dx / length, dirY = dy / length
  2. 角度转向量x = cos(θ) * r, y = sin(θ) * r
  3. 向量转角度θ = atan2(dy, dx)
  4. 2D 旋转x' = x*cos - y*sin, y' = x*sin + y*cos
  5. 距离计算d = sqrt(dx² + dy²)

优先级2(常用)

  1. 极坐标 ↔ 直角坐标转换
  2. 角度插值
  3. 球坐标系(3D 相机)

优先级3(了解)

  1. 和角公式、倍角公式
  2. 反三角函数(asin, acos

五、实践建议

  1. 理解单位圆:画一个单位圆,理解 sin/cos 的几何意义
  2. 多用向量:能用向量解决的问题,不要用角度(性能更好)
  3. 记住特殊角:30°、45°、60°、90° 的 sin/cos 值