在上一篇文章中,我写了一些关于Unity中各个坐标空间及其转换矩阵是如何得到的,说实在的,我是那种“记忆需要依靠外部装置存储”类、如同《攻壳机动队》的电子脑一样的人,每次遇到问题了再去对着笔记慢慢翻找才是我的风格:
破晓:Unity中常用矩阵的推导zhuanlan.zhihu.com
在文章中我漏了一个本该提到的内容,那就是切线空间。
一般在法线贴图中,颜色都是偏蓝的,按照颜色空间和坐标空间的对应来看,z值一般都很大,大部分情况下,都是接近垂直于物体表面的。
如果是不需要法线贴图的情况下还好说,用NORMAL语义可以直接从顶点获得模型空间下的法线,如果要使用法线贴图?那就需要切线空间了。
常识告诉我们,构建一个空间笛卡尔直角坐标系需要三个互相垂直的基向量,而常识又告诉我们,两个向量的叉乘结果垂直于这两个向量构成的平面,故我们可以用两个向量完成切线空间的构建。
一个就是法线(Normal),一个是切线(Tangent),其叉乘结果为副切线(Bitangent)或者副法线(Binormal),这两种叫法我都看到过。以Unity官方的Shader例子举例:
v2f vert (float4 vertex : POSITION, float3 normal : NORMAL, float4 tangent : TANGENT, float2 uv : TEXCOORD0)
{
v2f o;
//...
half3 wNormal = UnityObjectToWorldNormal(normal);
half3 wTangent = UnityObjectToWorldDir(tangent.xyz);
// 计算其方向
half tangentSign = tangent.w * unity_WorldTransformParams.w;
// 用世界空间下的法线、切线做叉乘,得到副法线
half3 wBitangent = cross(wNormal, wTangent) * tangentSign;
// 得到转换矩阵
o.tspace0 = half3(wTangent.x, wBitangent.x, wNormal.x);
o.tspace1 = half3(wTangent.y, wBitangent.y, wNormal.y);
o.tspace2 = half3(wTangent.z, wBitangent.z, wNormal.z);
//...
return o;
}
fixed4 frag (v2f i) : SV_Target
{
// 从法线贴图里采样并解码法线
half3 tnormal = UnpackNormal(tex2D(_BumpMap, i.uv));
// 从切线空间转换到世界空间
half3 worldNormal;
worldNormal.x = dot(i.tspace0, tnormal);
worldNormal.y = dot(i.tspace1, tnormal);
worldNormal.z = dot(i.