Company
교육 철학

Vertex Buffer(정점 버퍼) 클래스

개요

Vertex Buffer(정점 버퍼)는 3D 그래픽스에서 정점 데이터를 GPU 메모리에 저장하는 핵심 리소스입니다. 정점의 위치, 색상, 법선, UV 좌표 등의 정보를 효율적으로 관리하고 렌더링 파이프라인에 전달합니다.
3D 정점 데이터들을 GPU 메모리 상에 할당해야 합니다. 이것을 저장하는 공간을 정점 버퍼라고 합니다. 버텍스 버퍼는 버텍스 셰이더에서 어트리뷰트로 사용할 수 있는 버텍스별 정보를 저장합니다. 버텍스 버퍼, 인덱스 버퍼, 상수 버퍼 등 모든 버퍼는 DirectX 11의 ID3D11Buffer 오브젝트입니다.

Part 1: Vertex Buffer의 개념

정점 데이터란?

정점(Vertex)
3D 공간의 한 점을 나타내는 데이터
위치, 색상, 법선, 텍스처 좌표 등의 속성 포함
여러 정점이 모여 삼각형(폴리곤)을 구성
정점 구조체 예시
struct Vertex { Vector3 position; // 위치 (x, y, z) Vector4 color; // 색상 (r, g, b, a) Vector3 normal; // 법선 벡터 Vector2 uv; // 텍스처 좌표 };
C++
복사

Vertex Buffer의 역할

GPU 메모리 관리
CPU 메모리의 정점 데이터를 GPU로 전송
GPU가 빠르게 접근할 수 있는 메모리에 저장
렌더링 시 반복적인 데이터 전송 방지
렌더링 파이프라인 통합
Input Assembler 스테이지에 바인딩
Vertex Shader로 데이터 전달
효율적인 병렬 처리 지원
버퍼 리소스의 종류
버텍스 버퍼는 리소스 개념 중 하나인 버퍼 리소스입니다. 즉, 버퍼 리소스들(텍스처 버퍼, 인덱스 버퍼, 상수 버퍼 등)은 모두 저와 같은 형태로 생성을 합니다.

Part 2: VertexBuffer 클래스 설계

클래스 구조

#pragma once #include "yaGraphicDevice_DX11.h" namespace ya::graphics { class VertexBuffer : public GpuBuffer { public: VertexBuffer(); ~VertexBuffer(); bool Create(const std::vector<Vertex>& vertexes); void Bind(); private: }; }
C++
복사

클래스 설계 분석

GpuBuffer 상속
class VertexBuffer : public GpuBuffer
C++
복사
공통 버퍼 기능 재사용
ID3D11Buffer 포인터 관리
D3D11_BUFFER_DESC 구조체 보관
RAII 패턴으로 리소스 관리
GpuBuffer 기본 클래스 구조
class GpuBuffer { protected: Microsoft::WRL::ComPtr<ID3D11Buffer> buffer; D3D11_BUFFER_DESC desc; public: ID3D11Buffer* GetBuffer() const { return buffer.Get(); } const D3D11_BUFFER_DESC& GetDesc() const { return desc; } };
C++
복사
public 인터페이스
Create(): 정점 데이터로부터 버퍼 생성
Bind(): 렌더링 파이프라인에 바인딩
설계 이점
단순하고 명확한 인터페이스
DirectX API 복잡성 은닉
에러 처리 통합
재사용성 향상

Part 3: Vertex Buffer 생성 과정

일반적인 생성 순서

버텍스 버퍼의 일반적인 생성 순서는 아래와 같습니다:
1.
버텍스 버퍼의 데이터 형식을 정의하는 구조체 선언
2.
생성할 버텍스 버퍼를 D3D11_BUFFER_DESC 구조체로 정의
3.
서브 리소스의 초기화 데이터로 D3D11_SUBRESOURCE_DATA 구조체를 정의
4.
ID3D11Device::CreateBuffer 함수 호출

VertexBuffer::Create 함수

정점 버퍼의 생성 함수
bool VertexBuffer::Create(const std::vector<Vertex>& vertexes) { desc.ByteWidth = sizeof(Vertex) * vertexes.size(); desc.BindFlags = D3D11_BIND_FLAG::D3D11_BIND_VERTEX_BUFFER; desc.Usage = D3D11_USAGE::D3D11_USAGE_DYNAMIC; desc.CPUAccessFlags = D3D11_CPU_ACCESS_FLAG::D3D11_CPU_ACCESS_WRITE; D3D11_SUBRESOURCE_DATA sub = { }; sub.pSysMem = vertexes.data(); if (!(GetDevice()->CreateBuffer(&desc, &sub, buffer.GetAddressOf()))) assert(NULL, "Create vertex buffer failed!"); return true; }
C++
복사

코드 분석

매개변수
const std::vector<Vertex>& vertexes
C++
복사
정점 데이터를 담은 벡터
const 참조로 전달하여 복사 비용 방지
std::vector 사용으로 동적 크기 지원
버퍼 설명 구조체 설정
ByteWidth (버퍼 크기)
desc.ByteWidth = sizeof(Vertex) * vertexes.size();
C++
복사
전체 정점 배열의 바이트 크기 계산
정점 구조체 크기 × 정점 개수
예: Vertex 크기 48 bytes × 3개 = 144 bytes
BindFlags (바인딩 플래그)
desc.BindFlags = D3D11_BIND_FLAG::D3D11_BIND_VERTEX_BUFFER;
C++
복사
이 버퍼가 정점 버퍼로 사용됨을 명시
Input Assembler 스테이지에서 사용
필수 플래그
Usage (사용 방식)
desc.Usage = D3D11_USAGE::D3D11_USAGE_DYNAMIC;
C++
복사
DYNAMIC Usage의 의미
CPU에서 자주 업데이트하는 버퍼
Map/Unmap을 통한 빠른 데이터 수정
동적 메쉬, 파티클 시스템 등에 적합
Usage 옵션 비교
CPUAccessFlags (CPU 접근 플래그)
desc.CPUAccessFlags = D3D11_CPU_ACCESS_FLAG::D3D11_CPU_ACCESS_WRITE;
C++
복사
CPU에서 버퍼에 쓰기 가능
Map을 통한 데이터 업데이트 허용
DYNAMIC Usage와 함께 사용
READ 플래그는 STAGING Usage와 함께 사용
서브리소스 데이터 설정
리소스는 복수의 서브리소스의 집합이라 할 수 있습니다. 말이 어려운 것이지, 서브리소스는 그저 리소스 내에 있는 실제 데이터를 말합니다. 리소스를 생성할 때는 D3D11_SUBRESOURCE_DATA 구조체를 정의하고 여기서 서브리소스의 내용을 초기화합니다.
D3D11_SUBRESOURCE_DATA sub = { }; sub.pSysMem = vertexes.data();
C++
복사
D3D11_SUBRESOURCE_DATA 구조체
struct D3D11_SUBRESOURCE_DATA { const void* pSysMem; // CPU 메모리의 데이터 포인터 UINT SysMemPitch; // 2D 텍스처 행의 바이트 크기 UINT SysMemSlicePitch; // 3D 텍스처 깊이 슬라이스 크기 };
C++
복사
정점 버퍼에서는
pSysMem: 정점 배열의 시작 주소
SysMemPitch, SysMemSlicePitch: 사용하지 않음 (0)
버퍼 생성
마지막으로 CreateBuffer를 호출해주면 되며, 이때 받아올 포인터로 ID3D11Buffer 인터페이스를 사용합니다.
if (!(GetDevice()->CreateBuffer(&desc, &sub, buffer.GetAddressOf()))) assert(NULL, "Create vertex buffer failed!");
C++
복사
CreateBuffer 내부 동작
1.
GPU 메모리 할당
2.
CPU 메모리에서 GPU 메모리로 데이터 복사
3.
ID3D11Buffer 인터페이스 생성
4.
성공 시 true, 실패 시 false 반환

Part 4: 정점 데이터 준비

정점 데이터 선언 예시

우선 버텍스 데이터를 선언합니다:
std::vector<graphics::Vertex> vertexes = {}; vertexes.resize(3); vertexes[0].pos = Vector3(0.0f, 0.5f, 0.0f); vertexes[0].color = Vector4(0.0f, 1.0f, 0.0f, 1.0f); vertexes[1].pos = Vector3(0.5f, -0.5f, 0.0f); vertexes[1].color = Vector4(1.0f, 0.0f, 0.0f, 1.0f); vertexes[2].pos = Vector3(-0.5f, -0.5f, 0.0f); vertexes[2].color = Vector4(0.0f, 0.0f, 1.0f, 1.0f);
C++
복사

코드 분석

삼각형 정점 구성
NDC 좌표계 (Normalized Device Coordinates)
x축: -1 (왼쪽) ~ +1 (오른쪽)
y축: -1 (아래) ~ +1 (위)
z축: 0 (가까움) ~ 1 (멀음)
색상 값
RGBA 포맷 (0.0 ~ 1.0 범위)
v0: 녹색 (0, 1, 0, 1)
v1: 빨간색 (1, 0, 0, 1)
v2: 파란색 (0, 0, 1, 1)

다양한 도형 예시

사각형 (2개의 삼각형)
std::vector<Vertex> vertices; vertices.resize(4); // 좌하단 vertices[0].position = Vector3(-0.5f, -0.5f, 0.0f); vertices[0].uv = Vector2(0.0f, 1.0f); // 우하단 vertices[1].position = Vector3(0.5f, -0.5f, 0.0f); vertices[1].uv = Vector2(1.0f, 1.0f); // 우상단 vertices[2].position = Vector3(0.5f, 0.5f, 0.0f); vertices[2].uv = Vector2(1.0f, 0.0f); // 좌상단 vertices[3].position = Vector3(-0.5f, 0.5f, 0.0f); vertices[3].uv = Vector2(0.0f, 0.0f);
C++
복사
원 (삼각형 부채꼴)
std::vector<Vertex> CreateCircle(float radius, int segments) { std::vector<Vertex> vertices; vertices.resize(segments + 2); // 중심점 vertices[0].position = Vector3(0.0f, 0.0f, 0.0f); // 둘레 점들 float angleStep = 2.0f * 3.14159f / segments; for (int i = 0; i <= segments; i++) { float angle = i * angleStep; vertices[i + 1].position = Vector3( radius * cos(angle), radius * sin(angle), 0.0f ); } return vertices; }
C++
복사

Part 5: D3D11_BUFFER_DESC 상세

버퍼 설명 구조체

생성할 버텍스 버퍼를 D3D11_BUFFER_DESC 구조체로 정의하고, 디폴트 사용, 버텍스 버퍼 사이즈, 파이프라인에 버텍스 버퍼로써 바인드, CPU 액세스 하지 않음 등을 설정합니다:
// 버텍스 버퍼 정의 D3D11_BUFFER_DESC xyzBufferDesc; xyzBufferDesc.Usage = D3D11_USAGE_DEFAULT; // 디폴트로 사용 xyzBufferDesc.ByteWidth = sizeof(SimpleVertex) * 3; // 버텍스 3개 xyzBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER; // 버텍스 버퍼로써 바인드 xyzBufferDesc.CPUAccessFlags = 0; // CPU 액세스 하지 않음 xyzBufferDesc.MiscFlags = 0; xyzBufferDesc.StructureByteStride = 0;
C++
복사

각 필드 상세 설명

Usage (사용 방식)
D3D11_USAGE_DEFAULT // GPU 읽기/쓰기, 일반적 용도 D3D11_USAGE_DYNAMIC // CPU 쓰기, GPU 읽기, 자주 업데이트 D3D11_USAGE_IMMUTABLE // GPU 읽기만, 생성 후 변경 불가 D3D11_USAGE_STAGING // CPU 읽기/쓰기, GPU 복사용
C++
복사
ByteWidth (버퍼 크기)
바이트 단위 크기
정점 구조체 크기 × 정점 개수
반드시 0보다 커야 함
BindFlags (바인딩 플래그)
D3D11_BIND_VERTEX_BUFFER // 정점 버퍼 D3D11_BIND_INDEX_BUFFER // 인덱스 버퍼 D3D11_BIND_CONSTANT_BUFFER // 상수 버퍼 D3D11_BIND_SHADER_RESOURCE // 셰이더 리소스 D3D11_BIND_STREAM_OUTPUT // Stream Output D3D11_BIND_RENDER_TARGET // Render Target D3D11_BIND_DEPTH_STENCIL // Depth Stencil
C++
복사
CPUAccessFlags (CPU 접근 플래그)
0 // CPU 접근 불가 D3D11_CPU_ACCESS_WRITE // CPU 쓰기 가능 D3D11_CPU_ACCESS_READ // CPU 읽기 가능
C++
복사
MiscFlags (기타 플래그)
0 // 기본값 D3D11_RESOURCE_MISC_GENERATE_MIPS // 밉맵 자동 생성 D3D11_RESOURCE_MISC_SHARED // 공유 리소스 D3D11_RESOURCE_MISC_BUFFER_ALLOW_RAW_VIEWS // Raw 뷰 허용
C++
복사
StructureByteStride
Structured Buffer에서 각 요소의 크기
일반 정점 버퍼에서는 0

Part 6: Vertex Buffer 바인딩

VertexBuffer::Bind 함수

해당 정점 버퍼를 파이프라인에 묶어주는 함수
void VertexBuffer::Bind() { UINT offset = 0; UINT vectexSize = sizeof(Vertex); GetDevice()->BindVertexBuffer(0, 1, buffer.GetAddressOf(), &vectexSize, &offset); }
C++
복사

코드 분석

매개변수 준비
UINT offset = 0; // 버퍼 시작 오프셋 (바이트) UINT vectexSize = sizeof(Vertex); // 정점 크기 (stride)
C++
복사
offset (오프셋)
버퍼 시작부터 첫 정점까지의 바이트 수
일반적으로 0 (버퍼 처음부터 사용)
하나의 버퍼에 여러 메쉬가 있을 때 활용
vectexSize (stride, 보폭)
한 정점에서 다음 정점까지의 바이트 수
sizeof(Vertex): 정점 구조체 크기
GPU가 정점 데이터를 순차적으로 읽는 데 사용
BindVertexBuffer 호출
GetDevice()->BindVertexBuffer(0, 1, buffer.GetAddressOf(), &vectexSize, &offset);
C++
복사
내부 구현 (GraphicDevice 클래스)
void GraphicDevice_DX11::BindVertexBuffer( UINT startSlot, UINT numBuffers, ID3D11Buffer* const* ppVertexBuffers, const UINT* pStrides, const UINT* pOffsets) { mContext->IASetVertexBuffers( startSlot, numBuffers, ppVertexBuffers, pStrides, pOffsets ); }
C++
복사
매개변수 설명
startSlot: 시작 슬롯 번호 (0~15)
numBuffers: 바인딩할 버퍼 개수
ppVertexBuffers: 버퍼 포인터 배열
pStrides: 정점 크기 배열
pOffsets: 오프셋 배열

다중 정점 버퍼 바인딩

인터리브 vs 평면 버퍼
인터리브 (Interleaved)
struct Vertex { Vector3 position; // 12 bytes Vector3 normal; // 12 bytes Vector2 uv; // 8 bytes }; // 하나의 버퍼에 모든 속성 포함
C++
복사
평면 (Planar)
// 위치 버퍼 (슬롯 0) std::vector<Vector3> positions; // 법선 버퍼 (슬롯 1) std::vector<Vector3> normals; // UV 버퍼 (슬롯 2) std::vector<Vector2> uvs; // 여러 버퍼 동시 바인딩 ID3D11Buffer* buffers[] = { posBuffer, normalBuffer, uvBuffer }; UINT strides[] = { sizeof(Vector3), sizeof(Vector3), sizeof(Vector2) }; UINT offsets[] = { 0, 0, 0 }; context->IASetVertexBuffers(0, 3, buffers, strides, offsets);
C++
복사

Part 7: 사용 예시

기본 사용 패턴

// 1. 정점 데이터 준비 std::vector<Vertex> vertices = CreateTriangle(); // 2. Vertex Buffer 생성 VertexBuffer vertexBuffer; vertexBuffer.Create(vertices); // 3. 렌더링 루프 void Render() { // 버퍼 바인딩 vertexBuffer.Bind(); // Draw Call UINT vertexCount = vertices.size(); GetDevice()->GetContext()->Draw(vertexCount, 0); }
C++
복사

Mesh 클래스 통합

class Mesh { private: VertexBuffer mVertexBuffer; std::vector<Vertex> mVertices; public: void Create(const std::vector<Vertex>& vertices) { mVertices = vertices; mVertexBuffer.Create(vertices); } void Bind() { mVertexBuffer.Bind(); } void Render() { Bind(); GetDevice()->GetContext()->Draw(mVertices.size(), 0); } };
C++
복사

동적 정점 버퍼 업데이트

DYNAMIC Usage 활용
void UpdateVertexBuffer(VertexBuffer& vb, const std::vector<Vertex>& newData) { D3D11_MAPPED_SUBRESOURCE mappedResource; // 버퍼를 CPU 메모리에 매핑 GetDevice()->GetContext()->Map( vb.GetBuffer(), 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource ); // 데이터 복사 memcpy(mappedResource.pData, newData.data(), sizeof(Vertex) * newData.size()); // 매핑 해제 GetDevice()->GetContext()->Unmap(vb.GetBuffer(), 0); }
C++
복사
사용 예: 파티클 시스템
class ParticleSystem { VertexBuffer mVertexBuffer; std::vector<Vertex> mParticles; void Update(float deltaTime) { // 파티클 위치 업데이트 for (auto& particle : mParticles) { particle.position += particle.velocity * deltaTime; } // 버퍼 업데이트 UpdateVertexBuffer(mVertexBuffer, mParticles); } void Render() { mVertexBuffer.Bind(); GetDevice()->GetContext()->Draw(mParticles.size(), 0); } };
C++
복사

Part 8: 최적화 및 모범 사례

Usage 선택 가이드

메모리 레이아웃 최적화

정점 구조체 패딩
// 나쁜 예: 패딩으로 메모리 낭비 struct BadVertex { float x, y, z; // 12 bytes char flag; // 1 byte // 3 bytes 패딩! float u, v; // 8 bytes // 총 24 bytes }; // 좋은 예: 정렬된 구조 struct GoodVertex { float x, y, z; // 12 bytes float u, v; // 8 bytes char flag; // 1 byte char padding[3]; // 명시적 패딩 // 총 24 bytes (동일하지만 명시적) }; // 더 좋은 예: 패딩 제거 struct BetterVertex { Vector3 position; // 12 bytes Vector2 uv; // 8 bytes // 총 20 bytes (패딩 없음) };
C++
복사

배칭 (Batching)

인스턴싱 (Instancing)
// 같은 메쉬를 여러 번 그릴 때 struct InstanceData { Matrix world; }; // Instance Buffer 생성 VertexBuffer instanceBuffer; std::vector<InstanceData> instances; // ... 인스턴스 데이터 채우기 instanceBuffer.Create(instances); // 렌더링 meshVertexBuffer.Bind(); // 슬롯 0 instanceBuffer.Bind(); // 슬롯 1 GetDevice()->GetContext()->DrawInstanced( meshVertexCount, instances.size(), 0, 0 );
C++
복사

에러 처리

개선된 Create 함수
bool VertexBuffer::Create(const std::vector<Vertex>& vertexes) { // 유효성 검사 if (vertexes.empty()) { LOG_WARNING("Vertex buffer creation with empty vertices"); return false; } // 크기 제한 확인 size_t maxSize = 256 * 1024 * 1024; // 256 MB if (sizeof(Vertex) * vertexes.size() > maxSize) { LOG_ERROR("Vertex buffer size exceeds maximum: %zu bytes", sizeof(Vertex) * vertexes.size()); return false; } desc.ByteWidth = sizeof(Vertex) * vertexes.size(); desc.BindFlags = D3D11_BIND_VERTEX_BUFFER; desc.Usage = D3D11_USAGE_DYNAMIC; desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; D3D11_SUBRESOURCE_DATA sub = {}; sub.pSysMem = vertexes.data(); HRESULT hr = GetDevice()->CreateBuffer(&desc, &sub, buffer.GetAddressOf()); if (FAILED(hr)) { LOG_ERROR("Failed to create vertex buffer. HRESULT: 0x%08X", hr); return false; } return true; }
C++
복사

결론

핵심 요약

Vertex Buffer
정점 데이터를 GPU 메모리에 저장
ID3D11Buffer 인터페이스로 관리
Input Assembler 스테이지에서 사용
렌더링 파이프라인의 시작점
클래스 설계
GpuBuffer 상속으로 코드 재사용
간단한 Create/Bind 인터페이스
RAII 패턴으로 리소스 관리
DirectX API 복잡성 은닉
버퍼 생성
D3D11_BUFFER_DESC로 버퍼 설명
D3D11_SUBRESOURCE_DATA로 초기 데이터 전달
Usage에 따라 적절한 플래그 선택
CreateBuffer로 GPU 메모리 할당

실전 적용 가이드

정적 메쉬
1.
USAGE_IMMUTABLE 사용
2.
생성 시 한 번만 데이터 전달
3.
CPU 접근 불필요
4.
최고 성능
동적 메쉬
1.
USAGE_DYNAMIC 사용
2.
CPU_ACCESS_WRITE 플래그
3.
Map/Unmap으로 업데이트
4.
매 프레임 변경 가능
최적화
1.
정점 구조체 패딩 최소화
2.
적절한 Usage 선택
3.
배칭과 인스턴싱 활용
4.
에러 처리 강화
Vertex Buffer는 3D 렌더링의 가장 기본적인 구성 요소이며, 효율적인 관리는 렌더링 성능에 직접적인 영향을 미칩니다. 적절한 설계와 최적화를 통해 고성능 그래픽 시스템을 구축할 수 있습니다.