法线贴图+纹理贴图(细节明显)

纹理贴图

法线贴图

法线贴图

  存储法线的一张贴图,归一化的法线的 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个向量的叉乘来取得。

    

             struct vertexInput{
float4 vertex:POSITION;
float3 normal:NORMAL;
float4 texcoord:TEXCOORD0;
float4 tangent:TANGENT;
};

  在顶点函数中计算三个轴:

    

                 o.normal = normalize(v.normal);
o.tangent = normalize(v.tangent-v.normal*v.tangent*v.normal);
o.binormal = cross(v.normal, v.tangent)*v.tangent.w;

  其中:切线向量o.tangent通过Gram-Schmidt修正,使它与法线垂直;副切线向量o.binormal通过乘以v.tangent.w计算它的长度。          

   有了这3个切向量,我们就可以定义变换矩阵:

           float3x3 local2ObjectTranspose=float3x3(
i.tangent,
i.binormal,
i.normal
);

  在该矩阵中,没有平移矩阵,只有旋转矩阵,旋转矩阵又是正交矩阵,TBN两两垂直,正交矩阵的逆矩阵就是其转置矩阵。

  这个矩阵是模型空间到切空间的转换矩阵。

法线Shader:

在切空间计算:

源代码:

 //在切空间计算光源方向
Shader "JQM/NoamalMap_1"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_BumpMap ("Normal Texture", 2D) = "bump" {}
_BumpDepth("_Bump Depth",Range(-,2.0)) =
} SubShader
{ Pass
{
Tags { "LightMode"="ForwardBase" } CGPROGRAM
#pragma vertex vert
#pragma fragment frag #include "UnityCG.cginc" //使用自定义变量
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _BumpMap;
float4 _BumpMap_ST;
uniform float _BumpDepth; //使用Unity定义的变量
uniform float4 _LightColor0; //输入结构体
struct vertexInput{
float4 vertex:POSITION;
float3 normal:NORMAL;
float4 texcoord:TEXCOORD0;
float4 tangent:TANGENT;
}; //输出结构体
struct vertexOutput{
float4 pos:SV_POSITION;
float4 tex:TEXCOORD0;
float4 posWorld:TEXCOORD1;
float3 normal:TEXCOORD2;
float3 tangent:TEXCOORD3;
float3 binormal:TEXCOORD4;
}; vertexOutput vert (vertexInput v)
{
vertexOutput o; o.normal = normalize(v.normal);
o.tangent = normalize(v.tangent-v.normal*v.tangent*v.normal);
o.binormal = cross(v.normal, v.tangent)*v.tangent.w; o.posWorld = mul(_Object2World, v.vertex);
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
o.tex = v.texcoord; return o;
} fixed4 frag (vertexOutput i) : COLOR
{
float3 viewDirection = normalize(_WorldSpaceCameraPos.xyz- i.posWorld.xyz);
float3 lightDirection;
float atten; if(_WorldSpaceLightPos0.w==0.0)//平行光
{
atten = 1.0;
lightDirection = normalize(_WorldSpaceLightPos0.xyz);
}
else
{
float3 fragmentToLightSource = _WorldSpaceLightPos0.xyz -i.posWorld.xyz;
float distance = length(fragmentToLightSource);
atten = 1.0/distance;
lightDirection = normalize(fragmentToLightSource);
} //Texture Map
float4 tex = tex2D(_MainTex,i.tex.xy*_MainTex_ST.xy+_MainTex_ST.zw);
float4 texN = tex2D(_BumpMap,i.tex.xy*_BumpMap_ST.xy+_BumpMap_ST.zw); //UnpackNormal [0,1] 转换成[-1,1]
float3 localCoords = float3(2.0*texN.ag-float2(1.0,1.0),0.0);
localCoords.z = _BumpDepth; float3x3 Tangent2ObjectTranspose = float3x3(
i.tangent,
i.binormal,
i.normal
); //在切空间计算光照
lightDirection = normalize(mul(_World2Object,lightDirection));
lightDirection = normalize(mul(Tangent2ObjectTranspose,lightDirection));//转置矩阵=逆矩阵:TBN两两垂直 float3 diffuseReflection = saturate( dot(localCoords,lightDirection)); return float4(diffuseReflection*tex.xyz,1.0);
}
ENDCG
}
}
}

在世界空间计算:

 //在世界空间计算光源方向
Shader "JQM/NoamalMap_2"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_BumpMap ("Normal Texture", 2D) = "bump" {}
_BumpDepth("_Bump Depth",Range(-,2.0)) =
} SubShader
{ Pass
{
Tags { "LightMode"="ForwardBase" } CGPROGRAM
#pragma vertex vert
#pragma fragment frag #include "UnityCG.cginc" //使用自定义变量
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _BumpMap;
float4 _BumpMap_ST;
uniform float _BumpDepth; //使用Unity定义的变量
uniform float4 _LightColor0; //输入结构体
struct vertexInput{
float4 vertex:POSITION;
float3 normal:NORMAL;
float4 texcoord:TEXCOORD0;
float4 tangent:TANGENT;
}; //输出结构体
struct vertexOutput{
float4 pos:SV_POSITION;
float4 tex:TEXCOORD0;
float4 posWorld:TEXCOORD1;
float3 normalWorld:TEXCOORD2;
float3 tangentWorld:TEXCOORD3;
float3 binormalWorld:TEXCOORD4;
}; vertexOutput vert (vertexInput v)
{
vertexOutput o; o.normalWorld = normalize(mul(float4(v.normal,0.0),_World2Object).xyz);//法线向量转世界坐标,不同于顶点,他需要乘以模型转换矩阵的逆的转置;
o.tangentWorld = normalize(mul(_Object2World,v.tangent).xyz);
o.tangentWorld = o.tangentWorld-o.normalWorld*o.tangentWorld*o.normalWorld;//修正不垂直,使他们垂直
o.binormalWorld = cross(o.normalWorld, o.tangentWorld); o.posWorld = mul(_Object2World, v.vertex);
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
o.tex = v.texcoord; return o;
} fixed4 frag (vertexOutput i) : COLOR
{
float3 viewDirection = normalize(_WorldSpaceCameraPos.xyz- i.posWorld.xyz);
float3 lightDirection;
float atten; if(_WorldSpaceLightPos0.w==0.0)//平行光
{
atten = 1.0;
lightDirection = normalize(_WorldSpaceLightPos0.xyz);
}
else
{
float3 fragmentToLightSource = _WorldSpaceLightPos0.xyz -i.posWorld.xyz;
float distance = length(fragmentToLightSource);
atten = 1.0/distance;
lightDirection = normalize(fragmentToLightSource);
} //Texture Map
float4 tex = tex2D(_MainTex,i.tex.xy*_MainTex_ST.xy+_MainTex_ST.zw);
float4 texN = tex2D(_BumpMap,i.tex.xy*_BumpMap_ST.xy+_BumpMap_ST.zw); //UnpackNormal [0,1] 转换成[-1,1]
float3 localCoords = float3(2.0*texN.ag-float2(1.0,1.0),0.0);
localCoords.z = _BumpDepth; float3x3 Tangent2WorldTranspose = float3x3(
i.tangentWorld,
i.binormalWorld,
i.normalWorld
); //将法线转到世界空间
localCoords = normalize(mul(localCoords,Tangent2WorldTranspose)); float3 diffuseReflection = saturate( dot(localCoords,lightDirection)); return float4(diffuseReflection*tex.xyz,1.0);
}
ENDCG
}
}
}

    

Unity 3D 的UnityCG.cginc文件定义的切空间旋转矩阵:

 #define TANGENT_SPACE_ROTATION \
float3 binormal = cross( normalize(v.normal), normalize(v.tangent.xyz) ) * v.tangent.w; \
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, )).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
}

最新文章

  1. django queryset values&values_list
  2. struct和typedef struct用法
  3. 66. 有序数组构造二叉搜索树[array to binary search tree]
  4. 在DNS管理器——用局域网IP指定你所起的域名名称
  5. JAVA G1收集器 第11节
  6. 注解方式实现Spring声明式事务管理
  7. Chapter 2 Open Book——24
  8. Sitemesh 3 配置和使用(最新)
  9. [USACO 3.3.1]骑马修栅栏t
  10. jquery +/-小样式
  11. Spark:聚类算法
  12. appium 版本更新后的方法变化更新收集 ---持续更新
  13. meaven
  14. PHP开发——分支结构
  15. 移动端web兼容各种分辨率写法
  16. CSS opacity的兼容写法
  17. mongodb的认证(authentication)与授权(authorization)
  18. 怎样连接REDIS服务端
  19. DHCP 服务测试
  20. js本地储存userData实例

热门文章

  1. [ZPG TEST 115] 种树【差分约束】
  2. glassfish应用服务器安装配置
  3. F 点与多边形 数学 + 观察
  4. Windows下的一个Nginx 批处理命令行控制台
  5. vue--组件中的自定义事件
  6. 使用一个CSS Class去给标签定义Style
  7. 输入域名网站访问不了,ping与ftp都正常,这情况有可能域名被墙
  8. 常用css属性拓展
  9. zabbix基础安装
  10. 小b和矩阵