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个向量的叉乘来取得。
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 法线贴图的更多相关文章
- Esfog_UnityShader教程_NormalMap法线贴图
咳咳,好久没有更新了,一来是这段时间很忙很忙,再来就是自己有些懒了,这个要不得啊,赶紧补上.在前面我们已经介绍过了漫反射和镜面反射,这两个是基本的光照类型,仅仅依靠它们就想制作出精美的效果是远远不够的 ...
- Unity3d《Shader篇》法线贴图
效果图 贴图 法线贴图 //代码 Shader "Custom/NormalMap" { Properties { _MainTex ("Texture", 2 ...
- 翻译:非常详细易懂的法线贴图(Normal Mapping)
翻译:非常详细易懂的法线贴图(Normal Mapping) 本文翻译自: Shaders » Lesson 6: Normal Mapping 作者: Matt DesLauriers 译者: Fr ...
- Unity3D ShaderLab法线贴图
Unity3D ShaderLab法线贴图 说到法线贴图,应该算是我们最常使用的一种增强视觉效果的贴图.将法线贴图的各个像素点座位模型的法线,这样我们的光照可以模拟出高分辨率的效果, 同时也保持较低的 ...
- 【Unity Shaders】学习笔记——SurfaceShader(七)法线贴图
[Unity Shaders]学习笔记——SurfaceShader(七)法线贴图 转载请注明出处:http://www.cnblogs.com/-867259206/p/5627565.html 写 ...
- shader复杂与深入:Normal Map(法线贴图)1
转自:http://www.zwqxin.com/archives/shaderglsl/review-normal-map-bump-map.htmlNormal Map法线贴图,想必每个学习计算机 ...
- (转)Unity3D 游戏贴图(法线贴图,漫反射贴图,高光贴图)
原帖网址http://www.u3dpro.com/read.php?tid=207 感谢jdk900网友的辛苦编写 我们都知道,一个三维场景的画面的好坏,百分之四十取决于模型,百分之六十取决于贴图 ...
- 【Unity Shader】六、使用法线贴图(Normal Map)的Shader
学习资料: http://www.sikiedu.com/course/37/task/456/show# http://www.sikiedu.com/course/37/task/458/show ...
- 在 iPad 上试验从用算法生成法线贴图-到法线映射光照效果
在 iPad 上试验从用算法生成法线贴图-到法线映射光照效果 目录 概述 一般来说, 法线贴图是用高模的法线图, 低模的纹理图, 来生成较好的渲染效果. 而法线图通常是通过图像处理软件来生成的, 这里 ...
随机推荐
- JDK与JRE、JVM三者间的关系及JDK的安装部署
JDK与JRE.JVM三者间的关系及JDK的安装部署 一.JDK与JRE.JVM三者间的关系 JDK(Java Development Kit)是针对Java开发员的产品,是整个Java的核心,包括了 ...
- 小心我“DIR”溢出你!
转自https://blog.csdn.net/wql19881207/article/details/6300760 https://blog.csdn.net/wql19881207/articl ...
- C. Mahmoud and a Message dp + 暴力
http://codeforces.com/contest/766/problem/C 关键在于dp,如何计数. 设dp[i]表示前i个字母中,能分成多少份合法的情况.那么答案就是dp[n],其中dp ...
- 关于springMVC传参问题
今天写项目,碰到一个以前灭有注意到的问题,一般情况下使用springMVC @Controller注解之后,被此注解标记的方法的参数名只需要跟页面表单的标签的name的值相同即可拿到页面的值,但是如果 ...
- Web前端深思
WEB视图层技术从最初刀耕火种的时代到如今技术框架丛生,其中的感受只有经历过才知道.但到目前为止前端领域还只是整个IT行业比较边缘化的分支,因为目前的前端coder大多都还停留在视图层的处理上,利用前 ...
- PAT甲级考前整理(2019年3月备考)之三,持续更新中.....
PAT甲级考前整理一:https://www.cnblogs.com/jlyg/p/7525244.html,主要讲了131题的易错题及坑点 PAT甲级考前整理二:https://www.cnblog ...
- 详解java中staitc关键字
一.static定义 static是静态修饰符意思,什么叫静态修饰符呢?大家都知道,在程序中任何变量或者代码都是在编译时由系统自动分配内存来存储的,而所谓静态就是指在编译后所分配的内存会一直存在,直到 ...
- Farseer.net轻量级ORM开源框架 V1.x 入门篇:新版本说明
导航 目 录:Farseer.net轻量级ORM开源框架 目录 上一篇:没有了 下一篇:Farseer.net轻量级ORM开源框架 V1.x 入门篇:数据库配置 前言 V1.x版本终于到来了.本次 ...
- 编写图形界面下的Java Swing程序,接受用户输入的两个数据为上下限,然后输出上、下限之间的所有素数。(结果部分每行显示10个数据)
这个代码我整体写的比较简单,读者可以简单参考一下,对人家题目要求略微修改了一下,多加了一个“置空”按钮.下图为我设计的界面 运行程序后的截图请看我后面的截图: package com.wangshil ...
- Beta测试团队
---恢复内容开始--- Beta版本测试 这个作业属于哪个课程 https://edu.cnblogs.com/campus/xnsy/SoftwareEngineeringClass1/?page ...