http://www.manew.com/thread-97665-1-1.html
内容说明:
1. 法线贴图的原理
2. 法线贴图的unity实现
1. 法线贴图的原理
2. 法线贴图的unity实现
1. 法线贴图的原理
1.1 什么是法线贴图?
每个顶点都有一条法线,三角形内部法线由插值计算得出,颜色则是直接从纹理取数据。法线贴图的基本思想就是像纹理采样一样为法线取值。
1.2法线纹理
1.1 什么是法线贴图?
每个顶点都有一条法线,三角形内部法线由插值计算得出,颜色则是直接从纹理取数据。法线贴图的基本思想就是像纹理采样一样为法线取值。
1.2法线纹理
下图是一张法线的纹理图:
每个纹素的RGB值实际上表示的是XYZ向量:颜色的分量取值范围为0到1,而向量的分量取值范围是-1到1;可以建立从纹素到法线的简单映射:
法线纹理的映射方式和漫反射纹理相似。麻烦之处在于如何将法线从各三角形局部空间(切线空间tangent space,亦称图像空间image space)变换到模型空间(着色计算所采用的空间)。
1.3切线空间
大家对矩阵已经十分熟悉了,应该知道定义一个空间需要三个向量。现在Up向量已经有了,即法线:
大家对矩阵已经十分熟悉了,应该知道定义一个空间需要三个向量。现在Up向量已经有了,即法线:
然后是切线T:垂直于法线的向量。但这样的切线有很多个:
这么多切线中该选哪个呢?理论上哪一个都行。但我们必须保持连续一致性,以免衔接处出现瑕疵。Unity已经给我提供好了Tangent。
定义一组基需要三个向量,因此我们还得计算副切线B。可以使用法线和切线叉乘计算得到。那么切空间就可以表示成如下图这样了。
定义一组基需要三个向量,因此我们还得计算副切线B。可以使用法线和切线叉乘计算得到。那么切空间就可以表示成如下图这样了。
已知T、B、N向量之后,即可得下面这个漂亮的矩阵,完成从切线空间到模型空间的变换:
只需对上述矩阵求逆即可得逆变换。这个矩阵(正交阵,即各向量相互正交的矩阵,参见下文“延伸阅读”小节)的逆矩阵恰好也就是其转置矩阵,计算十分简单:
2. unity中的实现
我们看下unity实现的代码:
我们看下unity实现的代码:
[C#] 纯文本查看 复制代码
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
Shader
"Custom/NewSurfaceShader"
{
Properties
{
_MainText("MainText",2D)="white"{}
_BumpMap("BumpMap",2D)="white"{}
}
SubShader
{
pass{
Tags{"LightMode"="ForwardBase"}
CGPROGRAM
#pragma
vertex vert
#pragma
fragment frag
#include
"UnityCG.cginc"
float4
_LightColor0;
sampler2D
_BumpMap;
sampler2D
_MainText;
struct
v2f {
float4
pos:SV_POSITION;
float2
uv:TEXCOORD0;
float3
lightDir:TEXCOORD1;
};
v2f
vert (appdata_full v) {
v2f
o;
o.pos=mul(UNITY_MATRIX_MVP,v.vertex);
o.uv=v.texcoord.xy;
TANGENT_SPACE_ROTATION;
o.lightDir=
mul(_World2Object, _WorldSpaceLightPos0).xyz;//Direction
Light
o.lightDir=mul(rotation,o.lightDir);
return
o;
}
float4
frag(v2f i):COLOR
{
float4
c=1;
float3
N=UnpackNormal(tex2D(_BumpMap,i.uv));
float
diff=max(0,dot(N,i.lightDir));
c=_LightColor0*diff;
c*=tex2D(_MainText,i.uv);
return
c;
}
ENDCG
}
}
}
|
我们把光照贴图给附上,材质分别给一个平面和一个球看下效果:
2.3 UnpackNormal函数说明
[C#] 纯文本查看 复制代码
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
inline
fixed3 UnpackNormalDXT5nm (fixed4 packednormal)
{
fixed3
normal;
normal.xy
= packednormal.wy * 2 - 1;
normal.z
= sqrt(1 - saturate(dot(normal.xy, normal.xy)));
return
normal;
}
inline
fixed3 UnpackNormal(fixed4 packednormal)
{
#if
defined(UNITY_NO_DXT5nm)
return
packednormal.xyz * 2 - 1;
#else
return
UnpackNormalDXT5nm(packednormal);
#endif
}
|
可以看见不同的压缩格式解析有一定的区别,上面讲解的不是很全面,想了解更多可以自行百度下。
2.4 用Surface Shader来实现下
[C#] 纯文本查看 复制代码
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
Shader
"Custom/SurfaceShader"
{
Properties
{
_MainTex
("Base
(RGB)",
2D) = "white"
{}
_BumpMap
("Normalmap",
2D) = "bump"
{}
}
SubShader
{
Tags
{ "RenderType"="Opaque"
}
LOD
250
CGPROGRAM
#pragma
surface surf Lambert noforwardadd
sampler2D
_MainTex;
sampler2D
_BumpMap;
struct
Input {
float2
uv_MainTex;
};
void
surf (Input IN, inout SurfaceOutput o) {
fixed4
c = tex2D(_MainTex, IN.uv_MainTex);
o.Albedo
= c.rgb;
o.Alpha
= c.a;
o.Normal
= UnpackNormal(tex2D(_BumpMap, IN.uv_MainTex));
}
ENDCG
}
FallBack
Off
}
|
代码下载地址http://pan.baidu.com/s/1boCvoIz