学习unityshader,需要很多坐标系转换,坐标系转换离不开矩阵。尤其是shader,感觉要做到知其然易,知其所以然太难,本文主要记录学习过程中的体会,再好的记忆不如烂笔头。
引言
本文主要分析,在学习shader阶段经常出现的一个函数,源码如下
UnityObjectToWorldNormal源码如下:
// Transforms normal from object to world space
inline float3 UnityObjectToWorldNormal( in float3 norm )
{
#ifdef UNITY_ASSUME_UNIFORM_SCALING
return UnityObjectToWorldDir(norm);
#else
// mul(IT_M, norm) => mul(norm, I_M) => {dot(norm, I_M.col0), dot(norm, I_M.col1), dot(norm, I_M.col2)}
return normalize(mul(norm, (float3x3)unity_WorldToObject));
#endif
}
// Transforms direction from object to world space
inline float3 UnityObjectToWorldDir( in float3 dir )
{
return normalize(mul((float3x3)unity_ObjectToWorld, dir));
}
简要分析如下:
- 如果模型是等比缩放,则把向量(这里主要是法线向量)转化到世界坐标系上的法线向量时,直接调用
UnityObjectToWorldDir(norm);
这个方法里,很简单,unity_ObjectToWorld这个矩阵和物体坐标系下的法线向量相乘,顾名思义,这个矩阵就是物体坐标系转化到世界坐标系的变换矩阵
- 如果模型是非等比缩放,那么这个处理就复杂了,如下:
mul(norm, (float3x3)unity_WorldToObject)
本片主要解释的就是上面这段代码为什么能够解决在模型非等比缩放下法线向量不垂直模型的问题。
为什么等非比缩放会导致法线向量不在垂直模型表面?
先要解决这个问题,需要知道为什么非等比缩放会导致法线向量不在垂直模型表面?直观的看下图,三角形经过非等比缩放(0.6后,法线N已经不垂直模型表面了(图中,T是切线向量,N是法线向量)
为什么?
- 我们知道,一个向量可以表示成两点之间的差,以切线向量T为例,T可以表示成在图中三角形斜边上的两个点P1和P2之差:
- 向量可以表示成一个四元数表达的方式(x,y,z,w)其中w 设置为0.所以我们可以在上面表达式的基础上,左边和右边都右乘以一个ModelView 矩阵:
根据乘法规则,展开得到:
简化得到:
其中和
是三角形经过矩阵变换后的斜边上的点,
由上图右边可见,保持这斜边切线不变。因此可以看出,ModelView矩阵保持了切线性质,而法线方向确不在垂直三角形斜边了。
同样的,法线向量N可以表示成
。
主要问题是,通过转换变换后,点定义的向量不一定保持单位向量,如上图所示。法向量不是定义为两点之间的差,而是定义垂直于一个平面的向量。所以我们再单纯的乘以ModelView来处理非等比缩放来变换法线向量了。
解决思路
- 假设有一个3x3矩阵G,我们来看看如果用这个矩阵来变换一个法线向量。
- 因为法线向量N和切线向量垂直,所以
经过变幻后,N‘T'理应等于0,才是正常的。有上面的分析可以知道,T‘变换后保持了切线的方向正确性,N‘则 不在垂直表面,所以在非等比缩放下N'T' = 0不在成立。
- 现在我们在引入一个3x3矩阵M,切线向量T乘以该矩阵能够正常得到T'。在假设法线向量N能通过和第一步假设的矩阵G相乘得到一个向量N',使得N'T' =0;那么就有如下公式成立
。
- 我们还知道矩阵乘法的转置等于矩阵转置的乘法,则有下列公式成立
- 我们假设
成立,I是单位向量,综上则有
- 通过非等比缩放我们想要的结果是让变换后的法线向量N'满足下面等式
=
。
所以问题就转化成求出G,也就是矩阵G就是在非等比缩放下,把法线向量N转化为垂直模型边的向量N'的矩阵。推导如下:
至此我们得出了在非等比缩放下,变化法线向量的矩阵G就等于矩阵M的逆矩阵的转置。
如果矩阵M是正交矩阵,则我们又有:
。
总结
现在再回头来看看
在这里,
在等比缩放模式下nity_ObjectToWorld是我们的M矩阵,MN则得出正确的变换后的法向量,如下面源码所示
mul((float3x3)unity_ObjectToWorld, norm)
在非等比缩放模式下,unity_WorldToObject则是
而源码则调用的是mul(v,M)这个方法,参数v是向量,M是矩阵。同样一个向量和矩阵相乘,根据矩阵和向量相乘规则,mul(v,M)和mul(M,v)是在内部做了 一个转置处理。所以
mul(norm, (float3x3)unity_WorldToObject);
这里的mul(n,)就相当于了
这里有点绕,不太好理解好像。