[DirectX11] Terrain 02 - 높이 맵
Terrain 02 - 높이 맵
원문 : http://www.rastertek.com/tertut02.html
이 튜토리얼에서는 DirectX 11 및 C ++를 사용하여 3D로 지형을 표현하기 위한 높이 맵을 구현하는 방법에 대해 설명합니다. 이 듀토리얼의 코드는 이전 지형 듀토리얼을 기반으로 합니다.
높이 맵은 파일에 저장된 높이 포인트 매핑입니다. 높이 맵을 저장하는 가장 일반적인 방법은 비트맵, 원시, 텍스트 또는 이진 파일을 사용하는데 그 원리는 지형의 가장 낮은 높이가 0이고 최대 높이가 255 인 0-255의 값을 사용하여 지형의 높이를 저장하는 것입니다. 그레이 스케일 비트맵과 .raw 파일은 회색 색상의 밝기를 사용하여 높이를 나타낼 수 있기 때문에 높이 맵에 가장 적합합니다. 이것은 그리기 프로그램이나 수학적 알고리즘으로 편집하고 조작하기가 매우 쉽습니다.
높이 맵을 생성하는 가장 좋은 방법은 Perlin Noise 알고리즘을 사용하고 그 결과를 비트맵 파일에 저장하는 것입니다. 자신의 Perlin Noise 생성기를 작성하거나 이미 존재하는 Perlin Noise 생성기를 사용할 수 있습니다.
Perlin Noise generator를 사용하여 간단한 높이 맵을 다음과 같이 생성했습니다.
그 후 보통 다음과 같이 평평한 표면 / 언덕 스타일 지형을 생성하기 위해 Perlin Noise 이미지에 지수 알고리즘을 실행합니다.
마지막으로 다음과 같이 지형의 블록화 되지 않은 버전을 만들기 위해 이웃 픽셀을 평균화하는 평활화 프로세스를 실행합니다.
그런 다음 해당 이미지를 비트맵 형식으로 저장하고 이를 내 지형 높이 맵에 사용합니다. 비트맵은 지형 엔진에 로드되고 지도의 각 지점을 사용하여 삼각형으로 표시되는 지형 메쉬를 만듭니다.
이 튜토리얼에서는 이전 튜토리얼의 격자 코드를 새로운 높이 맵 코드로 바꿀 것입니다. 코드는 높이 맵에서 읽은 다음 그리드의 각 점을 높이 맵 높이와 일치하도록 높이기 위해 변경됩니다. 이렇게 하기 위해서는 간단히 TerrainClass 코드만 변경하면 됩니다.
Terrainclass.h
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 | #pragma once class TerrainClass { private: struct VertexType { XMFLOAT3 position; XMFLOAT4 color; }; struct HeightMapType { float x, y, z; }; public: TerrainClass(); TerrainClass(const TerrainClass&); ~TerrainClass(); bool Initialize(ID3D11Device*, const char*); void Shutdown(); void Render(ID3D11DeviceContext*); int GetIndexCount(); private: bool LoadHeightMap(const char*); void NormalizeHeightMap(); void ShutdownHeightMap(); bool InitializeBuffers(ID3D11Device*); void ShutdownBuffers(); void RenderBuffers(ID3D11DeviceContext*); private: int m_terrainWidth = 0; int m_terrainHeight = 0; int m_vertexCount = 0; int m_indexCount = 0; ID3D11Buffer* m_vertexBuffer = nullptr; ID3D11Buffer* m_indexBuffer = nullptr; HeightMapType* m_heightMap = nullptr; }; | cs |
Terrainclass.cpp
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 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 | #include "stdafx.h" #include "terrainclass.h" #include <stdio.h> TerrainClass::TerrainClass() { } TerrainClass::TerrainClass(const TerrainClass& other) { } TerrainClass::~TerrainClass() { } bool TerrainClass::Initialize(ID3D11Device* device, const char* heightMapFilename) { // 지형의 높이 맵을 로드합니다. if(!LoadHeightMap(heightMapFilename)) { return false; } // 높이 맵의 높이를 표준화합니다. NormalizeHeightMap(); // 지형에 대한 지오 메트릭을 포함하는 정점 및 인덱스 버퍼를 초기화합니다. return InitializeBuffers(device); } void TerrainClass::Shutdown() { // 버텍스와 인덱스 버퍼를 해제합니다. ShutdownBuffers(); // 높이맵 데이터를 해제합니다. ShutdownHeightMap(); } void TerrainClass::Render(ID3D11DeviceContext* deviceContext) { // 그리기를 준비하기 위해 그래픽 파이프 라인에 꼭지점과 인덱스 버퍼를 놓습니다. RenderBuffers(deviceContext); } int TerrainClass::GetIndexCount() { return m_indexCount; } bool TerrainClass::LoadHeightMap(const char* filename) { // 바이너리 모드로 높이맵 파일을 엽니다. FILE* filePtr = nullptr; if(fopen_s(&filePtr, filename, "rb") != 0) { return false; } // 파일 헤더를 읽습니다. BITMAPFILEHEADER bitmapFileHeader; if(fread(&bitmapFileHeader, sizeof(BITMAPFILEHEADER), 1, filePtr) != 1) { return false; } // 비트맵 정보 헤더를 읽습니다. BITMAPINFOHEADER bitmapInfoHeader; if(fread(&bitmapInfoHeader, sizeof(BITMAPINFOHEADER), 1, filePtr) != 1) { return false; } // 지형의 크기를 저장합니다. m_terrainWidth = bitmapInfoHeader.biWidth; m_terrainHeight = bitmapInfoHeader.biHeight; // 비트맵 이미지 데이터의 크기를 계산합니다. int imageSize = m_terrainWidth * m_terrainHeight * 3; // 비트맵 이미지 데이터에 메모리를 할당합니다. unsigned char* bitmapImage = new unsigned char[imageSize]; if(!bitmapImage) { return false; } // 비트맵 데이터의 시작 부분으로 이동합니다. fseek(filePtr, bitmapFileHeader.bfOffBits, SEEK_SET); // 비트맵 이미지 데이터를 읽습니다. if(fread(bitmapImage, 1, imageSize, filePtr) != imageSize) { return false; } // 파일을 닫습니다. if(fclose(filePtr) != 0) { return false; } // 높이 맵 데이터를 저장할 구조체를 만듭니다. m_heightMap = new HeightMapType[m_terrainWidth * m_terrainHeight]; if(!m_heightMap) { return false; } // 이미지 데이터 버퍼의 위치를 초기화합니다. int k = 0; // 이미지 데이터를 높이 맵으로 읽어들입니다. for(int j=0; j<m_terrainHeight; j++) { for(int i=0; i<m_terrainWidth; i++) { unsigned char height = bitmapImage[k]; int index = (m_terrainHeight * j) + i; m_heightMap[index].x = (float)i; m_heightMap[index].y = (float)height; m_heightMap[index].z = (float)j; k+=3; } } // 비트맵 이미지 데이터를 해제합니다. delete [] bitmapImage; bitmapImage = 0; return true; } void TerrainClass::NormalizeHeightMap() { for(int j=0; j<m_terrainHeight; j++) { for(int i=0; i<m_terrainWidth; i++) { m_heightMap[(m_terrainHeight * j) + i].y /= 15.0f; } } } void TerrainClass::ShutdownHeightMap() { if(m_heightMap) { delete [] m_heightMap; m_heightMap = 0; } } bool TerrainClass::InitializeBuffers(ID3D11Device* device) { // 지형 메쉬의 정점 수를 계산합니다. m_vertexCount = (m_terrainWidth - 1) * (m_terrainHeight - 1) * 12; // 인덱스 수를 꼭지점 수와 같게 설정합니다. m_indexCount = m_vertexCount; // 정점 배열을 만듭니다. VertexType* vertices = new VertexType[m_vertexCount]; if(!vertices) { return false; } // 인덱스 배열을 만듭니다. unsigned long* indices = new unsigned long[m_indexCount]; if(!indices) { return false; } // 정점 배열에 대한 인덱스를 초기화합니다. int index = 0; // 지형 데이터로 정점 및 인덱스 배열을 로드합니다. for(int j=0; j<(m_terrainHeight-1); j++) { for(int i=0; i<(m_terrainWidth-1); i++) { int index1 = (m_terrainHeight * j) + i; // 왼쪽 아래. int index2 = (m_terrainHeight * j) + (i+1); // 오른쪽 아래. int index3 = (m_terrainHeight * (j+1)) + i; // 왼쪽 위. int index4 = (m_terrainHeight * (j+1)) + (i+1); // 오른쪽 위. // 왼쪽 위. vertices[index].position = XMFLOAT3(m_heightMap[index3].x, m_heightMap[index3].y, m_heightMap[index3].z); vertices[index].color = XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f); indices[index] = index; index++; // 오른쪽 위. vertices[index].position = XMFLOAT3(m_heightMap[index4].x, m_heightMap[index4].y, m_heightMap[index4].z); vertices[index].color = XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f); indices[index] = index; index++; // 오른쪽 위. vertices[index].position = XMFLOAT3(m_heightMap[index4].x, m_heightMap[index4].y, m_heightMap[index4].z); vertices[index].color = XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f); indices[index] = index; index++; // 왼쪽 아래. vertices[index].position = XMFLOAT3(m_heightMap[index1].x, m_heightMap[index1].y, m_heightMap[index1].z); vertices[index].color = XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f); indices[index] = index; index++; // 왼쪽 아래. vertices[index].position = XMFLOAT3(m_heightMap[index1].x, m_heightMap[index1].y, m_heightMap[index1].z); vertices[index].color = XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f); indices[index] = index; index++; // 왼쪽 위. vertices[index].position = XMFLOAT3(m_heightMap[index3].x, m_heightMap[index3].y, m_heightMap[index3].z); vertices[index].color = XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f); indices[index] = index; index++; // 왼쪽 아래. vertices[index].position = XMFLOAT3(m_heightMap[index1].x, m_heightMap[index1].y, m_heightMap[index1].z); vertices[index].color = XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f); indices[index] = index; index++; // 오른쪽 위. vertices[index].position = XMFLOAT3(m_heightMap[index4].x, m_heightMap[index4].y, m_heightMap[index4].z); vertices[index].color = XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f); indices[index] = index; index++; // 오른쪽 위. vertices[index].position = XMFLOAT3(m_heightMap[index4].x, m_heightMap[index4].y, m_heightMap[index4].z); vertices[index].color = XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f); indices[index] = index; index++; // 오른쪽 아래. vertices[index].position = XMFLOAT3(m_heightMap[index2].x, m_heightMap[index2].y, m_heightMap[index2].z); vertices[index].color = XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f); indices[index] = index; index++; // 오른쪽 아래. vertices[index].position = XMFLOAT3(m_heightMap[index2].x, m_heightMap[index2].y, m_heightMap[index2].z); vertices[index].color = XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f); indices[index] = index; index++; // 왼쪽 아래. vertices[index].position = XMFLOAT3(m_heightMap[index1].x, m_heightMap[index1].y, m_heightMap[index1].z); vertices[index].color = XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f); indices[index] = index; index++; } } // 정적 정점 버퍼의 구조체를 설정한다. D3D11_BUFFER_DESC vertexBufferDesc; vertexBufferDesc.Usage = D3D11_USAGE_DEFAULT; vertexBufferDesc.ByteWidth = sizeof(VertexType) * m_vertexCount; vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER; vertexBufferDesc.CPUAccessFlags = 0; vertexBufferDesc.MiscFlags = 0; vertexBufferDesc.StructureByteStride = 0; // subresource 구조에 정점 데이터에 대한 포인터를 제공합니다. D3D11_SUBRESOURCE_DATA vertexData; vertexData.pSysMem = vertices; vertexData.SysMemPitch = 0; vertexData.SysMemSlicePitch = 0; // 이제 정점 버퍼를 만듭니다. if(FAILED(device->CreateBuffer(&vertexBufferDesc, &vertexData, &m_vertexBuffer))) { return false; } // 정적 인덱스 버퍼의 구조체를 설정합니다. D3D11_BUFFER_DESC indexBufferDesc; indexBufferDesc.Usage = D3D11_USAGE_DEFAULT; indexBufferDesc.ByteWidth = sizeof(unsigned long) * m_indexCount; indexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER; indexBufferDesc.CPUAccessFlags = 0; indexBufferDesc.MiscFlags = 0; indexBufferDesc.StructureByteStride = 0; // 하위 리소스 구조에 인덱스 데이터에 대한 포인터를 제공합니다. D3D11_SUBRESOURCE_DATA indexData; indexData.pSysMem = indices; indexData.SysMemPitch = 0; indexData.SysMemSlicePitch = 0; // 인덱스 버퍼를 만듭니다. if(FAILED(device->CreateBuffer(&indexBufferDesc, &indexData, &m_indexBuffer))) { return false; } // 이제 버퍼가 생성되고 로드된 배열을 해제하십시오. delete [] vertices; vertices = 0; delete [] indices; indices = 0; return true; } void TerrainClass::ShutdownBuffers() { // 인덱스 버퍼를 해제합니다. if(m_indexBuffer) { m_indexBuffer->Release(); m_indexBuffer = 0; } // 버텍스 버퍼를 해제합니다. if(m_vertexBuffer) { m_vertexBuffer->Release(); m_vertexBuffer = 0; } } void TerrainClass::RenderBuffers(ID3D11DeviceContext* deviceContext) { // 정점 버퍼 보폭 및 오프셋을 설정합니다. unsigned int stride = sizeof(VertexType); unsigned int offset = 0; // 렌더링 할 수 있도록 입력 어셈블러에서 정점 버퍼를 활성으로 설정합니다. deviceContext->IASetVertexBuffers(0, 1, &m_vertexBuffer, &stride, &offset); // 렌더링 할 수 있도록 입력 어셈블러에서 인덱스 버퍼를 활성으로 설정합니다. deviceContext->IASetIndexBuffer(m_indexBuffer, DXGI_FORMAT_R32_UINT, 0); // 이 버텍스 버퍼에서 렌더링되어야하는 프리미티브의 타입을 설정한다.이 경우 라인리스트이다. deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_LINELIST); } | cs |
이 듀토리얼에서는 ApplicationClass의 헤더가 변경되지 않았습니다. ApplicationClass.cpp 파일의 유일한 변경 사항은 지형 초기화 함수에 지형 높이 맵을 나타내는데 사용되는 비트맵의 파일 이름이 필요하다는 것입니다.
Applicationclass.cpp
1 2 3 4 5 6 7 8 9 | bool ApplicationClass::Initialize(HINSTANCE hinstance, HWND hwnd, int screenWidth, int screenHeight) { // ... // 지형 객체를 초기화 합니다. result = m_Terrain->Initialize(m_Direct3D->GetDevice(), "../Dx11Terrain_02/data/heightmap01.bmp"); // ... } | cs |
출력 화면
마치면서
우리는 이제 높이 맵을 로드하고 높이 맵 기반의 지형을 3D로 움직일 수 있습니다.
연습문제
1. 코드를 다시 컴파일하고 입력 키를 사용하여 지형을 따라 이동하십시오.
2. 자신만의 그레이 스케일 비트맵을 만들어 지형 엔진에 로드하여 해당 높이 맵을 3D로 봅니다.
3. .raw 형식으로 로드하도록 LoadHeightMap 함수를 변경하십시오.
소스코드
소스코드 : Dx11Terrain_02.zip
'DirectX 11 > Terrain' 카테고리의 다른 글
[DirectX 11] Terrain 06 - 높이 기반 이동 (0) | 2018.02.06 |
---|---|
[DirectX 11] Terrain 05 - 쿼드 트리 (0) | 2018.02.05 |
[DirectX11] Terrain 04 - 지형 텍스처 (0) | 2018.02.04 |
[DirectX11] Terrain 03 - 지형 조명 (0) | 2018.02.04 |
[DirectX11] Terrain 01 - 그리드 및 카메라 이동 (0) | 2018.02.01 |