Unity 已经帮我们把大多数坐标变换都封装在 Transform 里了,但只要开始写工具、程序化生成、特殊动画或者自定义网格,Matrix4x4.TRS 很快就会出现。
我自己在项目里最常碰到它的地方,其实不是“我要秀矩阵”,而是普通 Transform 节点一多,点位生成和调试就开始变得很脏的时候。
这时候最容易犯的错是:
知道有 T、R、S,却不知道顺序为什么不能随便换。
这篇就专门讲这个问题。
一、TRS 不是三个标签,而是一条变换流水线
T 是 Translation,R 是 Rotation,S 是 Scale。
它们都可以写成矩阵,然后连乘成一个总矩阵。
关键在于:
矩阵乘法有顺序。
先缩放再旋转,和先旋转再缩放,结果不一样。
所以 TRS 不能被理解成“把三个属性装在一起”,而是“这三个动作按某个顺序串起来”。
二、为什么一般会先 Scale,再 Rotate,再 Translate
最常见的直觉是:
- 先在物体自己的局部空间里做缩放
- 再把这个局部空间旋转到目标方向
- 最后整体搬到世界某个位置
这个顺序最符合大多数内容生产和运行时逻辑。
因为缩放通常希望沿局部轴发生,旋转通常希望围绕局部原点发生,平移最后只是把整个结果放到目标位置。
如果你把平移放前面,很多操作就会变成“绕世界原点打转”。
三、一个特别典型的错误:本来想原地旋转,结果绕远点转圈
这类 bug 常见于:
- 自己拼矩阵
- 自定义 gizmo
- 程序化摆放物件
- 特效跟随系统
症状通常是:
物体看起来不是原地变换,而是绕某个奇怪的点公转。
本质原因就是:
你把平移和旋转的乘法顺序写反了。
四、Matrix4x4.TRS 帮你封装了常规顺序,但你要知道它在帮你做什么
Unity 提供:
1 | Matrix4x4 matrix = Matrix4x4.TRS(pos, rot, scale); |
它很方便,但不能只停留在“会调”。
你要知道这个矩阵最终表达的是:
一个局部空间中的点,先经过缩放、再经过旋转、最后平移到目标位置。
这样后续你再用:
1 | Vector3 worldPos = matrix.MultiplyPoint(localPos); |
就知道自己在做什么,而不是把它当黑盒。
五、项目里什么时候会手动用 Matrix4x4.TRS
我自己觉得至少有这几类场景很常见:
1. 程序化摆放物件
比如一条道路、一个棋盘、一个阵列、一个地块拼接系统,需要根据规则批量生成位置和方向。
如果你先在局部空间定义模板点位,再乘一个 TRS,总体会比每次手写 position/rotation 偏移更稳定。
2. 自定义编辑器工具
比如场景里批量预览刷点、绘制本地包围盒、编辑器下做布局辅助,这时你常常有“局部模板 + 世界实例化”的需求。
3. 顶点或网格处理
某些运行时网格变形、顶点缓存、批量合并逻辑里,直接乘矩阵比绕很多 Transform 节点要直接得多。
4. 特效和挂点偏移
如果一个技能轨迹、命中特效、提示框位置不是简单地跟某个节点完全重合,而是相对于局部轴有一段可配置偏移,用 TRS 表达会更清楚。
六、MultiplyPoint 和 MultiplyVector 在这里再强调一次
用 TRS 矩阵做变换时,最常见的两种数据是:
- 一个局部位置点
- 一个局部方向向量
它们不能混用。
1 | Vector3 p = matrix.MultiplyPoint(localPoint); |
前者会吃进平移,后者不会。
如果你把方向拿去做点变换,经常会得到一个“方向看起来对,数值却带偏移”的诡异结果。
七、非等比缩放下,TRS 会把问题放大
如果 scale = (2, 1, 0.5) 这种非等比缩放参与进来,很多直觉会开始失灵。
尤其是当你后续还把这个结果继续喂给别的系统,比如:
- 法线计算
- 物理近似
- 子节点朝向
- 自定义包围盒
这时就要非常谨慎。
因为 TRS 不是错了,而是它忠实地保留了非等比缩放对坐标系的影响。
八、一个很实用的写法:先在局部定义,再统一乘矩阵
例如你要在角色前方摆 5 个预警点,可以这样写:
1 | var trs = Matrix4x4.TRS(transform.position, transform.rotation, Vector3.one); |
这个思路的优点是:
逻辑先在局部空间表达,很干净;真正落到场景时,再统一映射到世界。
对于技能、阵型、道路、棋盘格,这类写法都很稳。
如果换成二合或棋盘类项目,通常可以进一步写成“本地模板 + 根节点 TRS”的形式:
1 | Vector3[] localSlots = |
这种写法的价值,不只是能算出点位,而是后面整个棋盘搬家、旋转甚至切换朝向时,槽位规则都不用重写。
九、什么时候不必强行手写矩阵
也别反过来走极端。
如果只是普通挂点、普通跟随、普通父子关系,直接用 Transform API 更清晰。
矩阵适合的是:
- 你需要显式控制变换链
- 你要批量处理大量点位
- 你不想创建太多临时节点
- 你在写底层工具或渲染相关逻辑
否则一上来就全改成矩阵代码,只会让维护成本变高。
十、总结
Matrix4x4.TRS 最重要的价值,不只是“把位移旋转缩放拼起来”,而是让你可以显式、稳定地控制一条空间变换流水线。
只要你真正理解顺序问题,很多“为什么位置差一点”“为什么绕错点旋转”的 bug 都会好查很多。
下一篇我会把话题切到 Shader,讲顶点从模型空间一路走到裁剪空间时,MVP 矩阵链到底在干什么。