2025-11-30 · game_math
【游戏中的数学】游戏中的数学 (3) - 向量基础
游戏开发数学系列第一篇:向量。介绍向量的定义、加减法、标量乘法、点积与叉积及其在游戏中的实际应用。
前置阅读:建议先阅读向量,本文假设你已理解向量的加减法、点积和叉积。
如果说向量是游戏世界的”积木”,那么矩阵 (Matrix) 就是”胶水”。 矩阵的主要作用是变换 (Transformation)。它能把一个物体从一个位置移动到另一个位置,旋转它,缩放它,甚至把它从 3D 世界投影到 2D 屏幕上。
在 3D 游戏开发中,我们最常用的是 4x4 矩阵。它是一个 4 行 4 列的数字网格。
\[ M = \begin{bmatrix} m_{00} & m_{01} & m_{02} & m_{03} \\ m_{10} & m_{11} & m_{12} & m_{13} \\ m_{20} & m_{21} & m_{22} & m_{23} \\ m_{30} & m_{31} & m_{32} & m_{33} \end{bmatrix} \]
因为我们需要处理 3D 坐标 \((x, y, z)\)。 但是,3x3 矩阵只能表示线性变换(旋转、缩放),无法表示平移 (Translation)。 为了统一处理平移、旋转和缩放,我们引入了齐次坐标 (Homogeneous Coordinates),即增加第四个分量 \(w\)。 一个 3D 点表示为 \((x, y, z, 1)\),一个 3D 向量表示为 \((x, y, z, 0)\)。
齐次坐标的妙处:向量的 \(w=0\) 意味着平移矩阵对方向向量不起作用——把方向”平移”没有意义,矩阵自动帮你处理了这个语义区分。
将物体移动 \((tx, ty, tz)\)。 \[ \begin{bmatrix} 1 & 0 & 0 & tx \\ 0 & 1 & 0 & ty \\ 0 & 0 & 1 & tz \\ 0 & 0 & 0 & 1 \end{bmatrix} \]
将物体在各轴上缩放 \((sx, sy, sz)\)。 \[ \begin{bmatrix} sx & 0 & 0 & 0 \\ 0 & sy & 0 & 0 \\ 0 & 0 & sz & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} \]
绕各轴旋转。例如绕 Z 轴旋转 \(\theta\): \[ \begin{bmatrix} \cos\theta & -\sin\theta & 0 & 0 \\ \sin\theta & \cos\theta & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} \]
在 Unity 中,Matrix4x4 提供了直接构造 TRS
矩阵的 API:
// 构造 TRS 矩阵:先缩放,再旋转,最后平移
Vector3 pos = new Vector3(3f, 0f, 5f);
Quaternion rot = Quaternion.Euler(0f, 90f, 0f);
Vector3 scale = new Vector3(2f, 2f, 2f);
Matrix4x4 trs = Matrix4x4.TRS(pos, rot, scale);
// 将本地空间的点变换到世界空间
Vector3 localPoint = new Vector3(1f, 0f, 0f);
Vector3 worldPoint = trs.MultiplyPoint3x4(localPoint);
// worldPoint ≈ (3, 0, 7) — 先放大2倍得(2,0,0),绕Y轴转90°得(0,0,2),再平移得(3,0,7)
MultiplyPoint3x4vsMultiplyPoint:前者跳过透视除法(适用于仿射变换,更快),后者处理 \(w \neq 1\) 的情况(投影矩阵需要)。变换普通顶点位置时用MultiplyPoint3x4即可。
矩阵最强大的地方在于组合。 如果你想先把物体放大 2 倍,然后绕 Y 轴旋转 90 度,再向前移动 10 米。你不需要分别对每个顶点做三次计算。 你可以将这三个矩阵相乘,得到一个模型矩阵 (Model Matrix),然后只用这个矩阵乘以顶点即可。
矩阵乘法不满足交换律。\(A \times B \neq B \times A\)。 在大多数游戏引擎(如 Unity,使用列向量)中,变换顺序是从右向左读的: \[ M_{final} = M_{translate} \times M_{rotate} \times M_{scale} \] 这意味着:先缩放,再旋转,最后平移。(这是最常用的顺序,也就是 TRS)。
为什么是 TRS 顺序? 如果先平移再缩放,平移量也会被放大;如果先平移再旋转,物体会绕世界原点”公转”而非”自转”。TRS 确保缩放和旋转作用于物体本身,平移最后将它放到世界中。
// 手动组合与 TRS 等价
Matrix4x4 T = Matrix4x4.Translate(pos);
Matrix4x4 R = Matrix4x4.Rotate(rot);
Matrix4x4 S = Matrix4x4.Scale(scale);
Matrix4x4 manual = T * R * S; // 等价于 Matrix4x4.TRS(pos, rot, scale)矩阵的本质是坐标系转换。 在渲染管线中,顶点要经历一系列”空间跳跃”:
\[ P_{clip} = M_{projection} \times M_{view} \times M_{model} \times P_{local} \]
这就是著名的 MVP 变换 (Model-View-Projection)。
就是上面说的 TRS——将顶点从本地空间送到世界空间。Unity 中
transform.localToWorldMatrix 就是它。
View Matrix 将整个世界”搬到”摄像机前面。本质上,它是摄像机 Model Matrix 的逆矩阵(参见第 5 节:逆矩阵)。摄像机在世界空间有自己的位置和朝向,对它取逆就等于”把世界反向移动和旋转”,让摄像机回到原点看向 \(-Z\)。
// Unity 中获取 VP 矩阵
Matrix4x4 view = camera.worldToCameraMatrix;
Matrix4x4 proj = camera.projectionMatrix;
Matrix4x4 vp = proj * view;将 3D 的视锥体 (Frustum) 映射到标准化设备坐标 (NDC)。两种常见类型: - 透视投影 (Perspective):近大远小,用于大多数 3D 游戏。 - 正交投影 (Orthographic):无近大远小,用于 2D 游戏、UI 或建筑可视化。
矩阵 \(M\) 的逆矩阵 \(M^{-1}\) 满足: \[ M \times M^{-1} = M^{-1} \times M = I \]
其中 \(I\) 是单位矩阵 (Identity Matrix)。直觉上,逆矩阵就是”撤销”变换。如果 \(M\) 把物体从 A 移到 B,那么 \(M^{-1}\) 就把它从 B 移回 A。
// 将世界空间的点转换到物体的本地空间
Matrix4x4 worldToLocal = transform.worldToLocalMatrix; // = localToWorldMatrix 的逆
Vector3 localHitPoint = worldToLocal.MultiplyPoint3x4(worldHitPoint);Matrix4x4 inv = myMatrix.inverse; // 通用求逆(计算量较大)性能提示:通用 4x4 矩阵求逆涉及行列式和伴随矩阵,计算开销不小。对于纯旋转矩阵,逆等于转置 (\(R^{-1} = R^T\));对于 TRS 矩阵,可以分别对 T、R、S 求逆再反序组合,比通用算法更快。Unity 内部会做类似优化。
这是一个经典的图形学”坑”。对于位置和切线向量,直接乘以 Model Matrix 没有问题。但法线 (Normal) 不行——当物体有非均匀缩放 (Non-uniform Scale) 时,直接变换法线会导致方向错误。
直觉理解:假设一个球体沿 X 轴压扁变成椭球。表面法线本来垂直于表面,如果和顶点做相同的缩放,法线就不再垂直于压扁后的表面了。
法线应该用 Model Matrix 的逆转置矩阵 (Inverse-Transpose) 来变换: \[ \vec{n}_{world} = (M^{-1})^T \cdot \vec{n}_{local} \]
数学证明(简略):法线 \(\vec{n}\) 垂直于切线 \(\vec{t}\),即 \(\vec{n}^T \cdot \vec{t} = 0\)。变换后我们需要 \(\vec{n'}^T \cdot \vec{t'} = 0\)。切线变换为 \(\vec{t'} = M \cdot \vec{t}\),代入推导可得 \(\vec{n'} = (M^{-1})^T \cdot \vec{n}\)。
// C# — 计算法线变换矩阵
Matrix4x4 model = transform.localToWorldMatrix;
Matrix4x4 normalMatrix = model.inverse.transpose;
Vector3 worldNormal = normalMatrix.MultiplyVector(localNormal).normalized;在 Shader 中,Unity 内置变量
unity_WorldToObject 就是 Model Matrix
的逆。将它转置后乘以法线即可:
// URP Shader 中的法线变换
float3 worldNormal = normalize(
mul((float3x3)UNITY_MATRIX_I_M, objectNormal) // UNITY_MATRIX_I_M = transpose(inverse(M))
);
// 或使用内置函数
float3 worldNormal = TransformObjectToWorldNormal(objectNormal);优化:如果你确定模型只有均匀缩放 (Uniform Scale),可以直接用 Model Matrix 变换法线再归一化——此时逆转置等于原矩阵的标量倍数。只有非均匀缩放时逆转置才真正有区别。
在游戏开发中,表示旋转有三种主要方式。它们各有优劣,选择取决于使用场景。
| 特性 | 欧拉角 (Euler Angles) | 旋转矩阵 (3x3) | 四元数 (Quaternion) |
|---|---|---|---|
| 存储 | 3 个浮点数 | 9 个浮点数 | 4 个浮点数 |
| 直观性 | ✅ 最直观,人类可读 | ❌ 不直观 | ❌ 不直观 |
| 万向节死锁 | ❌ 有 | ✅ 无 | ✅ 无 |
| 插值 | ❌ 不平滑 | ❌ 困难且开销大 | ✅ Slerp 完美插值 |
| 组合旋转 | ❌ 不方便 | ✅ 矩阵乘法 | ✅ 四元数乘法 |
| 变换顶点 | 需先转矩阵 | ✅ 直接矩阵乘法 | 三明治乘法 \(qvq^{-1}\) |
| GPU 友好 | 需先转矩阵 | ✅ Shader 原生支持 | 需先转矩阵 |
实际工程中的选择: - 编辑器/UI:欧拉角——策划和美术人员最容易理解。 - 运行时存储和插值:四元数——紧凑、无万向节死锁、插值平滑。 - Shader 和批量顶点变换:矩阵——GPU 的矩阵乘法硬件加速。
// 三种表示之间的转换 (Unity)
Vector3 euler = new Vector3(0f, 90f, 0f);
Quaternion quat = Quaternion.Euler(euler);
Matrix4x4 mat = Matrix4x4.Rotate(quat);
// 反向转换
Quaternion backToQuat = mat.rotation;
Vector3 backToEuler = quat.eulerAngles;关于四元数的详细原理、Slerp 插值和万向节死锁的深入分析,请看 下一篇:四元数。
< 上一篇: 向量 | 回到目录 | 下一篇: 四元数 >
把当前热点继续串成多页阅读,而不是停在单篇消费。
2025-11-30 · game_math
游戏开发数学系列第一篇:向量。介绍向量的定义、加减法、标量乘法、点积与叉积及其在游戏中的实际应用。
2025-11-30 · game_math
为什么游戏引擎都用四元数来表示旋转?详解欧拉角的万向节死锁问题,以及四元数的定义、运算和 Slerp 插值。
2025-11-30 · game_math
详解游戏引擎中常用的标量数学函数:Lerp, InverseLerp, Remap, Clamp, SmoothStep 等。不仅有公式,更有实际应用场景。
2025-11-30 · game_math
深入浅出地讲解游戏开发中的三角函数:Sin, Cos, Atan2。从单位圆原理到圆周运动、波浪动画和朝向计算的实际应用。
前置阅读:建议先阅读向量,本文假设你已理解向量的加减法、点积和叉积。
如果说向量是游戏世界的”积木”,那么矩阵 (Matrix) 就是”胶水”。 矩阵的主要作用是变换 (Transformation)。它能把一个物体从一个位置移动到另一个位置,旋转它,缩放它,甚至把它从 3D 世界投影到 2D 屏幕上。
在 3D 游戏开发中,我们最常用的是 4x4 矩阵。它是一个 4 行 4 列的数字网格。
\[ M = \begin{bmatrix} m_{00} & m_{01} & m_{02} & m_{03} \\ m_{10} & m_{11} & m_{12} & m_{13} \\ m_{20} & m_{21} & m_{22} & m_{23} \\ m_{30} & m_{31} & m_{32} & m_{33} \end{bmatrix} \]
因为我们需要处理 3D 坐标 \((x, y, z)\)。 但是,3x3 矩阵只能表示线性变换(旋转、缩放),无法表示平移 (Translation)。 为了统一处理平移、旋转和缩放,我们引入了齐次坐标 (Homogeneous Coordinates),即增加第四个分量 \(w\)。 一个 3D 点表示为 \((x, y, z, 1)\),一个 3D 向量表示为 \((x, y, z, 0)\)。
齐次坐标的妙处:向量的 \(w=0\) 意味着平移矩阵对方向向量不起作用——把方向”平移”没有意义,矩阵自动帮你处理了这个语义区分。
将物体移动 \((tx, ty, tz)\)。 \[ \begin{bmatrix} 1 & 0 & 0 & tx \\ 0 & 1 & 0 & ty \\ 0 & 0 & 1 & tz \\ 0 & 0 & 0 & 1 \end{bmatrix} \]
将物体在各轴上缩放 \((sx, sy, sz)\)。 \[ \begin{bmatrix} sx & 0 & 0 & 0 \\ 0 & sy & 0 & 0 \\ 0 & 0 & sz & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} \]
绕各轴旋转。例如绕 Z 轴旋转 \(\theta\): \[ \begin{bmatrix} \cos\theta & -\sin\theta & 0 & 0 \\ \sin\theta & \cos\theta & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} \]
在 Unity 中,Matrix4x4 提供了直接构造 TRS
矩阵的 API:
// 构造 TRS 矩阵:先缩放,再旋转,最后平移
Vector3 pos = new Vector3(3f, 0f, 5f);
Quaternion rot = Quaternion.Euler(0f, 90f, 0f);
Vector3 scale = new Vector3(2f, 2f, 2f);
Matrix4x4 trs = Matrix4x4.TRS(pos, rot, scale);
// 将本地空间的点变换到世界空间
Vector3 localPoint = new Vector3(1f, 0f, 0f);
Vector3 worldPoint = trs.MultiplyPoint3x4(localPoint);
// worldPoint ≈ (3, 0, 7) — 先放大2倍得(2,0,0),绕Y轴转90°得(0,0,2),再平移得(3,0,7)
MultiplyPoint3x4vsMultiplyPoint:前者跳过透视除法(适用于仿射变换,更快),后者处理 \(w \neq 1\) 的情况(投影矩阵需要)。变换普通顶点位置时用MultiplyPoint3x4即可。
矩阵最强大的地方在于组合。 如果你想先把物体放大 2 倍,然后绕 Y 轴旋转 90 度,再向前移动 10 米。你不需要分别对每个顶点做三次计算。 你可以将这三个矩阵相乘,得到一个模型矩阵 (Model Matrix),然后只用这个矩阵乘以顶点即可。
矩阵乘法不满足交换律。\(A \times B \neq B \times A\)。 在大多数游戏引擎(如 Unity,使用列向量)中,变换顺序是从右向左读的: \[ M_{final} = M_{translate} \times M_{rotate} \times M_{scale} \] 这意味着:先缩放,再旋转,最后平移。(这是最常用的顺序,也就是 TRS)。
为什么是 TRS 顺序? 如果先平移再缩放,平移量也会被放大;如果先平移再旋转,物体会绕世界原点”公转”而非”自转”。TRS 确保缩放和旋转作用于物体本身,平移最后将它放到世界中。
// 手动组合与 TRS 等价
Matrix4x4 T = Matrix4x4.Translate(pos);
Matrix4x4 R = Matrix4x4.Rotate(rot);
Matrix4x4 S = Matrix4x4.Scale(scale);
Matrix4x4 manual = T * R * S; // 等价于 Matrix4x4.TRS(pos, rot, scale)矩阵的本质是坐标系转换。 在渲染管线中,顶点要经历一系列”空间跳跃”:
\[ P_{clip} = M_{projection} \times M_{view} \times M_{model} \times P_{local} \]
这就是著名的 MVP 变换 (Model-View-Projection)。
就是上面说的 TRS——将顶点从本地空间送到世界空间。Unity 中
transform.localToWorldMatrix 就是它。
View Matrix 将整个世界”搬到”摄像机前面。本质上,它是摄像机 Model Matrix 的逆矩阵(参见第 5 节:逆矩阵)。摄像机在世界空间有自己的位置和朝向,对它取逆就等于”把世界反向移动和旋转”,让摄像机回到原点看向 \(-Z\)。
// Unity 中获取 VP 矩阵
Matrix4x4 view = camera.worldToCameraMatrix;
Matrix4x4 proj = camera.projectionMatrix;
Matrix4x4 vp = proj * view;将 3D 的视锥体 (Frustum) 映射到标准化设备坐标 (NDC)。两种常见类型: - 透视投影 (Perspective):近大远小,用于大多数 3D 游戏。 - 正交投影 (Orthographic):无近大远小,用于 2D 游戏、UI 或建筑可视化。
矩阵 \(M\) 的逆矩阵 \(M^{-1}\) 满足: \[ M \times M^{-1} = M^{-1} \times M = I \]
其中 \(I\) 是单位矩阵 (Identity Matrix)。直觉上,逆矩阵就是”撤销”变换。如果 \(M\) 把物体从 A 移到 B,那么 \(M^{-1}\) 就把它从 B 移回 A。
// 将世界空间的点转换到物体的本地空间
Matrix4x4 worldToLocal = transform.worldToLocalMatrix; // = localToWorldMatrix 的逆
Vector3 localHitPoint = worldToLocal.MultiplyPoint3x4(worldHitPoint);Matrix4x4 inv = myMatrix.inverse; // 通用求逆(计算量较大)性能提示:通用 4x4 矩阵求逆涉及行列式和伴随矩阵,计算开销不小。对于纯旋转矩阵,逆等于转置 (\(R^{-1} = R^T\));对于 TRS 矩阵,可以分别对 T、R、S 求逆再反序组合,比通用算法更快。Unity 内部会做类似优化。
这是一个经典的图形学”坑”。对于位置和切线向量,直接乘以 Model Matrix 没有问题。但法线 (Normal) 不行——当物体有非均匀缩放 (Non-uniform Scale) 时,直接变换法线会导致方向错误。
直觉理解:假设一个球体沿 X 轴压扁变成椭球。表面法线本来垂直于表面,如果和顶点做相同的缩放,法线就不再垂直于压扁后的表面了。
法线应该用 Model Matrix 的逆转置矩阵 (Inverse-Transpose) 来变换: \[ \vec{n}_{world} = (M^{-1})^T \cdot \vec{n}_{local} \]
数学证明(简略):法线 \(\vec{n}\) 垂直于切线 \(\vec{t}\),即 \(\vec{n}^T \cdot \vec{t} = 0\)。变换后我们需要 \(\vec{n'}^T \cdot \vec{t'} = 0\)。切线变换为 \(\vec{t'} = M \cdot \vec{t}\),代入推导可得 \(\vec{n'} = (M^{-1})^T \cdot \vec{n}\)。
// C# — 计算法线变换矩阵
Matrix4x4 model = transform.localToWorldMatrix;
Matrix4x4 normalMatrix = model.inverse.transpose;
Vector3 worldNormal = normalMatrix.MultiplyVector(localNormal).normalized;在 Shader 中,Unity 内置变量
unity_WorldToObject 就是 Model Matrix
的逆。将它转置后乘以法线即可:
// URP Shader 中的法线变换
float3 worldNormal = normalize(
mul((float3x3)UNITY_MATRIX_I_M, objectNormal) // UNITY_MATRIX_I_M = transpose(inverse(M))
);
// 或使用内置函数
float3 worldNormal = TransformObjectToWorldNormal(objectNormal);优化:如果你确定模型只有均匀缩放 (Uniform Scale),可以直接用 Model Matrix 变换法线再归一化——此时逆转置等于原矩阵的标量倍数。只有非均匀缩放时逆转置才真正有区别。
在游戏开发中,表示旋转有三种主要方式。它们各有优劣,选择取决于使用场景。
| 特性 | 欧拉角 (Euler Angles) | 旋转矩阵 (3x3) | 四元数 (Quaternion) |
|---|---|---|---|
| 存储 | 3 个浮点数 | 9 个浮点数 | 4 个浮点数 |
| 直观性 | ✅ 最直观,人类可读 | ❌ 不直观 | ❌ 不直观 |
| 万向节死锁 | ❌ 有 | ✅ 无 | ✅ 无 |
| 插值 | ❌ 不平滑 | ❌ 困难且开销大 | ✅ Slerp 完美插值 |
| 组合旋转 | ❌ 不方便 | ✅ 矩阵乘法 | ✅ 四元数乘法 |
| 变换顶点 | 需先转矩阵 | ✅ 直接矩阵乘法 | 三明治乘法 \(qvq^{-1}\) |
| GPU 友好 | 需先转矩阵 | ✅ Shader 原生支持 | 需先转矩阵 |
实际工程中的选择: - 编辑器/UI:欧拉角——策划和美术人员最容易理解。 - 运行时存储和插值:四元数——紧凑、无万向节死锁、插值平滑。 - Shader 和批量顶点变换:矩阵——GPU 的矩阵乘法硬件加速。
// 三种表示之间的转换 (Unity)
Vector3 euler = new Vector3(0f, 90f, 0f);
Quaternion quat = Quaternion.Euler(euler);
Matrix4x4 mat = Matrix4x4.Rotate(quat);
// 反向转换
Quaternion backToQuat = mat.rotation;
Vector3 backToEuler = quat.eulerAngles;关于四元数的详细原理、Slerp 插值和万向节死锁的深入分析,请看 下一篇:四元数。
< 上一篇: 向量 | 回到目录 | 下一篇: 四元数 >
把当前热点继续串成多页阅读,而不是停在单篇消费。
2025-11-30 · game_math
游戏开发数学系列第一篇:向量。介绍向量的定义、加减法、标量乘法、点积与叉积及其在游戏中的实际应用。
2025-11-30 · game_math
为什么游戏引擎都用四元数来表示旋转?详解欧拉角的万向节死锁问题,以及四元数的定义、运算和 Slerp 插值。
2025-11-30 · game_math
详解游戏引擎中常用的标量数学函数:Lerp, InverseLerp, Remap, Clamp, SmoothStep 等。不仅有公式,更有实际应用场景。
2025-11-30 · game_math
深入浅出地讲解游戏开发中的三角函数:Sin, Cos, Atan2。从单位圆原理到圆周运动、波浪动画和朝向计算的实际应用。