-
[DirectX11] HeightMap을 활용한 지형 생성2024년 03월 05일
- 유니얼
-
작성자
-
2024.03.05.:46
728x90DirectX 11로 게임 엔진 아키텍처 만들기
3D 게임 개발 및 시각화 프로젝트에서 사실적인 지형을 생성하는 것은 몰입감을 높이는 중요한 요소입니다. DirectX11과 HeightMap을 활용하면, 효율적으로 다양한 지형을 구현할 수 있습니다. 이번 글에서는 HeightMap 기반 지형 생성 프로세스를 간단한 예제 코드를 통해 설명합니다.
참고강의 링크:
HeightMap이란?
HeightMap은 3차원 풍경을 모델링할 때 자주 사용되는 기술로, 각 지점의 높이 정보를 담고 있는 2차원 이미지입니다. 흑백 이미지에서 각 픽셀의 밝기가 해당 지점의 높이를 나타냅니다. 밝은 부분이 높은 지역을, 어두운 부분이 낮은 지역을 나타내며, 이를 통해 복잡한 지형을 상대적으로 적은 데이터로 표현할 수 있습니다.
HeightMap의 활용
HeightMap은 주로 지형 생성에 사용됩니다. 간단한 2차원 그리드(mesh) 위에 HeightMap을 적용하여, 각 정점(vertex)의 높이를 조절함으로써 3차원 지형을 만들어냅니다. 이 기법은 자연스러운 지형 모델링뿐만 아니라 도시의 건물 배치와 같은 다양한 3D 모델링 작업에도 활용됩니다.
DirectX11에서의 HeightMap 사용
DirectX11에서 HeightMap을 사용하기 위해 먼저 HeightMap 이미지를 로드하고, 이를 기반으로 각 정점의 높이 정보를 설정해야 합니다. 다음은 HeightMap을 적용하여 지형을 생성하는 과정을 간략하게 설명한 것입니다:
1, HeightMap 로드
HeightMap 이미지는 주로 흑백 이미지로 저장되며, DirectX11에서는 DirectXTK 라이브러리를 통해 쉽게 로드할 수 있습니다. 이미지의 각 픽셀 값을 읽어 높이 정보로 변환합니다.
2, 지형 메쉬 생성
2차원 그리드 형태의 지형 메쉬를 생성합니다. 이때, 메쉬의 각 정점에 HeightMap에서 얻은 높이 정보를 적용하여 3차원 지형을 형성합니다.
3, 정점 셰이더(Vertex Shader) 적용
정점 셰이더에서는 각 정점의 위치를 HeightMap에 기반한 높이로 조정합니다. 이 과정을 통해 실제 3D 지형이 구성됩니다.
4, 텍스처와 조명 적용
생성된 지형에 텍스처를 매핑하고 조명 효과를 추가하여, 더욱 사실적인 지형을 완성합니다. HeightMap은 지형의 형태만 결정하기 때문에, 외관을 개선하기 위해서는 추가적인 텍스처 매핑이 필요합니다.
예제: HeightMap을 이용한 지형 생성
다음은 DirectX11에서 HeightMap을 이용해 지형을 생성하는 과정을 단계별로 설명한 코드입니다.
Terrain.fx
// 3D 오브젝트의 위치, 방향, 크기 등을 결정하는 변환 행렬들을 정의합니다. matrix World; matrix View; matrix Projection; // 쉐이더에서 사용할 텍스처를 정의합니다. Texture2D Texture0; // 버텍스 쉐이더로 넘어오는 입력 데이터 구조체입니다. 각 버텍스의 위치와 텍스처 좌표를 포함합니다. struct VertexInput { float4 position : POSITION; // 3D 공간에서의 버텍스 위치 float2 uv : TEXCOORD; // 텍스처 좌표 }; // 버텍스 쉐이더의 출력 데이터 구조체입니다. 처리된 버텍스 위치와 텍스처 좌표를 포함합니다. struct VertexOutput { float4 position : SV_POSITION; // 스크린 공간에서의 버텍스 위치 float2 uv : TEXCOORD; // 텍스처 좌표 }; // 버텍스 쉐이더 함수입니다. 3D 오브젝트의 각 버텍스 위치를 처리합니다. VertexOutput VS(VertexInput input) { VertexOutput output; // 입력된 버텍스 위치를 월드, 뷰, 프로젝션 행렬을 사용하여 변환합니다. output.position = mul(input.position, World); output.position = mul(output.position, View); output.position = mul(output.position, Projection); // 입력된 텍스처 좌표를 출력 데이터에 그대로 전달합니다. output.uv = input.uv; return output; } // 텍스처 샘플링을 위한 샘플러 상태를 정의합니다. 여기서는 UV 좌표가 범위를 벗어날 경우 텍스처를 반복합니다. SamplerState Sampler0 { AddressU = Wrap; // U 좌표가 1을 초과할 경우 텍스처를 반복 AddressV = Wrap; // V 좌표가 1을 초과할 경우 텍스처를 반복 }; // 픽셀 쉐이더 함수입니다. 최종적으로 화면에 렌더링될 픽셀의 색상을 결정합니다. float4 PS(VertexOutput input) : SV_TARGET { // 정의된 샘플러를 사용하여 텍스처에서 색상 값을 샘플링하고 반환합니다. return Texture0.Sample(Sampler0, input.uv); } // 래스터라이저 상태를 정의합니다. 이 예제에서는 와이어프레임 모드를 설정합니다. RasterizerState FillModeWireFrame { FillMode = Wireframe; // 렌더링 모드를 와이어프레임으로 설정 }; // 렌더링 기법을 정의합니다. 이 기법은 두 개의 패스를 사용합니다. technique11 T0 { pass P0 { // 첫 번째 패스에서는 버텍스 쉐이더와 픽셀 쉐이더를 설정합니다. SetVertexShader(CompileShader(vs_5_0, VS())); SetPixelShader(CompileShader(ps_5_0, PS())); } pass P1 { // 두 번째 패스에서는 래스터라이저 상태를 와이어프레임 모드로 설정하고, // 동일한 버텍스 쉐이더와 픽셀 쉐이더를 재사용합니다. SetRasterizerState(FillModeWireFrame); SetVertexShader(CompileShader(vs_5_0, VS())); SetPixelShader(CompileShader(ps_5_0, PS())); } };
프로젝트 호출
생성한 Sampler는 픽셀 셰이더에서 사용됩니다. ID3D11DeviceContext::PSSetSamplers() 함수를 호출하여 픽셀 셰이더의 Sampler 슬롯에 바인딩합니다. 이렇게 하면 픽셀 셰이더에서 텍스처를 샘플링할 때 해당 Sampler의 설정을 사용하게 됩니다.
HeightMapDemo.h
#pragma once #include "IExecute.h" #include "Geometry.h" #include "GameObject.h" class HeightMapDemo : public IExecute { public: void Init() override; void Update() override; void Render() override; shared_ptr<Shader> _shader; //Object shared_ptr<Geometry<VertexTextureData>> _geometry; shared_ptr<VertexBuffer> _vertexBuffer; shared_ptr<IndexBuffer> _indexBuffer; Vec3 _translation = Vec3(0.f, 0.f, 0.f); Matrix _world = Matrix::Identity; //Camera shared_ptr<GameObject> _camera; shared_ptr<Texture> _heightMap; shared_ptr<Texture> _texture; };
HeightMapDemo.cpp
#include "pch.h" // 프리컴파일 헤더 #include "07. HeightMapDemo.h" // 이 클래스의 헤더 파일 #include "GeometryHelper.h" // 지오메트리 생성을 돕는 함수들이 있는 헤더 파일 #include "Camera.h" // 카메라 구현 헤더 파일 #include "CameraScript.h" // 카메라 스크립트(조작 등) 헤더 파일 #include "Transform.h" // 변환(위치, 회전, 크기 조정) 헤더 파일 #include "Texture.h" // 텍스처 관련 헤더 파일 #include "ResourceBase.h" // 리소스 관리 기본 클래스 헤더 파일 void HeightMapDemo::Init() { // 쉐이더와 텍스처 로드 _shader = make_shared<Shader>(L"06. Terrain.fx"); _heightMap = RESOURCES->Load<Texture>(L"Height", L"..\\Resources\\Texture\\Terrain\\height.png"); _texture = RESOURCES->Load<Texture>(L"Grass", L"..\\Resources\\Texture\\Terrain\\grass.jpg"); // 높이 맵의 크기 정보를 가져옴 const int32 width = _heightMap->GetSize().x; const int32 height = _heightMap->GetSize().y; // 픽셀 버퍼에서 높이 데이터를 가져옴 const DirectX::ScratchImage& info = _heightMap->GetInfo(); uint8* pixelBuffer = info.GetPixels(); // 지형 메시 생성 _geometry = make_shared<Geometry<VertexTextureData>>(); GeometryHelper::CreateGrid(_geometry, width, height); // CPU에서 지형의 높이를 설정 { vector<VertexTextureData>& v = const_cast<vector<VertexTextureData>&>(_geometry->GetVertices()); for (int32 z = 0; z < height; z++) { for (int32 x = 0; x < width; x++) { int32 idx = width * z + x; uint8 height = pixelBuffer[idx] / 255.f * 25.f; // 픽셀 값에 따라 높이 결정 v[idx].position.y = height; // 높이 값 설정 } } } // 버텍스 버퍼와 인덱스 버퍼 생성 및 데이터 설정 _vertexBuffer = make_shared<VertexBuffer>(); _vertexBuffer->Create(_geometry->GetVertices()); _indexBuffer = make_shared<IndexBuffer>(); _indexBuffer->Create(_geometry->GetIndices()); // 카메라 설정 _camera = make_shared<GameObject>(); _camera->GetOrAddTransform(); _camera->AddComponent(make_shared<Camera>()); _camera->AddComponent(make_shared<CameraScript>()); // 카메라 초기 위치 및 회전 설정 _camera->GetTransform()->SetWorldPosition(Vec3(0.f, 5.f, 0.f)); _camera->GetTransform()->SetWorldRotation(Vec3(25.f, 0.f, 0.f)); } void HeightMapDemo::Update() { _camera->Update(); // 카메라 업데이트 } void HeightMapDemo::Render() { // 쉐이더에 변환 행렬과 텍스처 리소스 바인딩 _shader->GetMatrix("World")->SetMatrix((float*)&_world); _shader->GetMatrix("View")->SetMatrix((float*)&Camera::S_MatView); _shader->GetMatrix("Projection")->SetMatrix((float*)&Camera::S_MatProjection); _shader->GetSRV("Texture0")->SetResource(_texture->GetComPtr().Get()); // 버텍스 버퍼와 인덱스 버퍼를 입력 어셈블러에 바인딩 uint32 stride = _vertexBuffer->GetStride(); uint32 offset = _vertexBuffer->GetOffset(); DC->IASetVertexBuffers(0, 1, _vertexBuffer->GetComPtr().GetAddressOf(), &stride, &offset); DC->IASetIndexBuffer(_indexBuffer->GetComPtr().Get(), DXGI_FORMAT_R32_UINT, 0); // 지형 메시를 렌더링 _shader->DrawIndexed(0, 0, _indexBuffer->GetCount(), 0, 0); }
결론
HeightMap을 활용한 지형 생성은 복잡한 자연 지형을 실시간으로 렌더링하는 데 매우 유용합니다. DirectX11을 사용하면, 이러한 프로세스를 통해 생성된 지형에 고급 렌더링 기술을 적용하여 보다 사실적인 3D 환경을 구현할 수 있습니다.
반응형다음글이전글이전 글이 없습니다.댓글