3 분 소요

이 글은 인프런 강의 [게임 프로그래머 도약반] DirectX11 입문를 보고 따라 만들면서 헷갈리는 부분을 복습하며 정리한 글입니다.

Input Assembler 인풋 어셈블러

Input Assembler란?

Input Assembler란 렌더링 파이프라인의 가창 첫 번째 단계로,
GPU에게 정점(Vertex)의 정보를 넘기는 작업을 맡는다.
즉, 이 단계에서는 어떤 도형을 만드는 데이터포장해서 GPU에게 넘기는 것이다.

어떤 도형을 만드는 정보를 포장해서 GPU에게 넘기는 것

이 작업을 하기 위해 사용하는 3가지의 구성요소가 있다.
1.VertexBuffer : 정점 데이터 포장 2.InputLayout : 포장된 데이터를 GPU가 사용할 수 있도록 설명서의 역할을함 3.IndexBuffer : 정점 데이터를 효율적으로 포장하기위해 사용

차례차례 봐보자

VertexBuffer

VertexBuffer 단계에서는 GPUVertexData(정점 데이터)를 사용할 수 있도록 저장하는 단계이다.

여기서 Vertex(정점)이란?
간단하게 보여주면 다음 그림과 같이 도형의 각 점들을 Vertex(정점)이라고 볼 수 있다.
정점

그림에서는 정점이 단순히 위치만을 가지고 있지만 각 정점별로
Color(색), Normal(법선), uv(텍스쳐 좌표)등 더 많은 정보를 가질 수 있다.

이러한 한 정점에 대한 여러 데이터 값을 정점 데이터라고 하며 구조체로 한 번에 묶어서 사용한다.

struct Vertex
{
    float3 Position;  // 위치
    float4 Color;     // 정점의 색
};

어느 한 도형의 VertexData를 vector로 묶어 도형에 대한 모든 버텍스 데이터를 저장할 수 있다.
만약 단순한 삼각형을 그리고 싶다면 다음과 같이 묶을 수 있다.
여기까진 아직 CPU가 데이터를 들고있는 것이다.

// 정점 데이터 벡터
vector<Vertex> _vertices;

_vertices.resize(3);

// Vec3와 Color는 using을 통해 이름만 바꾼 DirectX::XMFLOAT3와 DirectX::XMFLOAT4임
_vertices[0].position = Vec3(-0.5f, -0.5f, 0);
_vertices[0].color = Color(1.0f, 0.f, 0.f, 1.f);

_vertices[1].position = Vec3(0, 0.5f, 0);
_vertices[1].color = Color(1.0f, 0.f, 0.f, 1.f);

_vertices[2].position = Vec3(0.5f, -0.5f, 0);
_vertices[2].color = Color(1.0f, 0.f, 0.f, 1.f);


위에서 만든 데이터 VertexData로 다음과 같이 Buffer를 만든다.
Buffer를 만들떄는 D3D11_BUFFER_DESC라는 구조체를 통해 버퍼에 대한 정보를 명시해줘야하며,
D3D11_SUBRESOURCE_DATA 구조체를 통해 버퍼에 채울 데이터를 명시해준다.

바로 이 단계에서 CPU에서 GPU데이터를 넘겨준다.

// 버퍼 정보 초기화
D3D11_BUFFER_DESC desc;	
ZeroMemory(&desc, sizeof(desc));
desc.Usage = D3D11_USAGE_IMMUTABLE; // 버퍼의 사용 목적, 읽기 전용 -> 데이터를 고칠 수 없음
desc.BindFlags = D3D11_BIND_VERTEX_BUFFER; // 버퍼의 용도를 지정, 정점 버퍼를 만드는데 사용할거에요 라고 주장
desc.ByteWidth = (uint32)(sizeof(Vertex) * _vertices.size()); // 데이터를 넣을 버퍼의 크기를 정해준다


D3D11_SUBRESOURCE_DATA data;
ZeroMemory(&data, sizeof(data));
data.pSysMem = _vertices.data(); // &vertices[0]

// 디바이스 객체를 통해 버텍스 버퍼만들고 더블 포인터 변수 _vertexBuffer에 저장됨
// 즉 이 함수에서 CPU에서 ->GPU로 데이터가 옮겨짐
_device->CreateBuffer(&desc, &data, _vertexBuffer.GetAddressOf());


마지막으로 deviceContext를 통해 파이프라인에 버텍스 버퍼를 세팅해주면 된다.
이 단계에서는 GPU에 저장된 VertexBufferIA Slot에 연결시킨다.

uint32 stride = sizeof(Vertex);
uint32 offset = 0;
// deviceContext를 통해 vertexBuffer
_deviceContext->IASetVertexBuffers(0, 1, _vertexBuffer.GetAddressOf(),
	&stride, &offset);
// 삼각형 단위로 그림
_deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);


간단히 요약하면 PipeLine의 IA의 VertexBuffer 단계CPU에서 도형의 정보를 구성하고,
GPU로 옮긴 뒤 이 데이터를 사용하라고 지정해주는 것이다.

InputLayout

VertexBuffer에 저장된 값을 GPU가 사용하기 위해서 InputLayout을 통해 사용 설명서를 만들어줘야 한다.
그렇기 떄문에 VertexBuffer로 저장한 sturct와 맞춰야한다.

Vertex의 구조체는 다음과 같다.

float3 Position;  // 위치
float4 Color;     // 정점의 색

이와 맞춰서 InputLayout은 다음과 같이 만들 수 있다.

// Struct 에서 만든 Vertex를 묘사해줘야함
D3D11_INPUT_ELEMENT_DESC layout[] =
{
    {"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA,0},
    {"COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA,0},
};

const int32 count = sizeof(layout) / sizeof(D3D11_INPUT_ELEMENT_DESC);

_device->CreateInputLayout(layout, count, _vsBlob->GetBufferPointer(), _vsBlob->GetBufferSize(), _inputLayout.GetAddressOf());

여기서 CreateInputLayout에서 _vsBlob을 넣어주는 부분이 있다.
_vsBlob은 사전에 미리 저장한 .hlsl파일을 저장한 것으로
나중에 VertexBuffer의 값을 어떤식으로 사용할지를 묘사하는 .hlsl파일에서
VertexBuffer의 Position과 Color를 사용할 수 있도록 InputLayout의 POSITION과 COLOR
.hlsl파일의 POSITION과 COLOR매핑시키는 역할을 한다. (두 이름이 같아야함)

나중에 VS(Vertex Shader)단계에서 필요한 설정이므로 이정도로 넘어가겠다.

어찌되었든 이렇게 만들어진 InputLayout을 렌더링 파이프라인의 IA단계에서 지정해주면 된다.

_deviceContext->IASetInputLayout(_inputLayout.Get());

IndexBuffer

IndexBuffer 결론부터 말하자면 VertexBuffer를 효율적으로 저장하기 위해 사용된다.

도형을 묘사할 때 보통 다음과 같이 최소 도형의 형태를 삼각형으로 잡는다.

_deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);


이는 삼각형을 만들떄는 느끼지 못하지만 사각형 이상을 만든다면 알 수 있다.
보통 사각형을 만든다면 정점이 4개면 충분하다고 생각할 것이다.
하지만 최소 도형을 삼각형으로 하였기 때문에 사각형또한 다음과 같이 6개의 정점로 만들어야한다.

즉, 아래 그림처럼 3각형 두개를 만들어서 4각형을 만들어야한다. 사각형만들기

하지만 indexBuffer를 이용하면 같은 정점을 여러번 사용할 수 있다.
단지, 삼각형을 만들 때 사용할 정점3개의 순서만 지정해주면 된다.

인덱스버퍼로 사각형만들기

사각형을 만들기 위해 위해 버텍스는 다음과 같이 4개의 정점으로 재정의하여 사용한다면

// VertexData
{
    _vertices.resize(4);

    // 1 3
    // 0 2
    _vertices[0].position = Vec3(-0.5f, -0.5f, 0);
    _vertices[0].color = Color(1.0f, 0.f, 0.f, 1.f);

    _vertices[1].position = Vec3(-0.5f, 0.5f, 0);
    _vertices[1].color = Color(0.0f, 1.f, 0.f, 1.f);

    _vertices[2].position = Vec3(0.5f, -0.5f, 0);
    _vertices[2].color = Color(0.0f, 0.f, 1.f, 1.f);

    _vertices[2].position = Vec3(0.5f, 0.5f, 0);
    _vertices[2].color = Color(0.0f, 0.f, 1.f, 1.f);
}


indexBuffer는 다음과 같이 만들 수 있다.

// 인덱스 설정
vector<uint32> indices = { 0,1,2,2,1,3 };

_stride = sizeof(uint32);
_count = static_cast<uint32>(indices.size());

D3D11_BUFFER_DESC desc;
ZeroMemory(&desc, sizeof(desc));
desc.Usage = D3D11_USAGE_IMMUTABLE;
desc.BindFlags = D3D11_BIND_INDEX_BUFFER;
desc.ByteWidth = (uint32)(_stride * _count);

D3D11_SUBRESOURCE_DATA data;
ZeroMemory(&data, sizeof(data));
data.pSysMem = indices.data();

// indexBuffer생성
HRESULT hr = _device->CreateBuffer(&desc, &data, _indexBuffer.GetAddressOf());
CHECK(hr);


IA에 indexBuffer등록은 다음과 같이한다.

_deviceContext->IASetIndexBuffer(_indexBuffer.Get(), DXGI_FORMAT_R32_UINT, 0);

그 뒤 Draw함수가 아닌 DrawIndexed함수를 이용해서 렌더링한다.

// 인덱스 버퍼의 Count가 6이므로 6을 넣어줌
_deviceContext->DrawIndexed(6, 0, 0);

결론

IA단계에서 VertexBuffer, InputLayout, IndexBuffer는 간단히 다음의 역할을 한다.

1.VertexBuffer : 정점 데이터를 포장하여 GPU에게 넘기기 2.InputLayout : 포장된 데이터를 GPU가 사용할 수 있도록 설명서의 역할을함 3.IndexBuffer : 정점 데이터효율적으로 포장하기위해 사용

댓글남기기