NormalMap 法线贴图
法线贴图+纹理贴图(细节明显)
纹理贴图
法线贴图
法线贴图
存储法线的一张贴图,归一化的法线的 xyz 的值被映射成为对应的 RGB 值。归一化的法线值为[-1,1],RGB的每一个分量为无符号的8位组成,范围[0,255]。即法线的分量由[-1,1]映射成[0,255]。法线贴图一般呈蓝色,因为大多数朝向 (0,0,1)的法线被映射成为了 (0,0,255)。
转换关系:
法线转RGB:
R = (x+1)/2*255;
G = (y+1)/2*255;
B = (z+1)/2*255;
RGB转法线:
x = (R/255*2)-1;
y = (G/255*2)-1;
z = (B/255*2)-1;
切空间(Tangent Space,TBN):纹理空间
切空间是在某一点所有的切向量组成的线性空间。也就是说,在模型每个顶点中,都存在这样的一个切空间坐标系,以模型顶点为中心,再加上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两两垂直,正交矩阵的逆矩阵就是其转置矩阵。
这个矩阵是模型空间到切空间的转换矩阵。
法线Shader:
在切空间计算:
源代码:
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 }