首页 > 代码库 > [Unity Shader] 坐标变换与法线变换及Unity5新增加的内置函数

[Unity Shader] 坐标变换与法线变换及Unity5新增加的内置函数

  学习第六章Unity内置函数时,由于之前使用mul矩阵乘法时的顺序与书中不一致,导致使用内置函数时出现光照效果不一样,因此引出以下两个问题:

  1 什么时候使用3x3矩阵,什么时候使用4x4矩阵?

  2 法线变换矩阵与坐标变换矩阵不相同

  解答1:

  4.9.1节书中讲述了何时使用3x3和4x4矩阵。因为4x4矩阵是比3x3矩阵多了平移变换,因此对空间坐标进行变换时,通常使用4x4矩阵。而对于切线和法线这两种空间矢量,不存在平移的情况,因此仅使用3x3矩阵即可(是否可以偷懒使用4x4矩阵?是可以的)。

  解答2:

  而对于法线变换,因为对坐标缩放时,有可能不是等比缩放,这会导致法向量使用坐标变换矩阵后不再垂直于表面。有下述推导过程求得法线变换矩阵:

  假设某平面点的模型空间切线为T,法线为N。通过空间转换M,法线转换G后,切线为T‘,法线为N‘。那么有:

  TT·N = 0 ----- (1),

  (T‘)T·N‘ = 0 -----(2),

  T‘ = M·T -----(3),

  N‘ = G·N -----(4)。

  由式(2)(3)(4)有:

(M·T)T·(G·N) = 0  ==>  TT·MT·G·N = 0 ==>  (矩阵结合律)  (TT·MT·G)·N = 0  -----(5)

  结合式(1)和(5),得到:T= TT·MT·G,由此得到 MT·G = I,则:

 G = (MT)-1 =   (M-1)T

  即法线的变换矩阵,是空间变换矩阵的转置的逆。

  

======================Unity Shader中计算注意事项=======================

 

1  在Unity Shader中,将坐标从模型空间转换到世界空间,使用如下方式:

v2f o;o.worldPos = mul(_Object2World, v.vertex).xyz; 

  UnityShader中的转换矩阵为行矩阵,模型空间坐标v.vertex作为列矩阵,与转换矩阵右乘,得到世界坐标。

  注意,这里不能使用 o.worldPos = UnityObjectToWorldDir(v.vertex); 在UnityCG.cginc中查看UnityObjectToWorldDir的定义:

// Transforms direction from object to world spaceinline float3 UnityObjectToWorldDir( in float3 dir ){    return normalize(mul((float3x3)_Object2World, dir));}

  这里使用的是3x3矩阵,使得坐标变换丢失了平移参数。

 

2  将法线从模型空间转换到世界空间,使用如下方式:

o.worldNormal = mul(v.normal, (float3x3)_World2Object);

  在这里,矩阵_World2Object是矩阵_Object2World的逆矩阵。由于是法向量,因此取3x3矩阵进行计算。

  需要注意的是,这里的mul实现的矩阵乘法,当向量在左侧时,此向量相当于行向量,其中数值是与矩阵的列元素进行乘法与加法。相当于如下写法:

o.worldNormal = mul(transpose((float3x3)_World2Object), v.normal);

  即法线的变换是空间变换_Object2World的逆矩阵的转置transpose(_World2Object)。

  这里Unity Shader封装了一个函数来转换法线到世界坐标UnityObjectToWorldNormal(),替代上面的矩阵乘法写法,不容易出错。

o.worldNormal = UnityObjectToWorldNormal(v.normal); 

 

// Transforms normal from object to world spaceinline float3 UnityObjectToWorldNormal( in float3 norm ){    // Multiply by transposed inverse matrix, actually using transpose() generates badly optimized code    return normalize(_World2Object[0].xyz * norm.x + _World2Object[1].xyz * norm.y + _World2Object[2].xyz * norm.z);}

 

================书中完整示例===============

技术分享
Shader "Unity Shaders Book/Chapter 6/Blinn-Phong Build-In Function"{    Properties    {        _Diffuse("Diffuse", Color) = (1.0, 1.0, 1.0, 1.0)        _Specular("Specular", Color) = (1.0, 1.0, 1.0, 1.0)        _Gloss("Gloss", Range(8.0, 256.0)) = 20.0    }    SubShader    {        Pass        {            Tags { "LightMode"="ForwardBase" }            CGPROGRAM            #pragma vertex vert            #pragma fragment frag            #include "Lighting.cginc"            fixed4 _Diffuse;            fixed4 _Specular;            float _Gloss;            struct a2v            {                float4 vertex : POSITION;                float3 normal : NORMAL;            };            struct v2f            {                float4 pos : SV_POSITION;                fixed3 worldNormal : TEXCOORD0;                float3 worldPos : TEXCOORD1;            };            v2f vert (a2v v)            {                v2f o;                o.pos = mul(UNITY_MATRIX_MVP, v.vertex);                o.worldNormal = UnityObjectToWorldNormal(v.normal); // 效果与下一行相同,因为是矢量变换,用3x3变换矩阵即可。//                o.worldNormal = mul(v.normal, (float3x3)_World2Object);//                o.worldPos = UnityObjectToWorldDir(v.vertex); // normalize(mul((float3x3)_Object2World, dir));效果与下一行不同//                o.worldPos = mul(v.vertex, transpose(_Object2World)).xyz; // 将坐标左乘,将模型空间坐标转换到世界空间,与下式相等                o.worldPos = mul(_Object2World, v.vertex).xyz; // 将坐标右乘,将模型空间坐标转换到世界空间                return o;            }            fixed4 frag(v2f i) : SV_Target            {                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;                fixed3 worldNormal = normalize(i.worldNormal);                fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));                fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));                fixed3 halfDir = normalize(viewDir + worldLightDir);                fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(halfDir, worldNormal)) , _Gloss);                fixed3 color = ambient + diffuse + specular;                return fixed4(color, 1.0);            }            ENDCG        }    }    Fallback "Specular"}
使用Unity5 build-in的函数实现Blinn-Phong光照模型。

 

技术分享

 

[Unity Shader] 坐标变换与法线变换及Unity5新增加的内置函数