从 3x3 到 4x4:齐次坐标如何把平移装进矩阵

前两篇把线性变换讲到了旋转、缩放、错切,但还差一个最常用的动作没统一进来:平移。

这也是我觉得很多 Unity 初学者第一次看 Matrix4x4 会突然断层的原因。前面 2x2 还在讲“轴怎么变”,一到 4x4 就像突然多出一堆和项目代码很近、但又说不清为什么存在的数据。

这件事如果只站在线性代数的严格定义上看,会比较尴尬。

因为线性变换要求原点不动,而平移天生会把原点搬走。

可图形学又必须把旋转、缩放、平移放进一套统一流程,否则渲染管线和引擎实现会变得很碎。

解决方案就是齐次坐标。

一、为什么二维里要从 2x2 升到 3x3

二维点 (x, y),如果只用 2x2 矩阵,平移做不到。

因为 2x2 乘法本质上只能做线性组合,没法凭空加一个常量位移项。

比如你想把点变成:

$$
(x’, y’) = (x + t_x, y + t_y)
$$

这件事不是纯粹依赖 xy 的线性组合,它额外需要一个常量“1”。

所以图形学里把二维点扩展成:

$$
\begin{bmatrix}
x \
y \
1
\end{bmatrix}
$$

于是平移就能写进 3x3 矩阵:

$$
\begin{bmatrix}
1 & 0 & t_x \
0 & 1 & t_y \
0 & 0 & 1
\end{bmatrix}
$$

这样一乘,位移项就自然加进去了。

二、这个“多出来的 1”到底有什么用

它的作用不是为了数学形式好看,而是给平移留了一个入口。

你可以把它理解成:

前两维还是原来的空间坐标,最后那一维负责把“仿射变换”里的常量项也纳入矩阵乘法。

有了这个常量维度之后,旋转、缩放、平移都能统一用矩阵连乘来描述。

这对引擎非常重要,因为:

  • 数据结构统一
  • 乘法链统一
  • GPU 顶点处理统一

三、三维世界为什么会变成 4x4

同理,三维空间里的点 (x, y, z) 在图形学中通常扩展成:

$$
\begin{bmatrix}
x \
y \
z \
1
\end{bmatrix}
$$

于是三维里的平移、旋转、缩放,都能用 4x4 矩阵来表示。

这就是 Unity 里 Matrix4x4 的来源。

所以不要把 4x4 看成“为了复杂而复杂”,它的本质只是:

三维空间 + 一维齐次坐标 = 统一表达所有常见空间变换。

四、点和向量在齐次坐标里要区别对待

这个地方特别关键。

点通常写成:

$$
(x, y, z, 1)
$$

向量通常写成:

$$
(x, y, z, 0)
$$

为什么?

因为向量表示的是方向和偏移,它不应该受平移影响。

举个很实在的 Unity 对应:

  • 一个顶点位置,应该跟着平移一起动
  • 一个朝向向量,不应该因为物体整体平移就改变方向

这也正是 MultiplyPointMultiplyVector 行为不同的根本原因。

五、Unity 里怎么理解 4x4 矩阵的每一部分

如果把 Matrix4x4 当成 16 个数字,阅读成本很高。

更工程化的理解方式是:

  • 前三列:局部坐标系三个基向量在目标空间中的样子
  • 最后一列:原点平移到目标空间后的坐标

所以一个 localToWorldMatrix 可以同时回答两件事:

  1. 这个物体的轴朝哪儿了
  2. 这个物体被搬到哪儿了

旋转、缩放影响前三列。

平移主要影响最后一列。

六、为什么 TRS 能被合成一个矩阵

TRS 就是 Translation、Rotation、Scale。

单看它们像是三种不同操作,但因为都能写成 4x4 矩阵,所以可以连乘合成一个总矩阵。

也就是说:

$$
M = T \cdot R \cdot S
$$

或者在具体实现里是别的顺序,但本质上都是:

先准备好几段变换,再合成一个总规则。

这个总规则可以一次性把局部点变换到目标空间。

七、为什么引擎这么喜欢“统一成矩阵”

因为统一之后,很多流程都简单了:

  • 父子节点变换可以直接矩阵相乘
  • CPU 侧和 GPU 侧的顶点变换可以共享同一套表达
  • 相机、物体、投影都能接在一个矩阵链里

从实现角度看,这是一种极高性价比的抽象。

八、一个很容易踩的坑:把点和方向都拿去做同一种乘法

很多自定义工具、程序化生成代码里,会出现这种 bug:

本来要转换一个方向,却用了点变换;或者本来要转换一个位置,却当成向量去乘。

后果通常是:

  • 方向多了一段莫名其妙的偏移
  • 位置少了平移项
  • 射线起点和方向都不对

所以只要你遇到“坐标差一点但总不对”的问题,第一件事就该回头检查:

我现在处理的,到底是点还是向量?

九、在 Unity 里最直接对应的几个 API

这篇学完之后,再看这些接口会非常顺:

  • transform.localToWorldMatrix
  • transform.worldToLocalMatrix
  • Matrix4x4.TRS
  • matrix.MultiplyPoint
  • matrix.MultiplyVector
  • matrix.MultiplyPoint3x4

这些都不是孤立 API,而是在共享同一套齐次坐标和仿射变换框架。

如果你想把“点吃平移、向量不吃平移”这件事一次看明白,可以直接跑下面这段:

1
2
3
4
5
6
7
8
9
10
Matrix4x4 matrix = Matrix4x4.TRS(
new Vector3(10f, 0f, 0f),
Quaternion.identity,
Vector3.one);

Vector3 point = new Vector3(1f, 2f, 3f);
Vector3 dir = new Vector3(1f, 2f, 3f);

Debug.Log(matrix.MultiplyPoint(point));
Debug.Log(matrix.MultiplyVector(dir));

第一行结果会整体往 x 正方向多出一段平移,第二行不会。项目里只要把这两者混了,很多“怎么总差一点”的 bug 就会出现。

十、总结

齐次坐标不是“数学家为了炫技加的一维”。

它解决的是图形学里一个非常现实的问题:怎么把平移和其他变换统一成一套矩阵乘法流程。

理解了这一点,后面再看 Unity 的 Matrix4x4,你会更容易看出每一块数据的含义。

下一篇开始,就正式落回到 Unity 的 Transform,把局部空间和世界空间的关系讲透。