DirectX11 Geometry · VertexData · GeometryHelper – 구조화와 적용

출처/범위: 제공된 블로그 글의 흐름과 코드에 충실하게 재구성. 과도한 확장 없이, 학습·강의·리팩토링 실전에 바로 쓰도록 Step-by-Step로 정리.


0) 학습 목표

  • 하드코딩된 정점/인덱스 데이터를 Geometry 템플릿으로 일반화한다.
  • 정점 구조 정의(VertexData)와 InputLayout 자동 매핑을 연결한다.
  • 기본 도형 생성 로직을 GeometryHelper로 분리한다.
  • shared_ptr 기반으로 Graphics / VertexBuffer / IndexBuffer / InputLayout / Geometry의 수명 관리 체계를 정착한다.

1) 빅피처 (의존·데이터 흐름)

Game
 ├─ Graphics                 // Device / DeviceContext 보유
 ├─ VertexBuffer / IndexBuffer / InputLayout (shared_ptr)
 ├─ Geometry<T>              // vertices[] + indices[] 캡슐화 (템플릿)
 └─ GeometryHelper           // 기본 도형 정적 생성 유틸(사각형 등)

[절차]
1) GeometryHelper가 Geometry<T> 내부 데이터를 채움(정점/인덱스)
2) Game::CreateGeometry()에서 Geometry 데이터로 VB/IB 생성
3) VertexData::<descs> 로 InputLayout 생성
4) Render: IA 바인딩 → DrawIndexed

2) 선행 작업 – 포인터/인클루드 정리 (shared_ptr 전환)

2.1 pch.h (또는 공용 헤더)에 STL 인클루드 추가

#include <memory>
#include <iostream>

2.2 Game 멤버 포인터를 shared_ptr로 전환

// Before
Graphics* _graphics;
VertexBuffer* _vertexBuffer;
IndexBuffer* _indexBuffer;
InputLayout* _inputLayout;

// After
std::shared_ptr<Graphics>     _graphics;
std::shared_ptr<VertexBuffer> _vertexBuffer;
std::shared_ptr<IndexBuffer>  _indexBuffer;
std::shared_ptr<InputLayout>  _inputLayout;

2.3 Init() 생성부 전환

// Before
_graphics    = new Graphics(hwnd);
_vertexBuffer= new VertexBuffer(_graphics->GetDevice());
_indexBuffer = new IndexBuffer(_graphics->GetDevice());
_inputLayout = new InputLayout(_graphics->GetDevice());

// After
_graphics     = std::make_shared<Graphics>(hwnd);
_vertexBuffer = std::make_shared<VertexBuffer>(_graphics->GetDevice());
_indexBuffer  = std::make_shared<IndexBuffer>(_graphics->GetDevice());
_inputLayout  = std::make_shared<InputLayout>(_graphics->GetDevice());

⚠️ 주의: shared_ptr는 순환 참조에 유의. (필요 시 weak_ptr 사용 검토)


3) Geometry 템플릿 – 기하학 데이터 캡슐화

3.1 의도

  • 물체별로 정점/인덱스 원본을 하드코딩 대신 클래스로 보유.
  • 정점 구조는 상황에 따라 달라지므로 템플릿화로 범용 처리.

3.2 Geometry.h

#pragma once

template<typename T>
class Geometry {
public:
    Geometry() {}
    ~Geometry() {}

    // Query
    uint32 GetVertexCount() { return static_cast<uint32>(_vertices.size()); }
    void*  GetVertexData()  { return _vertices.data(); }
    const std::vector<T>& GetVertices() { return _vertices; }

    uint32 GetIndexCount()  { return static_cast<uint32>(_indices.size()); }
    void*  GetIndexData()   { return _indices.data(); }
    const std::vector<uint32>& GetIndices() { return _indices; }

    // Edit: Vertices
    void AddVertex(const T& v) { _vertices.push_back(v); }
    void AddVertices(const std::vector<T>& vs) {
        _vertices.insert(_vertices.end(), vs.begin(), vs.end());
    }
    void SetVertices(const std::vector<T>& vs) { _vertices = vs; }

    // Edit: Indices
    void AddIndex(uint32 i) { _indices.push_back(i); }
    void AddIndices(const std::vector<uint32>& is) {
        _indices.insert(_indices.end(), is.begin(), is.end());
    }
    void SetIndices(const std::vector<uint32>& is) { _indices = is; }

private:
    std::vector<T> _vertices;
    std::vector<uint32> _indices;
};

💡 템플릿 클래스이므로 .h에서 구현 완료.


4) VertexData – 정점 구조와 InputLayout 매핑의 연결점

4.1 VertexData.h (두 가지 버전 예시)

#pragma once

struct VertexTextureData {
    Vec3 position = {0,0,0};
    Vec2 uv       = {0,0};
    static std::vector<D3D11_INPUT_ELEMENT_DESC> descs; // IL 매핑 정보
};

struct VertexColorData {
    Vec3  position = {0,0,0};
    Color color    = {0,0,0,0};
    static std::vector<D3D11_INPUT_ELEMENT_DESC> descs; // IL 매핑 정보
};

4.2 VertexData.cpp (descs 정의)

std::vector<D3D11_INPUT_ELEMENT_DESC> VertexTextureData::descs = {
    {"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0,                         D3D11_INPUT_PER_VERTEX_DATA, 0},
    {"TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT,    0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0},
};

std::vector<D3D11_INPUT_ELEMENT_DESC> VertexColorData::descs = {
    {"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0,                         D3D11_INPUT_PER_VERTEX_DATA, 0},
    {"COLOR",    0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0},
};

D3D11_APPEND_ALIGNED_ELEMENT오프셋 자동 정렬. 수동 12/16 계산을 피할 수 있다.

4.3 InputLayout 생성부의 변화

void Game::CreateInputLayout() {
    // 기존: 수동 layout 벡터 작성 → _inputLayout->Create(layout, _vsBlob)
    // 변경: 정점 타입의 정적 descs 사용
    _inputLayout->Create(VertexTextureData::descs, _vsBlob);
}

정점 타입을 VertexColorData로 바꾸면 VertexColorData::descs로 교체하면 된다.


5) GeometryHelper – 기본 도형 자동 생성

5.1 GeometryHelper.h

#pragma once

class GeometryHelper {
public:
    static void CreateRectangle(std::shared_ptr<Geometry<VertexTextureData>> geometry);
    static void CreateRectangle(std::shared_ptr<Geometry<VertexColorData>>   geometry, Color color);
};

5.2 GeometryHelper.cpp – 블로그의 사각형 로직 이전

void GeometryHelper::CreateRectangle(std::shared_ptr<Geometry<VertexTextureData>> geometry) {
    std::vector<VertexTextureData> v(4);
    v[0].position = Vec3(-0.5f,-0.5f,0.f); v[0].uv = Vec2(0.f,1.f);
    v[1].position = Vec3(-0.5f, 0.5f,0.f); v[1].uv = Vec2(0.f,0.f);
    v[2].position = Vec3( 0.5f,-0.5f,0.f); v[2].uv = Vec2(1.f,1.f);
    v[3].position = Vec3( 0.5f, 0.5f,0.f); v[3].uv = Vec2(1.f,0.f);
    geometry->SetVertices(v);

    std::vector<uint32> i = {0,1,2, 2,1,3};
    geometry->SetIndices(i);
}

void GeometryHelper::CreateRectangle(std::shared_ptr<Geometry<VertexColorData>> geometry, Color color) {
    std::vector<VertexColorData> v(4);
    v[0].position = Vec3(-0.5f,-0.5f,0.f); v[0].color = color;
    v[1].position = Vec3(-0.5f, 0.5f,0.f); v[1].color = color;
    v[2].position = Vec3( 0.5f,-0.5f,0.f); v[2].color = color;
    v[3].position = Vec3( 0.5f, 0.5f,0.f); v[3].color = color;
    geometry->SetVertices(v);

    std::vector<uint32> i = {0,1,2, 2,1,3};
    geometry->SetIndices(i);
}

6) Game 적용 – 선언 → 생성 → 채움 → 버퍼화 → 렌더

6.1 멤버 선언

std::shared_ptr<Geometry<VertexTextureData>> _geometry; // 텍스처 버전 예시

6.2 Init()

_geometry = std::make_shared<Geometry<VertexTextureData>>();

6.3 CreateGeometry()

// (1) CPU 데이터 채우기
GeometryHelper::CreateRectangle(_geometry);

// (2) VB/IB 생성
_vertexBuffer->Create(_geometry->GetVertices());
_indexBuffer->Create(_geometry->GetIndices());

6.4 CreateInputLayout()

_inputLayout->Create(VertexTextureData::descs, _vsBlob);

6.5 Render() – IA/Draw 수정 포인트

// stride는 실제 정점 타입 크기와 일치해야 함
uint32 stride = sizeof(VertexTextureData);
uint32 offset = 0;

// DrawIndexed의 카운트도 Geometry 기반으로 통일
_deviceContext->DrawIndexed(_geometry->GetIndexCount(), 0, 0);

✅ 기존의 sizeof(Vertex) / _indices.size() 사용 부분을 Geometry + 정점 타입 기준으로 변경.


7) 체크리스트 & 자주 나는 실수

  • pch.h에 <memory> 포함(링커/컴파일 에러 예방)
  • 모든 원시 포인터를 shared_ptr로 치환(Init/해제 일관성)
  • Geometry<T>.h에서 구현(템플릿 링크 에러 방지)
  • VertexData::<descs>HLSL 시그니처 시맨틱/포맷 일치
  • D3D11_APPEND_ALIGNED_ELEMENT 사용으로 오프셋 자동 정렬
  • sizeof(정점타입) 스트라이드 정확성 확인
  • DrawIndexed에 _geometry->GetIndexCount() 사용
  • 순환 참조 구조 점검(shared_ptr ↔ shared_ptr 지양)

8) 미니 퀴즈

  1. Geometry<T>를 템플릿으로 만든 핵심 이유는?
    → 다양한 정점 구조(텍스처/컬러/노멀 등)를 동일한 틀로 다루기 위해.

  2. VertexTextureData::descs에서 D3D11_APPEND_ALIGNED_ELEMENT의 역할은?
    → 앞 요소 크기를 기준으로 오프셋을 자동으로 계산/정렬.

  3. Render에서 sizeof(Vertex) 대신 sizeof(VertexTextureData)를 쓰는 이유는?
    → 실제 현재 사용 중인 정점 타입의 크기가 스트라이드가 되어야 하기 때문.

  4. DrawIndexed의 카운트를 _indices.size()에서 _geometry->GetIndexCount()로 바꾸는 장점은?
    데이터 소유·질의를 일원화하여 일관성/유지보수성 향상.


9) 실습 과제 (손코딩 권장)

  • A. 텍스처 → 컬러 버전 교체: Geometry<VertexColorData>로 선언 바꾸고, Helper의 컬러 버전 API 사용. IL/HLSL도 COLOR 시그니처로 변경.
  • B. InputLayout 자동화: 수동 layout 벡터 삭제, VertexData::<descs>만 사용하도록 정리.
  • C. 크래시 유도/해결: stride를 잘못 입력해 의도적으로 화면 오류를 만든 뒤, 원인(타입 불일치)을 찾아 수정.

10) 요약 메모 (한 장)

  • Geometry = 정점/인덱스 원본의 템플릿 컨테이너
  • VertexData = 정점 구조 정의 + InputLayout descs 보관
  • GeometryHelper = 도형 데이터 생성 유틸(정적 메서드)
  • Game 흐름 = Helper로 Geometry 채움 → VB/IB 생성 → VertexData::descs로 IL 생성 → DrawIndexed
  • 핵심 포인트 = shared_ptr 수명 관리 / 정점 타입과 IL의 1:1 매칭 / stride 정확성

11) 다음 단계 예고

  • 셰이더/머티리얼 래퍼링(상수버퍼·텍스처·샘플러 전달 체계)
  • 다양한 정점 버전(노멀/탠젠트 등) 추가 & VertexData::<descs> 확장
  • FBX 등 외부 모델 로딩 시 Geometry로 적재하는 파이프라인 연결
profile
李家네_공부방

0개의 댓글