紋理貼圖html
法線貼圖ide
存儲法線的一張貼圖,歸一化的法線的 xyz 的值被映射成爲對應的 RGB 值。歸一化的法線值爲[-1,1],RGB的每個份量爲無符號的8位組成,範圍[0,255]。即法線的份量由[-1,1]映射成[0,255]。法線貼圖通常呈藍色,由於大多數朝向 (0,0,1)的法線被映射成爲了 (0,0,255)。函數
轉換關係:spa
法線轉RGB:code
R = (x+1)/2*255;orm
G = (y+1)/2*255;htm
B = (z+1)/2*255;blog
RGB轉法線:get
x = (R/255*2)-1;it
y = (G/255*2)-1;
z = (B/255*2)-1;
切空間是在某一點全部的切向量組成的線性空間。也就是說,在模型每一個頂點中,都存在這樣的一個切空間座標系,以模型頂點爲中心,再加上TBN3個軸(Tangent,Binormal,Normal),N是頂點的法線方向,T、B兩個向量是頂點切平面的2個向量,通常T的方向是紋理座標u的方向,B的方向經過TN叉乘計算獲得。而法線貼圖就是記錄該空間下頂點的法線方向,它不是固定(0,0,1)而是在切空中間中的擾動值。
首先,咱們須要計算出切空間到模型空間的變換矩陣。切空間由3個向量定義,Tangent,Binormal,Normal;咱們在模型頂點中已知Tangent和Normal的值,那麼Binormal能夠經過前2個向量的叉乘來取得。
1 struct vertexInput{ 2 float4 vertex:POSITION; 3 float3 normal:NORMAL; 4 float4 texcoord:TEXCOORD0; 5 float4 tangent:TANGENT; 6 };
在頂點函數中計算三個軸:
1 o.normal = normalize(v.normal); 2 o.tangent = normalize(v.tangent-v.normal*v.tangent*v.normal); 3 o.binormal = cross(v.normal, v.tangent)*v.tangent.w;
其中:切線向量o.tangent經過Gram-Schmidt修正,使它與法線垂直;副切線向量o.binormal經過乘以v.tangent.w計算它的長度。
有了這3個切向量,咱們就能夠定義變換矩陣:
1 float3x3 local2ObjectTranspose=float3x3( 2 i.tangent, 3 i.binormal, 4 i.normal 5 );
在該矩陣中,沒有平移矩陣,只有旋轉矩陣,旋轉矩陣又是正交矩陣,TBN兩兩垂直,正交矩陣的逆矩陣就是其轉置矩陣。
這個矩陣是模型空間到切空間的轉換矩陣。
在切空間計算:
源代碼:
1 //在切空間計算光源方向 2 Shader "JQM/NoamalMap_1" 3 { 4 Properties 5 { 6 _MainTex ("Texture", 2D) = "white" {} 7 _BumpMap ("Normal Texture", 2D) = "bump" {} 8 _BumpDepth("_Bump Depth",Range(-2,2.0)) = 1 9 } 10 11 SubShader 12 { 13 14 Pass 15 { 16 Tags { "LightMode"="ForwardBase" } 17 18 CGPROGRAM 19 #pragma vertex vert 20 #pragma fragment frag 21 22 #include "UnityCG.cginc" 23 24 //使用自定義變量 25 sampler2D _MainTex; 26 float4 _MainTex_ST; 27 sampler2D _BumpMap; 28 float4 _BumpMap_ST; 29 uniform float _BumpDepth; 30 31 //使用Unity定義的變量 32 uniform float4 _LightColor0; 33 34 //輸入結構體 35 struct vertexInput{ 36 float4 vertex:POSITION; 37 float3 normal:NORMAL; 38 float4 texcoord:TEXCOORD0; 39 float4 tangent:TANGENT; 40 }; 41 42 //輸出結構體 43 struct vertexOutput{ 44 float4 pos:SV_POSITION; 45 float4 tex:TEXCOORD0; 46 float4 posWorld:TEXCOORD1; 47 float3 normal:TEXCOORD2; 48 float3 tangent:TEXCOORD3; 49 float3 binormal:TEXCOORD4; 50 }; 51 52 vertexOutput vert (vertexInput v) 53 { 54 vertexOutput o; 55 56 o.normal = normalize(v.normal); 57 o.tangent = normalize(v.tangent-v.normal*v.tangent*v.normal); 58 o.binormal = cross(v.normal, v.tangent)*v.tangent.w; 59 60 o.posWorld = mul(_Object2World, v.vertex); 61 o.pos = mul(UNITY_MATRIX_MVP, v.vertex); 62 o.tex = v.texcoord; 63 64 return o; 65 } 66 67 fixed4 frag (vertexOutput i) : COLOR 68 { 69 float3 viewDirection = normalize(_WorldSpaceCameraPos.xyz- i.posWorld.xyz); 70 float3 lightDirection; 71 float atten; 72 73 if(_WorldSpaceLightPos0.w==0.0)//平行光 74 { 75 atten = 1.0; 76 lightDirection = normalize(_WorldSpaceLightPos0.xyz); 77 } 78 else 79 { 80 float3 fragmentToLightSource = _WorldSpaceLightPos0.xyz -i.posWorld.xyz; 81 float distance = length(fragmentToLightSource); 82 atten = 1.0/distance; 83 lightDirection = normalize(fragmentToLightSource); 84 } 85 86 //Texture Map 87 float4 tex = tex2D(_MainTex,i.tex.xy*_MainTex_ST.xy+_MainTex_ST.zw); 88 float4 texN = tex2D(_BumpMap,i.tex.xy*_BumpMap_ST.xy+_BumpMap_ST.zw); 89 90 //UnpackNormal [0,1] 轉換成[-1,1] 91 float3 localCoords = float3(2.0*texN.ag-float2(1.0,1.0),0.0); 92 localCoords.z = _BumpDepth; 93 94 float3x3 Tangent2ObjectTranspose = float3x3( 95 i.tangent, 96 i.binormal, 97 i.normal 98 ); 99 100 //在切空間計算光照 101 lightDirection = normalize(mul(_World2Object,lightDirection)); 102 lightDirection = normalize(mul(Tangent2ObjectTranspose,lightDirection));//轉置矩陣=逆矩陣:TBN兩兩垂直 103 104 float3 diffuseReflection = saturate( dot(localCoords,lightDirection)); 105 106 return float4(diffuseReflection*tex.xyz,1.0); 107 } 108 ENDCG 109 } 110 } 111 }
在世界空間計算:
1 //在世界空間計算光源方向 2 Shader "JQM/NoamalMap_2" 3 { 4 Properties 5 { 6 _MainTex ("Texture", 2D) = "white" {} 7 _BumpMap ("Normal Texture", 2D) = "bump" {} 8 _BumpDepth("_Bump Depth",Range(-2,2.0)) = 1 9 } 10 11 SubShader 12 { 13 14 Pass 15 { 16 Tags { "LightMode"="ForwardBase" } 17 18 CGPROGRAM 19 #pragma vertex vert 20 #pragma fragment frag 21 22 #include "UnityCG.cginc" 23 24 //使用自定義變量 25 sampler2D _MainTex; 26 float4 _MainTex_ST; 27 sampler2D _BumpMap; 28 float4 _BumpMap_ST; 29 uniform float _BumpDepth; 30 31 //使用Unity定義的變量 32 uniform float4 _LightColor0; 33 34 //輸入結構體 35 struct vertexInput{ 36 float4 vertex:POSITION; 37 float3 normal:NORMAL; 38 float4 texcoord:TEXCOORD0; 39 float4 tangent:TANGENT; 40 }; 41 42 //輸出結構體 43 struct vertexOutput{ 44 float4 pos:SV_POSITION; 45 float4 tex:TEXCOORD0; 46 float4 posWorld:TEXCOORD1; 47 float3 normalWorld:TEXCOORD2; 48 float3 tangentWorld:TEXCOORD3; 49 float3 binormalWorld:TEXCOORD4; 50 }; 51 52 vertexOutput vert (vertexInput v) 53 { 54 vertexOutput o; 55 56 o.normalWorld = normalize(mul(float4(v.normal,0.0),_World2Object).xyz);//法線向量轉世界座標,不一樣於頂點,他須要乘以模型轉換矩陣的逆的轉置; 57 o.tangentWorld = normalize(mul(_Object2World,v.tangent).xyz); 58 o.tangentWorld = o.tangentWorld-o.normalWorld*o.tangentWorld*o.normalWorld;//修正不垂直,使他們垂直 59 o.binormalWorld = cross(o.normalWorld, o.tangentWorld); 60 61 o.posWorld = mul(_Object2World, v.vertex); 62 o.pos = mul(UNITY_MATRIX_MVP, v.vertex); 63 o.tex = v.texcoord; 64 65 return o; 66 } 67 68 fixed4 frag (vertexOutput i) : COLOR 69 { 70 float3 viewDirection = normalize(_WorldSpaceCameraPos.xyz- i.posWorld.xyz); 71 float3 lightDirection; 72 float atten; 73 74 if(_WorldSpaceLightPos0.w==0.0)//平行光 75 { 76 atten = 1.0; 77 lightDirection = normalize(_WorldSpaceLightPos0.xyz); 78 } 79 else 80 { 81 float3 fragmentToLightSource = _WorldSpaceLightPos0.xyz -i.posWorld.xyz; 82 float distance = length(fragmentToLightSource); 83 atten = 1.0/distance; 84 lightDirection = normalize(fragmentToLightSource); 85 } 86 87 //Texture Map 88 float4 tex = tex2D(_MainTex,i.tex.xy*_MainTex_ST.xy+_MainTex_ST.zw); 89 float4 texN = tex2D(_BumpMap,i.tex.xy*_BumpMap_ST.xy+_BumpMap_ST.zw); 90 91 //UnpackNormal [0,1] 轉換成[-1,1] 92 float3 localCoords = float3(2.0*texN.ag-float2(1.0,1.0),0.0); 93 localCoords.z = _BumpDepth; 94 95 float3x3 Tangent2WorldTranspose = float3x3( 96 i.tangentWorld, 97 i.binormalWorld, 98 i.normalWorld 99 ); 100 101 //將法線轉到世界空間 102 localCoords = normalize(mul(localCoords,Tangent2WorldTranspose)); 103 104 float3 diffuseReflection = saturate( dot(localCoords,lightDirection)); 105 106 return float4(diffuseReflection*tex.xyz,1.0); 107 } 108 ENDCG 109 } 110 } 111 }
Unity 3D 的UnityCG.cginc文件定義的切空間旋轉矩陣:
1 #define TANGENT_SPACE_ROTATION \ 2 float3 binormal = cross( normalize(v.normal), normalize(v.tangent.xyz) ) * v.tangent.w; \ 3 float3x3 rotation = float3x3( v.tangent.xyz, binormal, v.normal )
將攝像機(視點)轉換到模型空間:
// Computes object space view direction inline float3 ObjSpaceViewDir( in float4 v ) { float3 objSpaceCameraPos = mul(_World2Object, float4(_WorldSpaceCameraPos.xyz, 1)).xyz; return objSpaceCameraPos - v.xyz; }
將光源轉換到模型空間:
// Computes object space light direction inline float3 ObjSpaceLightDir( in float4 v ) { float3 objSpaceLightPos = mul(_World2Object, _WorldSpaceLightPos0).xyz; #ifndef USING_LIGHT_MULTI_COMPILE return objSpaceLightPos.xyz - v.xyz * _WorldSpaceLightPos0.w; #else #ifndef USING_DIRECTIONAL_LIGHT return objSpaceLightPos.xyz - v.xyz; #else return objSpaceLightPos.xyz; #endif #endif }