本篇爲你們總結了Unity3D中的法線轉換與切線空間知識。在Shader編程中常常會使用一些矩陣變換函數接口,其實它就是把固定流水線中的矩陣變換轉移到了可編程流水線或者說GPU中,先看下面的函數語句:html
// Transform the normal from object space to world space o.worldNormal = mul(v.normal, (float3x3)_World2Object);
在這裏先給你們介紹一下,爲什麼使用此函數,模型的頂點法線是位於模型空間下的,所以咱們首先須要把法線轉換到世界空間中。計算方式可使用頂點變換矩陣的逆轉置矩陣對法線進行相同的變換,所以咱們首先獲得模型空間到世界空間的變換矩陣的逆矩陣_World2Object,而後經過調換它在mul函數中的位置,獲得和轉置矩陣相同的矩陣乘法。因爲法線是一個三維矢量,所以咱們只須要截取_World2Object的前三行前三列便可。下面給你們展現變換Shader代碼。編程
因爲光源方向、視角方向大多都是在世界空間下定義的,因此問題就是如何把它們從世界空間變換到切線空間下。咱們能夠先獲得世界空間中切線空間的三個座標軸的方向表示,而後把它們按列擺放,就能夠獲得從切線空間到世界空間的變換矩陣,那麼再對這個矩陣求逆就能夠獲得從世界空間到切線空間的變換:ide
/// /// Note that the code below can handle both uniform and non-uniform scales /// // Construct a matrix that transforms a point/vector from tangent space to world space fixed3 worldNormal = UnityObjectToWorldNormal(v.normal); fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz); fixed3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w; float4x4 tangentToWorld = float4x4(worldTangent.x, worldBinormal.x, worldNormal.x, 0.0, worldTangent.y, worldBinormal.y, worldNormal.y, 0.0, worldTangent.z, worldBinormal.z, worldNormal.z, 0.0, 0.0, 0.0, 0.0, 1.0); // The matrix that transforms from world space to tangent space is inverse of tangentToWorld float3x3 worldToTangent = inverse(tangentToWorld); // Transform the light and view dir from world space to tangent space o.lightDir = mul(worldToTangent, WorldSpaceLightDir(v.vertex)); o.viewDir = mul(worldToTangent, WorldSpaceViewDir(v.vertex));
因爲Unity不支持Cg的inverse函數,因此還須要本身定義一個inverse函數,這種作法明顯比較麻煩。實際上,在Unity 4.x版本及其以前的版本中,內置的shader一直是原來書上那種不嚴謹的轉換方法,這是由於Unity 5以前,若是咱們對一個模型A進行了非統一縮放,Unity內部會從新在內存中建立一個新的模型B,模型B的大小和縮放後的A是同樣的,可是它的縮放係數是統一縮放。換句話說,在Unity 5之前,實際上咱們在Shader中根本不須要考慮模型的非統一縮放問題,由於在Shader階段非統一縮放根本就不存在了。但從Unity 5之後,咱們就須要考慮非統一縮放的問題了。函數
Shader "Unity Shaders/Normal Map In Tangent Space" { Properties { _Color ("Color Tint", Color) = (1, 1, 1, 1) _MainTex ("Main Tex", 2D) = "white" {} _BumpMap ("Normal Map", 2D) = "bump" {} _BumpScale ("Bump Scale", Float) = 1.0 _Specular ("Specular", Color) = (1, 1, 1, 1) _Gloss ("Gloss", Range(8.0, 256)) = 20 } SubShader { Pass { Tags { "LightMode"="ForwardBase" } CGPROGRAM #pragma vertex vert #pragma fragment frag #include "Lighting.cginc" fixed4 _Color; sampler2D _MainTex; float4 \_MainTex\_ST; sampler2D _BumpMap; float4 \_BumpMap\_ST; float _BumpScale; fixed4 _Specular; float _Gloss; struct a2v { float4 vertex : POSITION; float3 normal : NORMAL; float4 tangent : TANGENT; float4 texcoord : TEXCOORD0; }; struct v2f { float4 pos : SV_POSITION; float4 uv : TEXCOORD0; float3 lightDir: TEXCOORD1; float3 viewDir : TEXCOORD2; }; // Unity doesn't support the 'inverse' function in native shader // so we write one by our own // Note: this function is just a demonstration, not too confident on the math or the speed // Reference: http://answers.unity3d.com/questions/218333/shader-inversefloat4x4-function.html float4x4 inverse(float4x4 input) { #define minor(a,b,c) determinant(float3x3(input.a, input.b, input.c)) float4x4 cofactors = float4x4( minor(\_22\_23\_24, \_32\_33\_34, \_42\_43_44), -minor(\_21\_23\_24, \_31\_33\_34, \_41\_43_44), minor(\_21\_22\_24, \_31\_32\_34, \_41\_42_44), -minor(\_21\_22\_23, \_31\_32\_33, \_41\_42_43), -minor(\_12\_13\_14, \_32\_33\_34, \_42\_43_44), minor(\_11\_13\_14, \_31\_33\_34, \_41\_43_44), -minor(\_11\_12\_14, \_31\_32\_34, \_41\_42_44), minor(\_11\_12\_13, \_31\_32\_33, \_41\_42_43), minor(\_12\_13\_14, \_22\_23\_24, \_42\_43_44), -minor(\_11\_13\_14, \_21\_23\_24, \_41\_43_44), minor(\_11\_12\_14, \_21\_22\_24, \_41\_42_44), -minor(\_11\_12\_13, \_21\_22\_23, \_41\_42_43), -minor(\_12\_13\_14, \_22\_23\_24, \_32\_33_34), minor(\_11\_13\_14, \_21\_23\_24, \_31\_33_34), -minor(\_11\_12\_14, \_21\_22\_24, \_31\_32_34), minor(\_11\_12\_13, \_21\_22\_23, \_31\_32_33) ); #undef minor return transpose(cofactors) / determinant(input); } v2f vert(a2v v) { v2f o; o.pos = mul(UNITY\_MATRIX\_MVP, v.vertex); o.uv.xy = v.texcoord.xy * \_MainTex\_ST.xy \_MainTex\_ST.zw; o.uv.zw = v.texcoord.xy * \_BumpMap\_ST.xy \_BumpMap\_ST.zw; /// /// Note that the code below can handle both uniform and non-uniform scales /// // Construct a matrix that transforms a point/vector from tangent space to world space fixed3 worldNormal = UnityObjectToWorldNormal(v.normal); fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz); fixed3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w; /* float4x4 tangentToWorld = float4x4(worldTangent.x, worldBinormal.x, worldNormal.x, 0.0, worldTangent.y, worldBinormal.y, worldNormal.y, 0.0, worldTangent.z, worldBinormal.z, worldNormal.z, 0.0, 0.0, 0.0, 0.0, 1.0); // The matrix that transforms from world space to tangent space is inverse of tangentToWorld float3x3 worldToTangent = inverse(tangentToWorld); */ //wToT = the inverse of tToW = the transpose of tToW as long as tToW is an orthogonal matrix. float3x3 worldToTangent = float3x3(worldTangent, worldBinormal, worldNormal); // Transform the light and view dir from world space to tangent space o.lightDir = mul(worldToTangent, WorldSpaceLightDir(v.vertex)); o.viewDir = mul(worldToTangent, WorldSpaceViewDir(v.vertex)); /// /// Note that the code below can only handle uniform scales, not including non-uniform scales /// // Compute the binormal // float3 binormal = cross( normalize(v.normal), normalize(v.tangent.xyz) ) * v.tangent.w; // // Construct a matrix which transform vectors from object space to tangent space // float3x3 rotation = float3x3(v.tangent.xyz, binormal, v.normal); // Or just use the built-in macro // TANGENT\_SPACE\_ROTATION; // // // Transform the light direction from object space to tangent space // o.lightDir = mul(rotation, normalize(ObjSpaceLightDir(v.vertex))).xyz; // // Transform the view direction from object space to tangent space // o.viewDir = mul(rotation, normalize(ObjSpaceViewDir(v.vertex))).xyz; return o; } fixed4 frag(v2f i) : SV_Target { fixed3 tangentLightDir = normalize(i.lightDir); fixed3 tangentViewDir = normalize(i.viewDir); // Get the texel in the normal map fixed4 packedNormal = tex2D(_BumpMap, i.uv.zw); fixed3 tangentNormal; // If the texture is not marked as "Normal map" // tangentNormal.xy = (packedNormal.xy * 2 - 1) * _BumpScale; // tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy))); // Or mark the texture as "Normal map", and use the built-in funciton tangentNormal = UnpackNormal(packedNormal); tangentNormal.xy *= _BumpScale; tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy))); fixed3 albedo = tex2D(\_MainTex, i.uv).rgb * \_Color.rgb; fixed3 ambient = UNITY\_LIGHTMODEL\_AMBIENT.xyz * albedo; fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(tangentNormal, tangentLightDir)); fixed3 halfDir = normalize(tangentLightDir tangentViewDir); fixed3 specular = \_LightColor0.rgb * \_Specular.rgb * pow(max(0, dot(tangentNormal, halfDir)), _Gloss); return fixed4(ambient diffuse specular, 1.0); } ENDCG } } FallBack "Specular" }
再給你們介紹一種方法:咱們想要把法線空切線空間變換到世界空間,即若是咱們想要把向量從空間A變換到空間B,則須要獲得空間A的三個基向量在空間B下的表示,並把這三個基向量依次按列擺放,再與須要進行變換的列向量相乘便可。所以,咱們須要獲得切線空間的三個基向量在世界空間下的表示,並把它們按列擺放。切線空間下的三個基向量分別是TBN(切線、副切線和法線),咱們已知這三個向量在模型空間下的表示,即模型自帶的TBN的值。而它們在世界空間下的表示就能夠經過把它們從模型空間變換到世界空間便可。切線T的變換直接用UnityObjectToWorldDir(v.tangent.xyz)變換便可,而法線N的變換就須要考慮非統一縮放的影響,若是咱們仍然使用UnityObjectToWorldDir(v. normal.xyz)來直接變換法線就會出現變換後的法線再也不於三角面垂直的狀況,因此由此構建出來的基向量空間也會是有問題的,致使變換後的法線方向也是錯誤的,你能夠參考下圖中的第一行的狀況。正確的作法是,在變換法線N時使用UnityObjectToWorldNormal(v.normal)來進行變換,即便用逆轉置矩陣去將模型法線N從模型空間變換到世界空間,由此咱們就能夠獲得正確的變換,參考下圖第二行的狀況。ui
你們可自行查看UnityCG.cginc文件,它裏面封裝了不少關於矩陣變換的接口函數以下所示:this