Transformation in ShaderLab | ShaderLab中的空间变换

写Shader经常会碰到一堆得空间变换,而又经常想不起来变换矩阵的名字,因此整理一下。

1 物体空间(Object Space), 顶点着色器接收的语义项比如POSITION, NORMAL, TANGENT都是在这个空间下
2 世界空间(World Space)
3 观察空间(View Space),以摄像机为原点
4 裁剪空间(Clip Space),裁剪空间,最常用的UNITY_MATRIX_MVP就是转换到这个空间
5 切线空间(Tangent Space),用了法线贴图就会涉及这个空间

关系到空间的转换,所以有5C2=10类。 再加上物体和世界空间中都有可能访问两种固定的类型,比如灯光,视线。

1. 物体空间-世界空间(1-2)

以前叫_Object2World,新版本改叫unity_WorldToObject了,其逆变换为unity_ObjectToWorld将顶点和矢量从物体空间转换到世界空间
当然法线不能这么干,因为法线变换需要相应空间变换的逆转置矩阵。但是"UnityCG.cginc"提供了一个UnityObjectToWorldNormal()的内联函数,专门变换物体空间到世界空间的法线。
另外矢量的变换需要截3×3的变换矩阵,一般要写mul((float3x3)unity_ObjectToWorld, dir),当然"UnityCG.cginc"也有一个UnityObjectToWorldDir()的内联函数,与其相反的UnityWorldToObjectDir()的内联函数。

2. 物体空间-观察空间(1-3)

UNITY_MATRIX_MV这个矩阵把物体空间转到观察空间,其转置矩阵UNITY_MATRIX_T_MV做相反的工作,将观察空间转到物体空间。
"UnityCG.cginc"提供了一个内联函数UnityObjectToViewPos(),其实做的和上面那个矩阵一样。

3. 物体空间-裁剪空间(1-4)

UNITY_MATRIX_MVP这个了,几乎每次顶点着色器都会用到。
"UnityCG.cginc"也提供了UnityObjectToClipPos()这个内联函数,看一下定义发现了有趣的事情:

#ifdef UNITY_USE_PREMULTIPLIED_MATRICES
    return mul(UNITY_MATRIX_MVP, float4(pos, 1.0));
#else
    // More efficient than computing M*VP matrix product
    return mul(UNITY_MATRIX_VP, mul(unity_ObjectToWorld, float4(pos, 1.0)));
#endif

居然更有效率的方式是先把顶点放到世界空间,然后用UNITY_MATRIX_VP来转换。

4. 物体空间-切线空间(1-5)

"UnityCG.cginc"中定义了一个TANGENT_SPACE_ROTATION的宏,用于生成物体空间到切线空间的转换矩阵。
这个宏其实就两句话:

float3 binormal = cross( v.normal, v.tangent.xyz ) * v.tangent.w;  
float3x3 rotation = float3x3( v.tangent.xyz, binormal, v.normal );  

binormal是副法线,rotation就是转换矩阵了。在切线空间算光照要把物体空间的光和视线变换到切线空间,就要用这个矩阵啦。

5. 世界空间-观察空间(2-3)

使用UNITY_MATRIX_V矩阵
"UnityCG.cginc"中定义了UnityWorldToViewPos(),其实和上面矩阵一样。

6. 世界空间-裁剪空间(2-4)

UNITY_MATRIX_VP,以及"UnityCG.cginc"中的UnityWorldToClipPos()

7. 世界空间-切线空间(2-5)

要在世界空间下算切线空间法线贴图的光照就需要把切线空间转换到世界空间。
但因为每个定点切线空间不一样,还得再片段着色器中算光照,所以得在v2f中把世界空间到切线空间的矩阵传过去。。。。。
当然变换矩阵还是(tangent, binormal, normal),但是顶点着色器中要把这仨向量从物体空间转换到世界空间,就用上面第一节的函数。

worldNormal = UnityObjectToWorldNormal( v.normal );
worldTangent = UnityObjectToWorldDir( v.tangent.xyz );
worldBinormal = cross( worldNormal, worldTangent) *v.tangent.w;
o.TtoW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);
o.TtoW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);
o.TtoW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);

上面把转换矩阵按行存的,我们需要一个float3x3的矩阵,但可以用float4存,所以顺便把世界空间下顶点坐标worldPos也存进去了。

8. 观察空间-裁剪空间(3-4)

UNITY_MATRIX_P以及"UnityCG.cginc"中的UnityViewToClipPos()

9. 观察空间-切线空间(3-5)

好像没怎么见过,略

10. 裁剪空间-切线空间(4-5

好像没怎么见过,略

11. 物体空间下的一些常量

ObjSpaceLightDir(float4 v),v是物体的坐标。这个内联函数用了_WorldSpaceLightPos0这个常量,是世界空间下灯的位置。
ObjSpaceViewDir(float4 v),v是物体的坐标。这个内联函数用了_WorldSpaceCameraPos这个常量,是世界空间下摄像机的位置。

12. 世界空间下一些常量

灯光和摄像机都放在这里,_WorldSpaceLightPos0_WorldSpaceCameraPos
"UnityCG.cginc"中定义了内联函数UnityWorldSpaceLightDir(float3 worldPos)用于得到灯光方向,深入看一下这个函数发现:

#ifndef USING_LIGHT_MULTI_COMPILE
        return _WorldSpaceLightPos0.xyz - worldPos * _WorldSpaceLightPos0.w;
#else
#ifndef USING_DIRECTIONAL_LIGHT
        return _WorldSpaceLightPos0.xyz - worldPos;
#else
        return _WorldSpaceLightPos0.xyz;
#endif
    #endif

_WorldSpaceLightPos0.w这个东西是灯光属性,平行光0其他1,其实和后面那个一样。大意是平行光我们就用它的方向就好啦,其他的话我们要算一下相对方向,之后可能还得采样一下atten衰减。上面ObjSpaceLightDir那个东西也有相似的处理。

UnityWorldSpaceViewDir(float3 worldPos)用来算世界空间下的实现,其实就是用了_WorldSpaceCameraPos

发表评论

电子邮件地址不会被公开。 必填项已用*标注