【DX11地形篇】3-高度图

参考教程

Tutorial 2: Bitmap Height Maps

学习记录

  这篇文章中我们简单介绍使用高度图来实现非平面地形的绘制,本篇代码基于上一篇。

  高度图其实就是一张单通道分量图(灰度图),每一个像素只有一个值。我们使用与我们地形网格等大小的高度图来表示我们地形中的高度。这篇文章中我们所使用的高度图如下:

1

  我们主要修改的也只是 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
////////////////////////////////////////////////////////////////////////////////
// Class name: TerrainClass
////////////////////////////////////////////////////////////////////////////////
class TerrainClass
{
private:
struct VertexType
{
XMFLOAT3 position;
XMFLOAT4 color;
};

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

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

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*);

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;
};

  添加了多个方法以读取高度图相关信息,例如我们的 Setup.txt 文件和 HeightMap.bmp 文件,其实现如下:

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
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
bool TerrainClass::LoadSetupFile(CHAR* filename) {
int stringLength;
std::ifstream fin;
char input;

// Initialize the string that will hold the terrain file name.
stringLength = 256;
m_terrainFilename = new char[stringLength];
if(!m_terrainFilename)
{
return false;
}

// Open the setup file. If it could not open the file then exit.
fin.open(filename);
if(fin.fail())
{
return false;
}

// Read up to the terrain file name.
fin.get(input);
while(input != ':')
{
fin.get(input);
}

// Read in the terrain file name.
fin >> m_terrainFilename;

// Read up to the value of terrain height.
fin.get(input);
while(input != ':')
{
fin.get(input);
}

// Read in the terrain height.
fin >> m_terrainHeight;

// Read up to the value of terrain width.
fin.get(input);
while (input != ':')
{
fin.get(input);
}

// Read in the terrain width.
fin >> m_terrainWidth;

// Read up to the value of terrain height scaling.
fin.get(input);
while (input != ':')
{
fin.get(input);
}

// Read in the terrain height scaling.
fin >> m_heightScale;

// Close the setup file.
fin.close();

return true;

}

bool TerrainClass::LoadBitmapHeightMap() {
int error, imageSize, i, j, k, index;
FILE* filePtr;
unsigned long long count;
BITMAPFILEHEADER bitmapFileHeader;
BITMAPINFOHEADER bitmapInfoHeader;
unsigned char* bitmapImage;
unsigned char height;


// Start by creating the array structure to hold the height map data.
m_heightMap = new HeightMapType[m_terrainWidth * m_terrainHeight];
if(!m_heightMap)
{
return false;
}

// Open the bitmap map file in binary.
error = fopen_s(&filePtr, m_terrainFilename, "rb");
if(error != 0)
{
return false;
}

// Read in the bitmap file header.
count = fread(&bitmapFileHeader, sizeof(BITMAPFILEHEADER), 1, filePtr);
if(count != 1)
{
return false;
}

// Read in the bitmap info header.
count = fread(&bitmapInfoHeader, sizeof(BITMAPINFOHEADER), 1, filePtr);
if(count != 1)
{
return false;
}

// Make sure the height map dimensions are the same as the terrain dimensions for easy 1 to 1 mapping.
if((bitmapInfoHeader.biHeight != m_terrainHeight) || (bitmapInfoHeader.biWidth != m_terrainWidth))
{
return false;
}

// Calculate the size of the bitmap image data.
// Since we use non-divide by 2 dimensions (eg. 257x257) we need to add an extra byte to each line.
imageSize = m_terrainHeight * ((m_terrainWidth * 3) + 1);

// Allocate memory for the bitmap image data.
bitmapImage = new unsigned char[imageSize];
if(!bitmapImage)
{
return false;
}

// Move to the beginning of the bitmap data.
fseek(filePtr, bitmapFileHeader.bfOffBits, SEEK_SET);

// Read in the bitmap image data.
count = fread(bitmapImage, 1, imageSize, filePtr);
if(count != imageSize)
{
return false;
}

// Close the file.
error = fclose(filePtr);
if(error != 0)
{
return false;
}

// Initialize the position in the image data buffer.
k=0;

// Read the image data into the height map array.
for(j=0; j<m_terrainHeight; j++)
{
for(i=0; i<m_terrainWidth; i++)
{
// Bitmaps are upside down so load bottom to top into the height map array.
index = (m_terrainWidth * (m_terrainHeight - 1 - j)) + i;

// Get the grey scale pixel value from the bitmap image data at this location.
height = bitmapImage[k];

// Store the pixel value as the height at this point in the height map array.
m_heightMap[index].y = (float)height;

// Increment the bitmap image data index.
k+=3;
}

// Compensate for the extra byte at end of each line in non-divide by 2 bitmaps (eg. 257x257).
k++;
}

// Release the bitmap image data now that the height map array has been loaded.
delete [] bitmapImage;
bitmapImage = 0;

// Release the terrain filename now that is has been read in.
delete [] m_terrainFilename;
m_terrainFilename = 0;

return true;

}

void TerrainClass::ShutdownHeightMap() {
// Release the height map array.
if(m_heightMap)
{
delete [] m_heightMap;
m_heightMap = 0;
}
return;
}

void TerrainClass::SetTerrainCoordinates() {
int i, j, index;


// Loop through all the elements in the height map array and adjust their coordinates correctly.
for(j=0; j<m_terrainHeight; j++)
{
for(i=0; i<m_terrainWidth; i++)
{
index = (m_terrainWidth * j) + i;

// Set the X and Z coordinates.
m_heightMap[index].x = (float)i;
m_heightMap[index].z = -(float)j;

// Move the terrain depth into the positive range. For example from (0, -256) to (256, 0).
m_heightMap[index].z += (float)(m_terrainHeight - 1);

// Scale the height.
m_heightMap[index].y /= m_heightScale;
}
}

return;

}

bool TerrainClass::BuildTerrainModel() {
int i, j, index, index1, index2, index3, index4;


// Calculate the number of vertices in the 3D terrain model.
m_vertexCount = (m_terrainHeight - 1) * (m_terrainWidth - 1) * 6;

// Create the 3D terrain model array.
m_terrainModel = new ModelType[m_vertexCount];
if(!m_terrainModel)
{
return false;
}

// Initialize the index into the height map array.
index = 0;

// Load the 3D terrain model with the height map terrain data.
// We will be creating 2 triangles for each of the four points in a quad.
for(j=0; j<(m_terrainHeight-1); j++)
{
for(i=0; i<(m_terrainWidth-1); i++)
{
// Get the indexes to the four points of the quad.
index1 = (m_terrainWidth * j) + i; // Upper left.
index2 = (m_terrainWidth * j) + (i+1); // Upper right.
index3 = (m_terrainWidth * (j+1)) + i; // Bottom left.
index4 = (m_terrainWidth * (j+1)) + (i+1); // Bottom right.

// Now create two triangles for that quad.
// Triangle 1 - Upper left.
m_terrainModel[index].x = m_heightMap[index1].x;
m_terrainModel[index].y = m_heightMap[index1].y;
m_terrainModel[index].z = m_heightMap[index1].z;
index++;

// Triangle 1 - Upper right.
m_terrainModel[index].x = m_heightMap[index2].x;
m_terrainModel[index].y = m_heightMap[index2].y;
m_terrainModel[index].z = m_heightMap[index2].z;
index++;

// Triangle 1 - Bottom left.
m_terrainModel[index].x = m_heightMap[index3].x;
m_terrainModel[index].y = m_heightMap[index3].y;
m_terrainModel[index].z = m_heightMap[index3].z;
index++;

// Triangle 2 - Bottom left.
m_terrainModel[index].x = m_heightMap[index3].x;
m_terrainModel[index].y = m_heightMap[index3].y;
m_terrainModel[index].z = m_heightMap[index3].z;
index++;

// Triangle 2 - Upper right.
m_terrainModel[index].x = m_heightMap[index2].x;
m_terrainModel[index].y = m_heightMap[index2].y;
m_terrainModel[index].z = m_heightMap[index2].z;
index++;

// Triangle 2 - Bottom right.
m_terrainModel[index].x = m_heightMap[index4].x;
m_terrainModel[index].y = m_heightMap[index4].y;
m_terrainModel[index].z = m_heightMap[index4].z;
index++;
}
}
return true;
}
void TerrainClass::ShutdownTerrainModel() {
// Release the terrain model data.
if(m_terrainModel)
{
delete [] m_terrainModel;
m_terrainModel = 0;
}

return;

}

  顶点的处理我们放在了 BuildTerrainModel 方法里,在 InitializeBuffers 方法里我们仅仅做一个赋值操作。

  最后,我们将渲染图元改为三角形:

1
deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

  最终效果:

2

  源代码:DX11TerrainTutorial-BitmapHeightMaps

0%