如果说矩阵在 Shader 里属于“你迟早要懂”,那在客户端业务里,最常见的高频落点其实是坐标转换。
这也是我自己觉得最值得早点学的一块。因为很多客户端问题表面上像 UI 逻辑,实际上最后都会追到“这个点现在到底在哪个空间”上。
尤其是这些需求:
- 3D 角色头顶挂血条
- 点击屏幕落到地面
- 世界物件映射到 UI 红点
- 小地图图标和场景位置同步
这些本质上都在做一件事:跨坐标空间翻译。
一、先分清几个常见空间
在 Unity 里,最容易混的通常有这几种:
- 世界空间
- 屏幕空间
- 视口空间
- UI 本地空间
如果不先分清语义,后面 API 调得再多也只是碰运气。
1. 世界空间
场景里物体真实存在的位置。
2. 屏幕空间
通常是像素坐标,左下或左上为原点要看具体上下文。
3. 视口空间
归一化后的屏幕比例坐标,一般在 [0,1] 范围。
4. UI 本地空间
相对于某个 RectTransform 的局部二维坐标。
二、为什么“世界点挂 UI”本质上不是一个步骤
很多人第一次做头顶血条,会以为只需要把 3D 位置塞给 UI。
实际上至少经历两段:
- 世界空间 -> 屏幕空间
- 屏幕空间 -> 某个 Canvas 或 RectTransform 的局部空间
这两段背后其实都和相机投影、矩阵变换有关。
三、WorldToScreenPoint 是最常见入口
例如:
1 | Vector3 screenPos = camera.WorldToScreenPoint(worldPos); |
它做的不是简单减法,而是把世界点走过相机的观察和投影过程,最终得到屏幕位置。
所以如果结果不对,问题不一定在 UI,也可能在:
- 相机选错了
- 点在相机后方
- 使用了错误的渲染相机
四、为什么拿到屏幕坐标后还不能直接给 UI
因为 UI 不是天然就和屏幕像素一一对应。
特别是 Canvas 处于不同模式时:
- Screen Space Overlay
- Screen Space Camera
- World Space
处理方式都可能不同。
很多时候你还要进一步做:
1 | RectTransformUtility.ScreenPointToLocalPointInRectangle(...) |
把屏幕点换成某个 UI 根节点的局部点。
一个常见写法大概会像这样:
1 | Vector3 screenPos = camera.WorldToScreenPoint(target.position + offset); |
这段代码看起来只是几行转换,但它其实已经把“世界 -> 屏幕 -> UI 本地”这条链走完了。
五、点击屏幕打到世界,也是反向变换
另一个高频需求是:
玩家点屏幕某个位置,我想知道场景里对应哪里。
这通常不是“直接得到 3D 点”,而是:
先从屏幕点发射一条射线,再和场景中的平面、碰撞体相交。
比如:
1 | Ray ray = camera.ScreenPointToRay(screenPos); |
这背后其实就是把二维屏幕输入重新解释回三维世界方向。
六、小地图、本地雷达、战斗提示都在做类似的空间投影
很多业务系统表面看起来不一样,但底层都类似:
- 小地图把世界平面位置压到一张 2D 贴图坐标里
- 战斗指示器把世界范围映射到屏幕角标
- 任务箭头把 3D 方向转成 UI 旋转
本质都是:
找对中间空间,然后做稳定的坐标变换。
七、为什么 UI 跟随经常“抖”或“飘”
常见原因通常有这些:
- 更新时机不对,角色已经动了,UI 还没跟上
- 使用了错误的相机
- 没处理物体在相机后方的情况
- 直接拿屏幕坐标硬塞局部坐标
- Canvas 缩放规则没有考虑进去
所以这种问题不要只盯数值,先把空间链路画出来更有效。
八、一个很实用的排查顺序
如果世界点映射 UI 有问题,我一般会按这个顺序查:
- 世界点本身是不是对的
WorldToScreenPoint输出是不是合理- 目标 Canvas 模式是什么
- UI 根节点的本地坐标系是不是理解对了
- 最后是不是又叠加了额外偏移
这比一上来疯狂调 offset 高效很多。
九、矩阵知识在这里真正帮到你的地方
你不一定需要手推 VP 矩阵,但你要知道:
- 世界到屏幕不是魔法,是投影变换
- 屏幕到 UI 不是直接赋值,是另一个空间映射
- 相机决定了中间变换规则
只要这个认知有了,很多坐标转换问题都会从“玄学”变成“可拆解流程”。
十、总结
UI、屏幕、世界坐标的来回转换,是 Unity 客户端里最实用的一类矩阵应用。
它看起来不像 Shader 那么“图形学”,但本质上用的是同一套空间变换思想。
下一篇我会继续讲更偏工程排查的一面:InverseTransformPoint、MultiplyPoint、Gizmos 这些工具怎么组合起来定位矩阵问题。