【DX11地形篇】6-网格法线贴图

参考教程

Tutorial 6: Terrain Normal Mapping

【DirectX】24-法线贴图

学习记录

  这篇文章中我们介绍地形网格的法线贴图,本篇代码基于上一篇,仅有少许的改动。

  对地形的法线贴图和之前介绍的单独法线贴图并无区别,我们需要在 TerrainClass 中计算网格三角形的切线副切线,方法如下:

1
2
3
4
5
6
7
8
9
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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
void TerrainClass::CalculateTerrainVectors()
{
int faceCount, i, index;
TempVertexType vertex1, vertex2, vertex3;
VectorType tangent, binormal;

// Calculate the number of faces in the terrain model.
faceCount = m_vertexCount / 3;

// Initialize the index to the model data.
index=0;

// Go through all the faces and calculate the the tangent, binormal, and normal vectors.
for(i=0; i<faceCount; i++)
{
// Get the three vertices for this face from the terrain model.
vertex1.x = m_terrainModel[index].x;
vertex1.y = m_terrainModel[index].y;
vertex1.z = m_terrainModel[index].z;
vertex1.tu = m_terrainModel[index].tu;
vertex1.tv = m_terrainModel[index].tv;
vertex1.nx = m_terrainModel[index].nx;
vertex1.ny = m_terrainModel[index].ny;
vertex1.nz = m_terrainModel[index].nz;
index++;

vertex2.x = m_terrainModel[index].x;
vertex2.y = m_terrainModel[index].y;
vertex2.z = m_terrainModel[index].z;
vertex2.tu = m_terrainModel[index].tu;
vertex2.tv = m_terrainModel[index].tv;
vertex2.nx = m_terrainModel[index].nx;
vertex2.ny = m_terrainModel[index].ny;
vertex2.nz = m_terrainModel[index].nz;
index++;

vertex3.x = m_terrainModel[index].x;
vertex3.y = m_terrainModel[index].y;
vertex3.z = m_terrainModel[index].z;
vertex3.tu = m_terrainModel[index].tu;
vertex3.tv = m_terrainModel[index].tv;
vertex3.nx = m_terrainModel[index].nx;
vertex3.ny = m_terrainModel[index].ny;
vertex3.nz = m_terrainModel[index].nz;
index++;

// Calculate the tangent and binormal of that face.
CalculateTangentBinormal(vertex1, vertex2, vertex3, tangent, binormal);

// Store the tangent and binormal for this face back in the model structure.
m_terrainModel[index-1].tx = tangent.x;
m_terrainModel[index-1].ty = tangent.y;
m_terrainModel[index-1].tz = tangent.z;
m_terrainModel[index-1].bx = binormal.x;
m_terrainModel[index-1].by = binormal.y;
m_terrainModel[index-1].bz = binormal.z;

m_terrainModel[index-2].tx = tangent.x;
m_terrainModel[index-2].ty = tangent.y;
m_terrainModel[index-2].tz = tangent.z;
m_terrainModel[index-2].bx = binormal.x;
m_terrainModel[index-2].by = binormal.y;
m_terrainModel[index-2].bz = binormal.z;

m_terrainModel[index-3].tx = tangent.x;
m_terrainModel[index-3].ty = tangent.y;
m_terrainModel[index-3].tz = tangent.z;
m_terrainModel[index-3].bx = binormal.x;
m_terrainModel[index-3].by = binormal.y;
m_terrainModel[index-3].bz = binormal.z;
}
return;
}

void TerrainClass::CalculateTangentBinormal(TempVertexType vertex1, TempVertexType vertex2, TempVertexType vertex3, VectorType& tangent, VectorType& binormal)
{
float vector1[3], vector2[3];
float tuVector[2], tvVector[2];
float den;
float length;


// Calculate the two vectors for this face.
vector1[0] = vertex2.x - vertex1.x;
vector1[1] = vertex2.y - vertex1.y;
vector1[2] = vertex2.z - vertex1.z;

vector2[0] = vertex3.x - vertex1.x;
vector2[1] = vertex3.y - vertex1.y;
vector2[2] = vertex3.z - vertex1.z;

// Calculate the tu and tv texture space vectors.
tuVector[0] = vertex2.tu - vertex1.tu;
tvVector[0] = vertex2.tv - vertex1.tv;

tuVector[1] = vertex3.tu - vertex1.tu;
tvVector[1] = vertex3.tv - vertex1.tv;

// Calculate the denominator of the tangent/binormal equation.
den = 1.0f / (tuVector[0] * tvVector[1] - tuVector[1] * tvVector[0]);

// Calculate the cross products and multiply by the coefficient to get the tangent and binormal.
tangent.x = (tvVector[1] * vector1[0] - tvVector[0] * vector2[0]) * den;
tangent.y = (tvVector[1] * vector1[1] - tvVector[0] * vector2[1]) * den;
tangent.z = (tvVector[1] * vector1[2] - tvVector[0] * vector2[2]) * den;

binormal.x = (tuVector[0] * vector2[0] - tuVector[1] * vector1[0]) * den;
binormal.y = (tuVector[0] * vector2[1] - tuVector[1] * vector1[1]) * den;
binormal.z = (tuVector[0] * vector2[2] - tuVector[1] * vector1[2]) * den;

// Calculate the length of the tangent.
length = (float)sqrt((tangent.x * tangent.x) + (tangent.y * tangent.y) + (tangent.z * tangent.z));

// Normalize the tangent and then store it.
tangent.x = tangent.x / length;
tangent.y = tangent.y / length;
tangent.z = tangent.z / length;

// Calculate the length of the binormal.
length = (float)sqrt((binormal.x * binormal.x) + (binormal.y * binormal.y) + (binormal.z * binormal.z));

// Normalize the binormal and then store it.
binormal.x = binormal.x / length;
binormal.y = binormal.y / length;
binormal.z = binormal.z / length;

return;
}

  TerrainClass 的整体声明如下:

1
2
3
4
5
6
7
8
9
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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
////////////////////////////////////////////////////////////////////////////////
// Class name: TerrainClass
////////////////////////////////////////////////////////////////////////////////
class TerrainClass
{
private:
struct VertexType
{
XMFLOAT3 position;
XMFLOAT2 texcoord;
XMFLOAT3 normal;
XMFLOAT3 tangent;
XMFLOAT3 binormal;

};

struct HeightMapType {
float x, y, z;
float nx, ny, nz;
};

struct ModelType {
float x, y, z;
float tu, tv;
float nx, ny, nz;
float tx, ty, tz;
float bx, by, bz;
};

struct VectorType {
float x, y, z;
};

struct TempVertexType
{
float x, y, z;
float tu, tv;
float nx, ny, nz;
};

public:
TerrainClass();
TerrainClass(const TerrainClass&);
~TerrainClass();

bool Initialize(ID3D11Device* , char*);
void Shutdown();
bool Render(ID3D11DeviceContext*);

int GetIndexCount();

private:
bool InitializeBuffers(ID3D11Device*);
void ShutdownBuffers();
void RenderBuffers(ID3D11DeviceContext*);

void CalculateTerrainVectors();
void CalculateTangentBinormal(TempVertexType, TempVertexType, TempVertexType, VectorType&, VectorType&);

bool CalculateNormals();
bool LoadSetupFile(CHAR*);
bool LoadBitmapHeightMap();
void ShutdownHeightMap();
void SetTerrainCoordinates();
bool BuildTerrainModel();
void ShutdownTerrainModel();

private:
ID3D11Buffer *m_vertexBuffer, *m_indexBuffer;
int m_vertexCount, m_indexCount;

int m_terrainHeight , m_terrainWidth;
float m_heightScale;
LPSTR m_terrainFilename;
HeightMapType* m_heightMap;
ModelType* m_terrainModel;
};

  之后我们需要一个从 LightShaderClass 修改而来的支持切线与副切线数据的 TerrainShaderClass ,声明如下:

1
2
3
4
5
6
7
8
9
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
44
45
////////////////////////////////////////////////////////////////////////////////
// Class name: TerrainShaderClass
////////////////////////////////////////////////////////////////////////////////
class TerrainShaderClass
{
private:
struct MatrixBufferType
{
XMMATRIX world;
XMMATRIX view;
XMMATRIX projection;
};

struct LightBufferType
{
XMFLOAT4 diffuseColor;
XMFLOAT3 lightDirection;
float padding;
};

public:
TerrainShaderClass();
TerrainShaderClass(const TerrainShaderClass&);
~TerrainShaderClass();

bool Initialize(ID3D11Device*, HWND);
void Shutdown();
bool Render(ID3D11DeviceContext*, int, XMMATRIX, XMMATRIX, XMMATRIX, ID3D11ShaderResourceView*,ID3D11ShaderResourceView*, XMFLOAT3, XMFLOAT4);

private:
bool InitializeShader(ID3D11Device*, HWND, CHAR*, CHAR*);
void ShutdownShader();
void OutputShaderErrorMessage(ID3D10Blob*, HWND, CHAR*);

bool SetShaderParameters(ID3D11DeviceContext*, XMMATRIX, XMMATRIX, XMMATRIX, ID3D11ShaderResourceView*,ID3D11ShaderResourceView*, XMFLOAT3, XMFLOAT4);
void RenderShader(ID3D11DeviceContext*, int);

private:
ID3D11VertexShader* m_vertexShader;
ID3D11PixelShader* m_pixelShader;
ID3D11InputLayout* m_layout;
ID3D11Buffer* m_matrixBuffer;
ID3D11SamplerState* m_sampleState;
ID3D11Buffer* m_lightBuffer;
};

  在 terrain.vsterrain.ps 中使用切线副切线和法线贴图中的数据共同计算光照:

1
2
3
4
5
6
7
8
9
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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
////////////////////////////////////////////////////////////////////////////////
// Filename: terrain.vs
////////////////////////////////////////////////////////////////////////////////


/////////////
// GLOBALS //
/////////////
cbuffer MatrixBuffer
{
matrix worldMatrix;
matrix viewMatrix;
matrix projectionMatrix;
};


//////////////
// TYPEDEFS //
//////////////
struct VertexInputType
{
float4 position : POSITION;
float2 tex : TEXCOORD0;
float3 normal : NORMAL;
float3 tangent : TANGENT;
float3 binormal : BINORMAL;
};

struct PixelInputType
{
float4 position : SV_POSITION;
float2 tex : TEXCOORD0;
float3 normal : NORMAL;
float3 tangent : TANGENT;
float3 binormal : BINORMAL;
};


////////////////////////////////////////////////////////////////////////////////
// Vertex Shader
////////////////////////////////////////////////////////////////////////////////
PixelInputType TerrainVertexShader(VertexInputType input)
{
PixelInputType output;


// Change the position vector to be 4 units for proper matrix calculations.
input.position.w = 1.0f;

// Calculate the position of the vertex against the world, view, and projection matrices.
output.position = mul(input.position, worldMatrix);
output.position = mul(output.position, viewMatrix);
output.position = mul(output.position, projectionMatrix);

// Store the texture coordinates for the pixel shader.
output.tex = input.tex;

// Calculate the normal vector against the world matrix only.
output.normal = mul(input.normal, (float3x3)worldMatrix);

// Normalize the normal vector.
output.normal = normalize(output.normal);

// Calculate the tangent vector against the world matrix only and then normalize the final value.
output.tangent = mul(input.tangent, (float3x3)worldMatrix);
output.tangent = normalize(output.tangent);

// Calculate the binormal vector against the world matrix only and then normalize the final value.
output.binormal = mul(input.binormal, (float3x3)worldMatrix);
output.binormal = normalize(output.binormal);

return output;
}

////////////////////////////////////////////////////////////////////////////////
// Filename: terrain.ps
////////////////////////////////////////////////////////////////////////////////


/////////////
// GLOBALS //
/////////////
Texture2D shaderTexture : register(t0);
Texture2D normalTexture : register(t1);

SamplerState SampleType : register(s0);

//////////////////////
// CONSTANT BUFFERS //
//////////////////////
cbuffer LightBuffer
{
float4 diffuseColor;
float3 lightDirection;
float padding;
};


//////////////
// TYPEDEFS //
//////////////
struct PixelInputType
{
float4 position : SV_POSITION;
float2 tex : TEXCOORD0;
float3 normal : NORMAL;
float3 tangent : TANGENT;
float3 binormal : BINORMAL;
};


////////////////////////////////////////////////////////////////////////////////
// Pixel Shader
////////////////////////////////////////////////////////////////////////////////
float4 TerrainPixelShader(PixelInputType input) : SV_TARGET
{
float4 textureColor;
float3 lightDir;
float4 bumpMap;
float3 bumpNormal;
float lightIntensity;
float4 color;


// Sample the pixel color from the texture using the sampler at this texture coordinate location.
textureColor = shaderTexture.Sample(SampleType, input.tex);

// Invert the light direction for calculations.
lightDir = -lightDirection;

// Calculate the amount of light on this pixel using the normal map.
bumpMap = normalTexture.Sample(SampleType, input.tex);
bumpMap = (bumpMap * 2.0f) - 1.0f;
bumpNormal = (bumpMap.x * input.tangent) + (bumpMap.y * input.binormal) + (bumpMap.z * input.normal);
bumpNormal = normalize(bumpNormal);
lightIntensity = saturate(dot(bumpNormal, lightDir));

// Determine the final amount of diffuse color based on the diffuse color combined with the light intensity.
color = saturate(diffuseColor * lightIntensity);

// Multiply the texture pixel and the final diffuse color to get the final pixel color result.
color = (color + 0.3) * textureColor;

return color;
}

  最后在 ShaderManagerClass 中增加 TerrainShaderClass 的对象和在 ZoneClass 中使用 TerrainShaderClass 来渲染就可以了。

  最终效果如下:

1

  贴近看的话可以看到效果:

2

  源代码:DX11TerrainTutorial-TerrainNormalMap

  这篇文章之前我修改了操作逻辑,如今可以使用鼠标来旋转视角,使用 Space 向上 ,Ctrl 向下。

0%