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

纹理贴图

法线贴图

法线贴图

  存储法线的一张贴图,归一化的法线的 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
}

NormalMap 法线贴图的更多相关文章

  1. Esfog_UnityShader教程_NormalMap法线贴图

    咳咳,好久没有更新了,一来是这段时间很忙很忙,再来就是自己有些懒了,这个要不得啊,赶紧补上.在前面我们已经介绍过了漫反射和镜面反射,这两个是基本的光照类型,仅仅依靠它们就想制作出精美的效果是远远不够的 ...

  2. Unity3d《Shader篇》法线贴图

    效果图 贴图 法线贴图 //代码 Shader "Custom/NormalMap" { Properties { _MainTex ("Texture", 2 ...

  3. 翻译:非常详细易懂的法线贴图(Normal Mapping)

    翻译:非常详细易懂的法线贴图(Normal Mapping) 本文翻译自: Shaders » Lesson 6: Normal Mapping 作者: Matt DesLauriers 译者: Fr ...

  4. Unity3D ShaderLab法线贴图

    Unity3D ShaderLab法线贴图 说到法线贴图,应该算是我们最常使用的一种增强视觉效果的贴图.将法线贴图的各个像素点座位模型的法线,这样我们的光照可以模拟出高分辨率的效果, 同时也保持较低的 ...

  5. 【Unity Shaders】学习笔记——SurfaceShader(七)法线贴图

    [Unity Shaders]学习笔记——SurfaceShader(七)法线贴图 转载请注明出处:http://www.cnblogs.com/-867259206/p/5627565.html 写 ...

  6. shader复杂与深入:Normal Map(法线贴图)1

    转自:http://www.zwqxin.com/archives/shaderglsl/review-normal-map-bump-map.htmlNormal Map法线贴图,想必每个学习计算机 ...

  7. (转)Unity3D 游戏贴图(法线贴图,漫反射贴图,高光贴图)

    原帖网址http://www.u3dpro.com/read.php?tid=207  感谢jdk900网友的辛苦编写 我们都知道,一个三维场景的画面的好坏,百分之四十取决于模型,百分之六十取决于贴图 ...

  8. 【Unity Shader】六、使用法线贴图(Normal Map)的Shader

    学习资料: http://www.sikiedu.com/course/37/task/456/show# http://www.sikiedu.com/course/37/task/458/show ...

  9. 在 iPad 上试验从用算法生成法线贴图-到法线映射光照效果

    在 iPad 上试验从用算法生成法线贴图-到法线映射光照效果 目录 概述 一般来说, 法线贴图是用高模的法线图, 低模的纹理图, 来生成较好的渲染效果. 而法线图通常是通过图像处理软件来生成的, 这里 ...

随机推荐

  1. shiro错误:Subject does not have permission [user:select]

    使用的是jdbcRealm,在数据库中有相应的权限 错误日志: org.apache.shiro.authz.UnauthorizedException: Subject does not have ...

  2. JavaScript--DOM浏览器窗口可视区域大小

    浏览器窗口可视区域大小 获得浏览器窗口的尺寸(浏览器的视口,不包括工具栏和滚动条)的方法: 一.对于IE9+.Chrome.Firefox.Opera 以及 Safari: •  window.inn ...

  3. 数据结构 - 链栈的实行(C语言)

    数据结构-链栈的实现 1 链栈的定义 现在来看看栈的链式存储结构,简称为链栈. 想想看栈只是栈顶来做插入和删除操作,栈顶放在链表的头部还是尾部呢?由于单链表有头指针,而栈顶指针也是必须的,那干吗不让它 ...

  4. xml小练习

    挑选你熟悉省份,制作xml城市列表 ----- 必备城市基本信息 10个城市 --- 一定要有属性对城市列表 添加DTD约束 <?xml version="1.0" enco ...

  5. import android.support.v4或者import android.support.v7提示导入错误解决办法

    转自:  http://blog.csdn.net/forandever/article/details/37655139 在使用Eclipse开发andriod程序时,程序中提示import and ...

  6. 教你如何在实战项目中使用WCF

    我们都知道调用WCF直接在Service References中引用可以远程调用的WCF Url就行了. 但是我们想过没,在Development环境中可以这样做,但是QA.UAT.Productio ...

  7. NavigationView的使用

    代码已经分享至github:https://github.com/YanYoJun/NavigationDemo 转载请注明原文链接:http://www.cnblogs.com/yanyojun/p ...

  8. 掌握Spark机器学习库-07-线性回归算法概述

    1)简介 自变量,因变量,线性关系,相关系数,一元线性关系,多元线性关系(平面,超平面) 2)使用线性回归算法的前提 3)应用例子 沸点与气压 浮力与表面积

  9. RGB、YUV和YCbCr介绍【转】

    RGB: 就是常说的红(Red).绿(Green)和蓝(Blue),每个图像的像素点由RGB三个通道的值组成. YUV和YCbCr: YUV与RGB的转换: Y'= 0.299*R' + 0.587* ...

  10. 核武器代理CC工具V3.42最新版本!

    软件说明 !!!有新版本更新,请移步到更新地址:https://www.cnblogs.com/cnhacker/p/10878688.html ########################### ...