法线贴图的原理和实现
本文出自:http://shiba.hpe.sh.cn/jiaoyanzu/wuli/showArticle.aspx?articleId=328&classId=4
法线映射可以让由少量多边形构成的模型看起来像是由大量多边形构成的一样,无需添加更多的多边形。使用法线映射可以使表面(如墙壁)看起来更加富有细节和真实。展示法线映射的一个简单方法是模拟几何形状。要计算法线映射我们需要两个纹理:一个用于颜色贴图,如一张石头的纹理,另一个用于法线贴图,描述了法线的方向。我们通过储存在法线贴图中的法线信息计算光线,代替了前面使用顶点法线计算光线。
听起来挺容易?但是,在大多数法线映射技术(如我今天介绍的这个)中,法线信息是储存在一个称之为“纹理空间”坐标系统中,或“切线空间”坐标系统中。由于光线向量是在模型空间或世界空间中处理的,所以我们需要将光线矢量转换到法线贴图中的法线相同的空间(即切线空间)中去。
切线空间
看一下下面的图片展示了切线空间:
我们的shader将通过使用法线为纹理空间坐标系统创建一个W向量。然后,我们会在DirectX中一个叫做D3DXComputeTangent()的函数的帮助下计算V向量,接着通过叉乘V和W计算U向量。
U = V × W
后面我们会深入讨论如何实现,但现在,让我们先讨论下一件事:纹理!您可能已经注意到,我们需要纹理去实现法线映射。
要处理纹理,我们需要建立一些被称为纹理采样器(texture sampler)的东西。纹理采样器,顾名思义,是用来设置纹理采样器状态的。这可以是纹理过滤(在我的例子中是Linear)的信息,以及纹理寻址方式,这可以是clamp(夹持),mirror(镜像),Wrap(包装)等。
要创建一个采样器,我们首先需要定义一个纹理采样器将使用的变量:
现在,我们可以使用texDiffuse创建一个纹理采样器:
2 {
3 Texture =<texDiffuse>;
4 MinFilter = Linear;
5 MagFilter = Linear;
6 MipFilter = Linear;
7 AddressU = Wrap;
8 AddressV = Wrap;
9 };
生成切线空间
用D3DXLoadMeshFromX加载的网格数据的顶点格式仅包含位置和法线信息,为了生成切线空间,需要定义包含切线向量的顶点声明。
2 {
3 {0, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT,
4 D3DDECLUSAGE_POSITION, 0},
5 {0, 12, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT,
6 D3DDECLUSAGE_TEXCOORD, 0},
7 {0, 20, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT,
8 D3DDECLUSAGE_NORMAL, 0},
9 {0, 32, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT,
10 D3DDECLUSAGE_TANGENT, 0},
11 {0, 44, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT,
12 D3DDECLUSAGE_BINORMAL, 0},
13 D3DDECL_END()
14 };
以上面的顶点声明为参数调用CloneMesh()克隆网格,然后就可以用D3DXComputeTangent()生成切线向量。
2 D3DXMESH_MANAGED,
3 Vertex,
4 Device,
5 &CloneBox);
6 Box->Release();
7
8 float sd = CloneBox->GetNumFaces();
9
10 adjacency =new DWORD[CloneBox->GetNumFaces() *3];
11 CloneBox->GenerateAdjacency(0.001f, adjacency);
12 D3DXComputeTangent(
13 CloneBox,
14 0,
15 0,
16 0,
17 0,
18 adjacency);
效果文件:effect.fx
2
3 // Global
4 float4x4 matrixWorldViewProj;
5 float4x4 matrixWorld;
6 float4 vecLight;
7 float4 vecEye;
8
9 texture2D texDiffuse;
10 sampler DiffuseSampler = sampler_state
11 {
12 Texture =<texDiffuse>;
13 MinFilter = Linear;
14 MagFilter = Linear;
15 MipFilter = Linear;
16 AddressU = Wrap;
17 AddressV = Wrap;
18 };
19
20 texture2D texNormal;
21 sampler NormalSampler = sampler_state
22 {
23 Texture =<texNormal>;
24 MinFilter = Linear;
25 MagFilter = Linear;
26 MipFilter = Linear;
27 AddressU = Wrap;
28 AddressV = Wrap;
29 };
30
31 // Structures
32
33 // vs
34 struct VS_INPUT
35 {
36 float4 pos : POSITION;
37 float2 tex : TEXCOORD0;
38 float4 normal : NORMAL;
39 float4 tangent : TANGENT;
40 };
41
42 struct VS_OUTPUT
43 {
44 float4 pos : POSITION;
45 float2 tex : TEXCOORD0;
46 float4 light : TEXCOORD1;
47 float4 eye : TEXCOORD2;
48 };
49
50 // ps
51 struct PS_INPUT
52 {
53 float2 tex : TEXCOORD0;
54 float4 light : TEXCOORD1;
55 float4 eye : TEXCOORD2;
56 };
57
58 struct PS_OUTPUT
59 {
60 float4 color : COLOR;
61 };
62
63 // vertex shader
64 VS_OUTPUT VS(VS_INPUT input)
65 {
66 VS_OUTPUT output = (VS_OUTPUT)0;
67
68 output.pos = mul(input.pos, matrixWorldViewProj);
69
70 // output - texcoord
71 output.tex = input.tex;
72
73 // matrix - worldToTangentSpace
74 float4x4 worldToTangentSpace;
75 worldToTangentSpace[0] = mul( cross(input.tangent, input.normal), matrixWorld );
76 worldToTangentSpace[1] = mul( input.tangent, matrixWorld );
77 worldToTangentSpace[2] = mul( input.normal, matrixWorld );
78 worldToTangentSpace[3] = float4(0.0f, 0.0f, 0.0f, 1.0f);
79
80 // output - light
81 output.light = mul(worldToTangentSpace, vecLight);
82
83 // output - eye
84 float4 eyeTarget = mul(input.pos, matrixWorld);
85 output.eye = mul(worldToTangentSpace, vecEye - eyeTarget);
86
87 return output;
88 }
89
90 // pixsel shader
91 PS_OUTPUT PS(PS_INPUT input)
92 {
93 PS_OUTPUT output = (PS_OUTPUT)0;
94
95 // normal
96 float4 DiffuseColor = tex2D(DiffuseSampler, input.tex);
97 float4 normal =2* tex2D(NormalSampler, input.tex) -1.0f;
98 normal.w =0.0f;
99 normal = normalize(normal);
100
101 // light/eye
102 input.light.w =0;
103 input.eye.w =0;
104 float4 light = normalize(input.light);
105 float4 eye = normalize(input.eye);
106
107 float DiffuseIntensity = saturate( dot(normal, input.light) );
108
109 float4 ReflectDir = normalize(2.0f* dot(normal, light) * normal - light);
110 float Specular = pow(saturate(dot(ReflectDir, eye)), 18);
111
112 output.color = (0.0f+ DiffuseIntensity) * DiffuseColor + Specular;
113 //
114 return output;
115 }
116
117 // effect
118 technique Shader
119 {
120 pass P0
121 {
122 VertexShader = compile vs_2_0 VS();
123 PixelShader = compile ps_2_0 PS();
124 }
125 }