Unity 法线之旅
最近遇到一个需求,处理法线的流程大致如下:
两张切线空间的法线,在切线空间里做一个混合,再转换到世界空间做一些操作。
而且这个流程会拆成两半,一半放在 surface shader 里,另一半丢进 LightingModel。
起初,我把整个流程写在 LightingModel,传进 s.Normal
做计算,很快发现一个问题:unity 的 surface shader 处理会自动把 s.Normal
转成世界空间的。
这就意味着我们必须拥有自己的 output struct field,例如新写一个 s.NormalT
,然后把 binormal, tangent 这些都传到 LightingModel 里……等等,为什么不直接在 surface shader 里计算世界空间的混合结果呢?
也就是说:
想清楚就可以开工了,首先在 input struct 里加入下面的内容:
struct Input
{
half4 tangentT; // 注意不能直接用 tangent,会报错
half3 normalW; // 也不能直接用 worldNormal,因为 o.Normal 被写了
...
}
然后是 output struct:
struct SurfaceOutput {
half3 BlendedNormalW;
...
}
vert:
o.tangentT.xyz = UnityObjectToWorldDir(v.tangent.xyz);
o.tangentT.w = v.tangent.w; // specifies tangent direction
o.normalW = UnityObjectToWorldNormal(v.tangent.xyz);
surf:
half3 noiseNormal = tex2D(_NoiseTex, uv);
noiseNormal = 2.h * noiseNormal - 1.h;
half3 binormal = cross(IN.tangentT.xyz, IN.normalW) * IN.tangentT.w;
half3x3 rotation = half3x3(IN.tangentT.xyz, binormal, IN.normalW);
o.BlendedNormalW = mul(rotation, noiseNormal + o.Normal);
最后把 output struct 传进 LightingModel 就可以了。
总结
在 unity surface shader 里:
位置 | 名称 | 空间 |
---|---|---|
vert | v.normal | object |
surf | IN.normal | tangent |
LightingModel | s.Normal | world |
TODO
这篇文还没写完,几个还需要细讲的地方记录一下:
UNITY_INITIALIZE_OUTPUT
TANGENT_SPACE_ROTATION
- worldNormal 的写入问题
INTERNAL_DATA
- 怎么辨别法线所在的空间(基础)
- 怎么灵活转换各种空间的法线
5.15 修订
勘误和补记
之前构造 rotation
matrix 时,使用了切线空间的法线和世界空间的切线,铸成大错。
所以我们需要自己做一个世界空间法线。worldNormal
这个名字被污染了,用不了,重定义一个 normalW
来转换。
另外,关于 binormal 的详细解释请参见这个 post。$$B = N \times T$$,但实际操作的时候好像把 tangent 放在前面也没事(待进一步验证)。tangent.w
这个分量则是为了调整左右镜像模型的切线方向设置的。
辨别
- tangent space normal 和普通的法线图长得一样
- object space normal 和世界法线差不多,呈现出十字形,但相对于物体固定
- world space normal 相对于世界坐标旋转
转换
⇒ | tangent | object | world |
---|---|---|---|
tangent | / | tTw ⸰ wTo | rotation |
object | oTw ⸰ wTt | / | UnityObjectToWorldNormal |
world | rotation^(-1) | UnityWorldToObjectNormal |
/ |