[DirectX11] Input Assembler
이 글은 인프런 강의 [게임 프로그래머 도약반] DirectX11 입문를 보고 따라 만들면서 헷갈리는 부분을 복습하며 정리한 글입니다.
Input Assembler 인풋 어셈블러
Input Assembler란?
Input Assembler
란 렌더링 파이프라인의 가창 첫 번째 단계로,
GPU
에게 정점(Vertex)
의 정보를 넘기는 작업을 맡는다.
즉, 이 단계에서는 어떤 도형을 만드는 데이터
를 포장
해서 GPU에게 넘기는 것
이다.
어떤 도형을 만드는 정보를 포장해서 GPU에게 넘기는 것
이 작업을 하기 위해 사용하는 3가지의 구성요소가 있다.
1.VertexBuffer : 정점 데이터 포장
2.InputLayout : 포장된 데이터를 GPU가 사용할 수 있도록 설명서의 역할을함
3.IndexBuffer : 정점 데이터를 효율적으로 포장하기위해 사용
차례차례 봐보자
VertexBuffer
VertexBuffer 단계에서는 GPU
가 VertexData(정점 데이터)를 사용할 수 있도록 저장
하는 단계이다.
여기서 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에 저장된 VertexBuffer
를 IA 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 : 정점 데이터
를 효율적으로 포장
하기위해 사용
댓글남기기