写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()
这个内联函数,看一下定义发现了有趣的事情:
1 2 3 4 5 6 7 |
#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
的宏,用于生成物体空间到切线空间的转换矩阵。
这个宏其实就两句话:
1 2 3 |
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),但是顶点着色器中要把这仨向量从物体空间转换到世界空间,就用上面第一节的函数。
1 2 3 4 5 6 7 |
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)
用于得到灯光方向,深入看一下这个函数发现:
1 2 3 4 5 6 7 8 9 10 |
#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