수업


🧭 주제

  • 이 강의의 주제는 8비트 HeightMap 이미지를 기반으로 지형의 고도를 표현하는 3D Mesh 생성 기법입니다.
  • 이미지의 픽셀 데이터를 CPU에서 직접 파싱하고, 해당 값을 정점의 Y좌표로 보정하여 울퉁불퉁한 지형을 구성하는 실습입니다.
  • 이후에는 이 기법을 기반으로 Tessellation Shader, Collision, Tool 연동 등 다양한 시스템으로 확장할 수 있습니다.

📘 개념

개념설명
HeightMap흑백 이미지의 픽셀 밝기값(R)으로 지형의 높이를 표현하는 방식
픽셀 파싱이미지 파일을 메모리에 로드한 후, 픽셀 데이터를 접근해 정점 Y좌표로 변환
Grid GeometryXZ 평면 상의 정점 배열을 기반으로 한 격자형 Mesh
CPU 기반 보정높이맵 데이터를 기반으로 정점 위치를 CPU에서 수정한 후 버퍼에 반영
Shader 연동정점 데이터를 HLSL 셰이더로 넘겨 World/View/Projection 변환과 텍스처 샘플링 수행

📑 용어정리

용어의미
ScratchImageDirectXTex에서 사용하는 이미지 컨테이너. 픽셀 데이터 접근 가능
uint8* pixelBuffer픽셀 데이터가 담긴 버퍼 포인터 (한 픽셀 = 1바이트)
CreateGrid()주어진 너비와 높이에 맞춰 정점과 인덱스 배열 생성
VertexTextureDataposition + uv 정보를 가진 정점 구조체
DrawIndexed()인덱스 기반으로 GPU에서 정점 데이터를 그리는 함수
Wrap텍스처 좌표가 범위를 넘을 때 반복 적용하는 방식 (UV 처리)

🔍 코드 분석

✅ HeightMapDemo::Init()

1. HeightMap 이미지 로드

_heightMap = RESOURCES->Load<Texture>(L"Height", L"..\\Resources\\Textures\\Terrain\\height.png");
_texture = RESOURCES->Load<Texture>(L"Grass", L"..\\Resources\\Textures\\Terrain\\grass.jpg");
  • Load() 함수로 리소스를 메모리에 로딩
  • HeightMap은 8비트 grayscale로 저장된 height.png

2. 이미지 해상도 및 픽셀 데이터 획득

const int32 width = _heightMap->GetSize().x;
const int32 height = _heightMap->GetSize().y;

const DirectX::ScratchImage& info = _heightMap->GetInfo();
uint8* pixelBuffer = info.GetPixels();
  • 이미지 해상도는 이후 격자(Grid) 생성 기준
  • GetPixels()는 픽셀 배열 반환 (R 값만 있음)

3. Grid 생성 및 정점 높이 수정

_geometry = make_shared<Geometry<VertexTextureData>>();
GeometryHelper::CreateGrid(_geometry, width, height);
  • 이미지 크기와 동일한 격자 크기 생성
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;
        float h = pixelBuffer[idx] / 255.f * 25.f;
        v[idx].position.y = h;
    }
}
  • 픽셀 값을 정규화 후(0~1) 25를 곱해 고도로 변환
  • 1픽셀 = 1정점에 매핑하여 Y좌표 보정

4. 정점/인덱스 버퍼 생성

_vertexBuffer = make_shared<VertexBuffer>();
_vertexBuffer->Create(_geometry->GetVertices());

_indexBuffer = make_shared<IndexBuffer>();
_indexBuffer->Create(_geometry->GetIndices());
  • 보정된 정점과 인덱스를 GPU에 전달하기 위해 버퍼 생성

5. 카메라 위치 및 회전 설정

_camera = make_shared<GameObject>();
_camera->GetOrAddTransform();
_camera->AddComponent(make_shared<Camera>());
_camera->AddComponent(make_shared<CameraScript>());
_camera->GetTransform()->SetPosition(Vec3(0.f, 5.f, 0.f));
_camera->GetTransform()->SetRotation(Vec3(25.f, 0.f, 0.f));
  • 카메라를 위로 올려 지형을 내려다볼 수 있게 설정
  • 회전 각도는 X축으로 25도

✅ 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());
  • 셰이더에 월드/뷰/프로젝션 행렬과 텍스처 설정
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);
  • 버퍼 바인딩 후 실제 그리기 명령 수행

✅ Terrain.fx 셰이더

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;
}
  • 정점 → 월드 → 뷰 → 프로젝션 순으로 위치 변환
float4 PS(VertexOutput input) : SV_TARGET
{
    return Texture0.Sample(Sampler0, input.uv);
}
  • uv좌표로 텍스처 샘플링 후 픽셀 색상 반환
RasterizerState FillModeWireFrame {
    FillMode = Wireframe;
};
  • 와이어프레임 모드로 지형 구조 확인 가능

✅ 핵심

  1. HeightMap은 픽셀 밝기값을 높이값으로 사용하는 이미지 기반 지형 표현 방식이다.
  2. 픽셀 값은 0~255 정수형이며, uint8* 배열로 접근해 정규화한 후 Y값으로 사용한다.
  3. Grid는 이미지의 해상도만큼 생성되며, 각 정점은 1:1로 픽셀에 대응된다.
  4. Y좌표 보정은 CPU에서 최초 1회만 수행되며, 이후 버퍼에 저장되어 GPU가 렌더링을 수행한다.
  5. 셰이더에서는 position 변환과 텍스처 샘플링만 수행하며, 필요한 경우 와이어프레임 시각화가 가능하다.
  6. 이 시스템은 후속적으로 GPU Tessellation Shader, 충돌 처리, LOD 시스템, 툴 연동 기반 Terrain 편집기로 확장할 수 있다.

profile
李家네_공부방

0개의 댓글