本篇为你们总结了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