Unity3D Shader 坐标空间详解
Unity3D Shader 坐标空间详解。
Unity3D Shader 坐标空间详解
前言
学 Shader 的过程中,你一定会反复看到这些词:
- 对象空间(Object Space)
- 世界空间(World Space)
- 观察空间(View Space)
- 裁剪空间(Clip Space)
很多初学者一开始最容易卡住的地方就是:
这些“空间”到底是什么?为什么同一个顶点要换来换去?
什么是“空间”
所谓“空间”,本质上就是:
同一个点,在不同参考坐标系下的表示方式。
比如一个模型顶点,在模型自身看来,它的位置可能是:
1 | |
但如果把模型摆到场景里,这个点在世界中的位置就变成了另外一个值。
所以:
- 点没变
- 只是参考系变了
Unity Shader 中最常见的 4 个空间
1 对象空间(Object Space)
也叫:
- 本地空间
- 模型空间
它是 以模型自身为原点 的坐标系。
例如一个 Cube 的中心在模型原点,那么它顶部某个点可能是:
1 | |
在顶点着色器里,最开始拿到的 v.vertex,通常就是对象空间坐标。
1 | |
也就是说:
Mesh 里的顶点数据,默认就是对象空间。
2 世界空间(World Space)
世界空间是 整个场景统一使用的坐标系。
不管场景里有多少模型,只要都转到世界空间,就能放在同一个参考系里比较。
比如:
- 模型 A 在
(0,0,0) - 模型 B 在
(10,0,0) - 摄像机在
(0,5,-10)
这些位置,都是世界空间下的描述。
对象空间转世界空间,常见写法:
1 | |
这里的 unity_ObjectToWorld 是 Unity 提供的模型矩阵。
它会把:
- 模型的位移
- 旋转
- 缩放
都计算进去。
3 观察空间(View Space)
观察空间可以理解为:
以摄像机为原点的空间。
把世界中的所有点,都换算成“站在摄像机视角下看”的坐标。
它的意义是:
- 更方便做某些和摄像机相关的计算
- 用于后续投影流程
在日常入门 Shader 中,手动使用观察空间的机会没有世界空间那么多,但它是渲染流程里必经的一步。
4 裁剪空间(Clip Space)
裁剪空间是顶点着色器最终必须输出的空间。
因为 GPU 后续要根据这个空间的数据去做:
- 视锥裁剪
- 透视除法
- 光栅化
在 Unity 中,最常见的写法是:
1 | |
这行代码等价于:
对象空间 → 世界空间 → 观察空间 → 裁剪空间
也就是说,它帮你把整个变换链都做了。
顶点是如何一步步走到屏幕上的
最常见的变换流程如下:
1 | |
你可以把它理解成:
- 顶点先在模型自己的局部坐标里定义
- 再放到场景中
- 再转换到摄像机视角
- 最后投影到屏幕上
所以 Shader 中很多“位置错误”的问题,本质上都是:
拿了 A 空间的数据,却和 B 空间的数据混着算。
为什么一定要区分空间
看一个最经典的错误示例:
1 | |
这段代码的问题是:
pos是 对象空间normal是 世界空间
把两个不同空间的数据直接相加了。
这种写法在某些情况下可能“看起来还能跑”,但本质上是错误的,
一旦模型旋转、缩放,就容易出问题。
正确做法应该是:
- 要么位置和法线都在对象空间算
- 要么位置和法线都先转到世界空间再算
Unity 中常见的空间转换写法
对象空间 → 裁剪空间
1 | |
最常用。
对象空间 → 世界空间
1 | |
常用于:
- 世界空间光照
- 世界空间特效
- 和摄像机位置做比较
对象空间法线 → 世界空间法线
1 | |
常用于:
- 光照计算
- Fresnel
- 描边方向
一个最简单的坐标空间可视化示例
下面写一个简单 Shader:
- 顶点正常显示
- 片段颜色根据 世界空间高度 变化
这样你可以直观看到:
同一个 Shader,在场景不同高度的位置,会显示不同颜色。
1 | |
代码解读
1 | |
这一步把顶点从对象空间转成世界空间。
然后在片段着色器里:
1 | |
根据世界坐标的 y 值控制颜色。
也就是说:
- 放得越高,颜色越亮
- 放得越低,颜色越暗
这就是世界空间参与计算的一个简单例子。
那法线也有空间吗
有的。
法线本质上也是方向向量,所以它同样可能处于:
- 对象空间
- 世界空间
- 切线空间
最常见的是:
- Mesh 自带法线:通常是 对象空间法线
- 光照计算常用:世界空间法线
- 法线贴图常用:切线空间法线
进阶:什么是切线空间(Tangent Space)
如果你后面学习法线贴图,一定会遇到切线空间。
它不是以世界为参考,也不是以模型原点为参考,
而是以模型表面某一点的局部方向为参考:
- Tangent(切线)
- Bitangent(副切线)
- Normal(法线)
这三个方向构成一个局部坐标系。
法线贴图里存的“蓝紫色法线”,大多数就是切线空间法线。
这部分先有个概念就够了,后面讲法线贴图时再深入。
常见错误总结
1 把不同空间的数据直接混算
例如:
- 对象空间顶点 + 世界空间法线
- 世界空间位置 和 观察空间方向做点乘
这是最常见的问题。
2 误以为 UnityObjectToClipPos 得到的是屏幕坐标
它得到的是 裁剪空间坐标,不是最终屏幕像素坐标。
后面 GPU 还要继续做投影和光栅化。
3 不知道什么时候该用对象空间,什么时候该用世界空间
一个简单经验:
- 模型内部形变:常用对象空间
- 和场景、灯光、摄像机相关的计算:常用世界空间
什么时候用哪种空间
| 需求 | 常用空间 |
|---|---|
| 顶点位移、模型内部动画 | 对象空间 |
| 与场景位置相关的效果 | 世界空间 |
| 与摄像机相关的计算 | 观察空间 / 世界空间 |
| 顶点最终输出 | 裁剪空间 |
| 法线贴图 | 切线空间 |
总结
可以先记住一句最核心的话:
空间不是“多出来的数据”,而是“同一份数据所处的参考系”。
在 Unity Shader 中,最重要的几个空间是:
- 对象空间:模型自己的局部坐标
- 世界空间:场景统一坐标
- 观察空间:摄像机视角坐标
- 裁剪空间:顶点着色器最终输出
只要后面写 Shader 时始终注意:
参与同一次计算的数据,必须尽量处于同一个空间。
那么很多 Shader 问题都会一下子清晰很多。