입력 조립기(1) - 정점과 입력 배치(Input Layout)

WanJu Kim·2022년 12월 28일

Direct3D

목록 보기
8/29

Direct3D의 정점은 공간적 자료 이외에 추가적인 자료를 가질 수 있다고 했다. 원하는 자료를 가진 커스텀 정적 형식을 만들려면, 우선 그러한 자료를 담을 구조체를 정의해야 한다. 예를 들어 다음 정점 구조체들은, 하나는 정점의 위치와 색상을 담고 있고, 다른 하나는 위치, 법선, 텍스처 좌표를 담고 있다.

struct Vertex1
{
	XMFLOAT3 pos;
    XMFLOAT4 color;
};

struct Vertex2;
{
	XMFLOAT3 pos;
    XMFLOAT3 normal;
    XMFLOAT2 tex0;
    XMFLOAT2 tex1;
};

정점 구조체를 정의했다면, 다음으로는 그 정점 구조체의 각 성분이 어떤 용도인지를 Direct3D에게 알려줘야 한다. 그 수단이 바로 ID3D11InputLayout 형식의 입력 배치(input layout) 객체이다. 이 입력 배치 객체는 D3D11_INPUT_ELEMENT_DESC 구조체들로 이루어진 '배열'을 통해서 구축한다. 그 성분 하나하나는, 정점 구조체의 각 성분과 관련있다. 하나하나 알아보자.

struct D3D11_INPUT_ELEMENT_DESC{
	LPCSTR SemanticName;
    UINT SemanticIndex;
    DXGI_FORMAT Format;
    UINT InputSlot;
    UINT AlignedByteOffset;
    D3D11_INPUT_CLASSIFICATION InputSlotClass;
    UINT InstanceDataStepRate;

다음은 이를 이해하기 위한 공통 예시 코드이다.

// 정점 구조체.
struct Vertex2;
{
	XMFLOAT3 pos;
    XMFLOAT3 normal;
    XMFLOAT2 tex0;
    XMFLOAT2 tex1;
};

// input layout.
D3D11_INPUT_ELEMENT_DESC vertexDesc[] = 
{
	{"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0},
    {"NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0},
    {"TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 24, D3D11_INPUT_PER_VERTEX_DATA, 0},
    {"TEXCOORD", 1, DXGI_FORMAT_R32G32_FLOAT, 0, 32, D3D11_INPUT_PER_VERTEX_DATA, 0}
};

// 셰이더 파일
VertexOut VS(float3 iPos : POSITION,	// : 뒤에 적힌 것이 의미소(semantic)이다.
			 float3 iNormal : NORMAL,
             float2 iTex0 : TEXCOORD0,
             float2 iTex1 : TEXCOORD1)
  1. SemanticName : 성분에 부여된 문자열 이름. 여기 적힌 문자열과 정점 셰이더에 적힌 문자열은 같아야한다.(POSITION, NORMAL, TEXCOORD0, TEXCOORD1)

  2. SemanticIndex : 의미소에 부여된 색인. 위의 코드로 확인할 수 있는데, 만약 의미소 이름이 같다면 어떻게 구분할 것인가? 이 문제를 예방하기 위해 숫자를 제2 매개변수로 넣을 수 있게 했다. 이는 의미소 이름의 뒤에 붙여쓴다. 숫자가 없으면 0으로 간주한다. (POSITION = POSITION0)

  3. Format : DXGI_FORMAT 열거형의 한 멤버로, 이 정점 성분의 자료 형식을 나타낸다. 다음은 그 예이다.

DXGI_FORMAT_R32G32B32A32_FLOAT	// 4차원 32비트 부동소수점 스칼라.
DXGI_FORMAT_R8G8B8A8_UINT		// 4차원 8비트 부호 없는 정수 벡터.
DXGI_FORMAT_R32G32_FLOAT	// 2차원 32비트 부동소수점 벡터.

공통 예시를 보면 pos, normal은 float3형 세 개라 DXGI_FORMAT_R32G32B32_FLOAT가 사용되었고, tex0과 tex1은 float2형 두 개라 DXGI_FORMAT_R32G32_FLOAT가 사용되었다.

  1. InputSlot : 이 성분의 자료가 공급될 정점 버퍼 슬롯의 색인이다. Direct3D는 16개의 정점 버퍼 슬롯들(색인은 0에서 15까지)을 지원한다. 예를 들어 이렇게 나눌 수 있다.
D3D11_INPUT_ELEMENT_DESC vertexDesc[] = 
{
	{"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0},
    {"NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0},
    {"TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 1, 0, D3D11_INPUT_PER_VERTEX_DATA, 0},
    {"TEXCOORD", 1, DXGI_FORMAT_R32G32_FLOAT, 1, 8, D3D11_INPUT_PER_VERTEX_DATA, 0}
};

이렇게 하면 무슨 이득인가? 나중에 배우겠지만, 뒤의 성분들을 다른 용도로 적을 수 있다.

  1. AlignedByteOffset : 입력 슬롯을 하나만 사용하는 경우, 정점 구조체의 시작 위치와 이 정점 성분의 시작 위치 사이의 오프셋이다. 예를 들어 공통 예시 코드에서 vertexDesc[0]의 이 성분은 0이다. 왜? 이전에 아무것도 없으니까. vertexDesc[1]의 이 성분은 12다. 왜? 그 전의 pos크기가 12이기 때문이다. vertexDesc[2]의 성분은 24다. 왜? 그 전의 pos, normal의 크기가 합해서 24이기 때문이다. 이렇게 나아간다. 만약 입력 슬롯이 두 개 이상이다? 그럼 그 슬롯 내에서 위의 방법을 적용하면 된다.

  2. InputSlotClas : 지금은 D3D11_INPUT_PER_VERTEX_DATA를 지정한다고 알아두고 넘어간다. 나중에 고급 기술인 인스턴싱에 쓰인다.

  3. InstanceDataStepRate : 지금은 0이라고 지정한다. 나중에 고급 기술인 인스턴싱에 쓰인다.

이 과정까지 완료 했으면, ID3D11Device::CreateInputLayout 메서드를 호출해 입력 배치를 생성한다.

HRESULT ID3D11Device::CreateInputLayout(
	const D3D11_INPUT_ELEMENT_DESC* pInputElementDescs,
    UINT NumElements,
    const void* pShaderBytecodeWithInputSignature,
    SIZE_T BytecodeLength,
    ID3D11InputLayout** ppInputLayout);
  1. pInputElementsDescs : 정점 구조체를 서술하는 D3D11_INPUT_ELEMENT_DESC들의 배열.
  2. NumElements : 그 D3D11_INPUT_ELEMENT_DESC 배열의 원소 개수.
  3. pShaderBytecodeWithInputSignature : 정점 셰이더를 컴파일해서 얻은 바이트 코드를 가리키는 포인터.
  4. BytecodeLength : 그 바이트 코드의 크기(바이트 단위). 제3 매개변수에서 이만큼의 크기를 읽는다.
  5. ppInputLayout : 생성된 입력 배치를 이 포인터로 반환한다.

이 메서드를 통해 입력 배치를 생성했으면, 마지막으로 입력 배치를 사용하고자 하는 장치에 묶어야 한다. 바로 다음과 같이 말이다.

ID3D11InputLayout* mInputLayout;
// 입력 배치 생성.
md3dImmediateContext->IASetInputLayout(mInputLayout);

이것은 마치 기본도형 위상구조(primitive topology)처럼, 다른 입력 배치를 묶기 전까지는 그 입력 배치가 계속 적용된다.

profile
Question, Think, Select

0개의 댓글