4 minutes
❎ Direct3D 11 Graphics Pipeline - 10. 버퍼
1. Vertex Buffer(정점 버퍼)
컴퓨터 그래픽스에서 3D 객체를 구성하는 정점(Vertex) 데이터를 저장하는 메모리 공간
- 쉽게 말해, 3D 객체(정확히는 Primitive)의 뼈대를 정의하는 점들의 집합
정점 데이터를 GPU에 효율적으로 전달하기 위해 사용하는 메모리 공간으로, 프레임 버퍼가 GPU가 화면에 전달하기 위한 공간이였다면 정점 버퍼는 CPU가 GPU에 전달하기 위한 공간
Vertex
3D 그래픽스에서 점(Point)를 의미
3D 객체는 삼각형 Polygon의 집합으로 표현되며, 이 삼각형의 꼭짓점이 바로 Vertex
다음과 같은 정보들을 포함함
위치(Position): 점의 3D 좌표(x, y, z)
법선(Normal): 표면의 방향(조명 계산 등에 사용)
- Polygon이 아니라 Vertex의 Normal
텍스처 좌표(Texture Coordinates): 텍스처 매핑을 위한 좌표(u, v)
색상(Color): 정점의 색상 정보(RGB + A)
Vertex Buffer의 동작 과정
정점 버퍼 생성: CPU에서 삼각형의 세 꼭짓점 좌표 등을 준비하여 정점 버퍼에 저장
복잡한 형태의 모델은 모든 정점을 하드코딩하기 어려우니, 실제로는 Blender / Maya 등으로 3D 모델을 생성 후 export한 결과 파일을 정점 버퍼로 로드함
ID3D11Device::CreateBuffer()등으로 버퍼 생성
셰이더와 연결: 정점 버퍼를 GPU에서 돌아가는 프로그램인 셰이더와 연결함
- 물론, VS로 들어가기 전 정점 버퍼의 내용을 셰이더가 이해할 수 있도록 IA 스테이지에서 Input Layout등을 설정함
GPU에서 렌더링
코드 예제
struct Vertex {
float x, y, z;
float r, g, b;
};
Vertex vertices[] = {
{ 0.0f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f }, // Red
{ 0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f }, // Green
{-0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f } // Blue
};
// 정점 버퍼 생성
D3D11_BUFFER_DESC bufferDesc = {};
bufferDesc.Usage = D3D11_USAGE_DEFAULT; // GPU 읽기 / 쓰기 허용
bufferDesc.ByteWidth = sizeof(vertices);
bufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER; // 이 버퍼는 정점 버퍼임을 바인딩
// 실제 데이터는 subresource를 통해 넘겨주기
D3D11_SUBRESOURCE_DATA initData = {};
initData.pSysMem = vertices; // CPU 메모리의 vertices가 가리키는 데이터를 그대로 복사하여 초기화
ID3D11Buffer* vertexBuffer = nullptr;
device->CreateBuffer(&bufferDesc, &initData, &vertexBuffer); // GPU 접근 가능 메모리로 정점 데이터 복사(subresource가 안주어지면 접근 가능 메모리만 생성)
2. Index Buffer(인덱스 버퍼)
정점을 효율적으로 재사용하기 위해 사용하는, 정점의 인덱스를 통해 삼각형을 구성하는 데이터 구조
대부분의 3D 객체는 여러 삼각형이 동일한 정점을 공유함
예를 들어, 정육면체를 표현하기 위해서는 12개의 삼각형을 위해 36개의 정점이 필요하나, 실제 고유한 정점은 단 8개 뿐임
인덱스 버퍼를 사용하면 8개의 고유한 정점들의 인덱스만 반복 사용하여 정점 데이터 크기를 줄일 수 있음
장점
정점 재사용: 동일한 정점을 여러 삼각형에 반복 사용 가능
메모리 절약: 동일한 정점을 중복 저장하지 않아도 되어 GPU 메모리 절약 가능
렌더링 최적화: GPU가 삼각형을 효율적으로 그릴 수 있도록 도움
- VS 실행 횟수 감소, 메모리 접근 감소(캐시 친화), 대형 mesh의 효율적 처리 등
유지보수 용이: 3D 모델의 정점 데이터와 삼각형 구성 정보를 분리하여 관리하기 쉬워짐
동작 원리
정점 버퍼에는 고유한 정점 정보들만 담음
인덱스 버퍼에 각 Primitive를 구성하는 정점의 인덱스를 순서대로 기록
GPU는 인덱스 버퍼를 참조하여 삼각형을 구성할 정점을 가져와 렌더링
코드 예제
struct Vertex {
float x, y, z;
};
// 인덱스 버퍼를 사용할 때 정점 버퍼는 그냥 고유한 정점 데이터들의 집합(순서는 크게 중요하지 않음)
Vertex vertices[] = {
{-1.0f, -1.0f, -1.0f}, {1.0f, -1.0f, -1.0f}, {1.0f, 1.0f, -1.0f},
{-1.0f, 1.0f, -1.0f}, {-1.0f, -1.0f, 1.0f}, {1.0f, -1.0f, 1.0f},
{1.0f, 1.0f, 1.0f}, {-1.0f, 1.0f, 1.0f}
};
// 정육면체 인덱스 데이터
UINT indices[] = {
0, 1, 2, 0, 2, 3, // 앞면
4, 5, 6, 4, 6, 7, // 뒷면
0, 1, 5, 0, 5, 4, // 아래
2, 3, 7, 2, 7, 6, // 위
0, 3, 7, 0, 7, 4, // 왼쪽
1, 2, 6, 1, 6, 5 // 오른쪽
};
// 정점 버퍼는 별도로 생성(여기서는 생략)
// 인덱스 버퍼 생성
D3D11_BUFFER_DESC indexBufferDesc = {};
indexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
indexBufferDesc.ByteWidth = sizeof(indices);
indexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER;
D3D11_SUBRESOURCE_DATA initData = {};
initData.pSysMem = indices;
ID3D11Buffer* indexBuffer = nullptr;
device->CreateBuffer(&indexBufferDesc, &initData, &indexBuffer);
3. Constant Buffer(상수 버퍼)
셰이더에 공유 데이터를 빠르고 효율적으로 전달하기 위해 사용하는 메모리 공간
여러 셰이더 단계가 공통으로 사용하거나, 특정 셰이더가 일정 기간 동안 동일하게 사용하는 읽기 전용 파라미터 묶음을 GPU에 전달하기 위한 목적
헷갈리면 안되는게, GPU(셰이더)에서 읽기 전용인 것이지 CPU에서 읽기 전용인 것이 아님!
역할
데이터 전달: 월드 변환 행렬(MVP 행렬등)등 CPU가 계산한 데이터를 GPU로 전달함
셰이더와 데이터 공유: 셰이더는 상수 버퍼를 통해 필요한 데이터를 읽어오며, 상수 버퍼 안의 데이터는 셰이더에서 읽기 전용임
성능 최적화: 상수 버퍼 사용은 GPU 메모리를 효율적으로 관리하면서 데이터 전송이 더 빠르고, CPU와 GPU 간 동기화 문제를 줄일 수 있음
동작 방식
버퍼 생성: CPU에서 상수 버퍼 생성 후 데이터 초기화(MVP 행렬, 조명 색상 / 방향, 카메라 위치 등)
D3D11_BUFFER_DESC생성 시BindFlags를D3D11_BIND_CONSTANT_BUFFER로 지정
셰이더에 바인딩: 프레임 생성 시 Draw 호출 전에 생성한 상수 버퍼를 필요한 각 셰이더에 바인딩
ID3D11DeviceContext::VSSetConstantBuffer(),ID3D11DeviceContext::PSSetConstantBuffer()등을 통해 상수 버퍼 바인딩
셰이더에서 접근: 셰이더 내부에서 상수 버퍼 데이터를 이용하여 계산을 수행
특징
데이터 전송 속도가 빠르고, 메모리를 효과적으로 사용할 수 있음
필요한 상수 데이터를 하나의 버퍼에 모아서 GPU로 전달하므로 코드가 깔끔해짐
CPU와 GPU가 데이터를 효율적으로 교환하여 성능 저하를 방지