[목차]

 

- InputLayout(버텍스 정보) 정의

- Vertex Buffer

- Index Buffer

- 버퍼 생성하기

- 셰이더 컴파일

- CreateInputLayout

- 결과 출력

- 예상치 못한 결과들

 

 

[InputLayout(버텍스 정보) 정의]

 

렌더링의 가장 기본 자원은 정점(Vertex)이고, 이걸 활용하여 프리미티브 데이터(도형의 최소 단위인 점, 선, 면)을 만들 수 있습니다. 정점에는 다양한 정보를 포함시킬 수 있는데, 이 정보들에 대한 골격을 정의해주는 것이 InputLayout입니다.

 

나중에 CreateInputLayout() 함수를 호출하여 VertexBuffer, VertexShader 정보를 통해 생성을 해줘야하지만, 시멘틱 정보에 대해서는 먼저 정의해줄 수 있습니다. 아래와 같이 함수를 호출해줍니다.

Ext_DirectXVertexData::GetInputLayoutData().AddInputLayoutDesc("POSITION", DXGI_FORMAT_R32G32B32A32_FLOAT);
Ext_DirectXVertexData::GetInputLayoutData().AddInputLayoutDesc("COLOR", DXGI_FORMAT_R32G32B32A32_FLOAT);
Ext_DirectXVertexData::GetInputLayoutData().AddInputLayoutDesc("TEXCOORD", DXGI_FORMAT_R32G32B32A32_FLOAT);
Ext_DirectXVertexData::GetInputLayoutData().AddInputLayoutDesc("NORMAL", DXGI_FORMAT_R32G32B32A32_FLOAT);

 

함수의 전달 인자로 첫 번째는 시멘틱 이름, 두 번째는 자료형을 전달해줍니다. 자료형의 경우 DXGI_FORMAT_R32G32B32A32_FLOAT를 전달해주면 시멘틱 정보 하나 당 표현하는 자료형 크기가 16byte(float4)가 됩니다. 이러면 아래의 함수가 호출됩니다.

void InputLayoutData::AddInputLayoutDesc
(
	/*1*/LPCSTR _SemanticName, 
	/*2*/DXGI_FORMAT _Format, 
	/*3*/D3D11_INPUT_CLASSIFICATION _InputSlotClass, 
	/*4*/UINT _InstanceDataStepRate, 
	/*5*/UINT _AlignedByteOffset, 
	/*6*/UINT _InputSlot,
	/*7*/UINT _SemanticIndex
)
{
	D3D11_INPUT_ELEMENT_DESC Data;

	Data.SemanticName = _SemanticName;
	Data.Format = _Format;
	Data.InputSlotClass = _InputSlotClass;
	Data.InstanceDataStepRate = _InstanceDataStepRate;
	Data.AlignedByteOffset = Offset;
	Data.InputSlot = _InputSlot;
	Data.SemanticIndex = _SemanticIndex;

	Offset += FormatSize(Data.Format);
	InputLayoutDescs.push_back(Data);
}

 

해당 함수는 D3D11_INPUT_ELEMENT_DESC 구조체에 전달받은 정보를 담고 InputLayOutDesc라는 전역 컨테이너 변수에 값을 저장하는 역할을 수행합니다. 함수에 전달된 인자들의 정보는 다음과 같습니다.

LPCSTR 
_SemanticName
- 정점 요소의 의미(Semantic)를 나타내는 문자열
- "POSITION", "COLOR", "TEXCOORD", "NORMAL" 등이 주로 사용하는 문자열
- 이것은 Vertex Shader의 입력 파라미터 순서와 일치해야함
DXGI_FORMAT
_Format
- 해당 요소의 데이터 형식을 지정
> DXGI_FORMAT_R32G32B32A32_FLOAT : float4
> DXGI_FORMAT_R32G32B32_FLOAT : float3
> DXGI_FORMAT_R32G32_FLOAT : float2
- 셰이더가 해석할 데이터 구조와 일치해야함
- 여기서는 DXGI_FORMAT_R32G32B32A32_FLOAT로 활용
D3D11_INPUT_CLASSIFICATION 
_InputSlotClass
- 정점 데이터가 정점 당(per Vetex)인지, 인스턴스 당(per Instance)인지 구분
- D3D11_INPUT_PER_VERTEX_DATA나 D3D11_INPUT_PER_INSTANCE_DATA 전달
UINT 
_InstanceDataStepRate
- InputSlotClass가 PER_VERTEX_DATA일 경우에만 사용되는 정보
- 인스턴스 몇 개마다 해당 데이터를 한 번씩 사용할지 지정하며, 보통 1을 사용(1 인스턴스 당 1개 값)
UINT
_AlignedByteOffset
- 정점 구조체 내에서 이 요소가 몇 바이트 떨어져 있는지 명시
- D3D11_APPEND_ALIGNED_ELEMENT 지정 시 자동 계산이 가능
- 수동 지정 시 수동으로 누적 오프셋을 관리해야함
- 여기서는 수동 관리를 위해 0을 할당하고 Offset으로 관리
- Offset은 각 호출마다 DXGI_FORMAT_R32G32B32A32_FLOAT 크기만큼씩 쌓일 것(16)
- 다음 호출때는 쌓인 만큼(16)에서 추가로 쌓이는 형태(32)로 관리
UINT
_InputSlot
- 어떤 버퍼 슬롯에서 데이터를 읽어올 지 지정
- IASetVertexBuffers() 함수에서 바인딩된 슬롯과 일치해야함
> 예시로 0번 슬롯은 POSITION, 1번 슬롯은 TEXTCOORD 이런 식으로
UINT 
_SemanticIndex
- 같은 SemanticName이 여러 개 있을 경우, 몇 번째인지 구분함
- TEXTCOORD0, TEXTCOORD1이 있으면 각 index는 0, 1

 

 

[Vertex Buffer]

 

정보들을 정의해줬으니, 전달받은 정보들을 어떻게 사용할지를 정의해주는 Vertex Buffer를 만들어줍니다.

////////////////////// Ext_DirectXVertexBuffer.h
#pragma once
#include "Ext_ResourceManager.h"

// 버텍스 버퍼(Vertex Buffer) 생성을 위한 클래스
class Ext_DirectXVertexBuffer : public Ext_ResourceManager<Ext_DirectXVertexBuffer>
{
	friend class Ext_DirectXMesh;

public:
	// constrcuter destructer
	Ext_DirectXVertexBuffer() {}
	~Ext_DirectXVertexBuffer() {}

	// delete Function
	Ext_DirectXVertexBuffer(const Ext_DirectXVertexBuffer& _Other) = delete;
	Ext_DirectXVertexBuffer(Ext_DirectXVertexBuffer&& _Other) noexcept = delete;
	Ext_DirectXVertexBuffer& operator=(const Ext_DirectXVertexBuffer& _Other) = delete;
	Ext_DirectXVertexBuffer& operator=(Ext_DirectXVertexBuffer&& _Other) noexcept = delete;

	// Vertex Buffer 생성 및 저장
	template<typename VertexLayout>
	static std::shared_ptr<Ext_DirectXVertexBuffer> CreateVertexBuffer(std::string_view _Name, const std::vector<VertexLayout>& _Vertexs)
	{
		std::shared_ptr<Ext_DirectXVertexBuffer> NewVertexBuffer = Ext_ResourceManager::CreateNameResource(_Name);
		NewVertexBuffer->InputLayout = std::shared_ptr<InputLayoutData>(&VertexLayout::GetInputLayoutData(), [](InputLayoutData*) {});
		NewVertexBuffer->CreateVertexBuffer(&_Vertexs[0], sizeof(VertexLayout), static_cast<UINT>(_Vertexs.size()));

		return NewVertexBuffer;
	}

	// Getter
	std::shared_ptr<class InputLayoutData> GetInputLayout() { return InputLayout; }
	COMPTR<ID3D11Buffer>& GetVertexBuffer() { return VertexBuffer; }
	UINT GetVertexSize() { return VertexSize; }
	UINT GetVertexCount() { return VertexCount; }
	UINT GetBufferSize() { return VertexBufferInfo.ByteWidth; }

protected:
	
private:
	void CreateVertexBuffer(const void* _Data, UINT _VertexSize, UINT _VertexCount); // Vertex Buffer 생성 및 저장

	std::shared_ptr<class InputLayoutData> InputLayout = nullptr;	// 생성된 입력 레이아웃 정보 저장용
	D3D11_BUFFER_DESC VertexBufferInfo = { 0, };  // 버텍스 버퍼 DESC 저장용
	COMPTR<ID3D11Buffer> VertexBuffer = nullptr;   // 버텍스 버퍼 인터페이스 저장용
	UINT VertexSize = 0;  // 버텍스 사이즈
	UINT VertexCount = 0; // 버텍스 갯수
	
	UINT Offset = 0;
};

////////////////////// Ext_DirectXVertexBuffer.cpp
#include "PrecompileHeader.h"
#include "Ext_DirectXVertexBuffer.h"
#include "Ext_DirectXDevice.h"

// 버텍스 버퍼 생성
void Ext_DirectXVertexBuffer::CreateVertexBuffer(const void* _Data, UINT _VertexSize, UINT _VertexCount)
{
	VertexSize = _VertexSize;
	VertexCount = _VertexCount;

	// D3D11_BUFFER_DESC 정보 입력
	VertexBufferInfo.BindFlags = D3D11_BIND_VERTEX_BUFFER;
	VertexBufferInfo.ByteWidth = VertexSize * VertexCount;
	VertexBufferInfo.CPUAccessFlags = 0;
	if (0 == VertexBufferInfo.CPUAccessFlags)
	{
		VertexBufferInfo.Usage = D3D11_USAGE_DEFAULT;
	}
	else
	{
		VertexBufferInfo.Usage = D3D11_USAGE_DYNAMIC;
	}
	// <<설명>>
	/*1. BindFlags : 이 버퍼가 GPU에서 어떤 용도로 바인딩될 지 설명하기 위함, D3D11_BIND_VERTEX_BUFFER는 버텍스 버퍼로 사용하겠다는 뜻*/ 
	/*2. ByteWidth : 버퍼의 크기(byte 단위), (Vertex 크기 * Vertex 갯수)로 정해줌*/
	/*3. CPUAccessFlags : CPU가 버퍼에 접근할 수 있는지 설정*/
	/*4. Usage : 버퍼의 사용 방식을 정의함, D3D11_USAGE_DEFAULT는 GPU가 사용하는 버퍼로 설정하여 CPU접근 불가 설정, D3D11_USAGE_DYNAMIC은 CPU가 자주 수정하는 버퍼*/

	// D3D11_SUBRESOURCE_DATA : 버퍼 또는 리소스를 생성할 때 초기 데이터를 GPU에 전달하기 위한 구조체
	D3D11_SUBRESOURCE_DATA Data;
	Data.pSysMem = _Data;
	// pSysMem은 리소스에 복사할 원본 데이터의 포인터
	// 두 개 더 있는데, 버퍼 생성시에는 하지않음

	// VertexBuffer 생성
	if (S_OK != Ext_DirectXDevice::GetDevice()->CreateBuffer(&VertexBufferInfo, &Data, VertexBuffer.GetAddressOf()))
	{
		MsgAssert("버텍스 버퍼 생성에 실패했습니다.");
	}
	// <<설명>>
	/*1. D3D11_BUFFER_DESC 전달*/
	/*2. D3D11_SUBRESOURCE_DATA 전달*/
	/*3. ID3D11Buffer 전달*/
}

 

리소스를 만들 곳에서 CreateVertexBuffer()를 호출하면, 리소스 매니저 컨테이너에 데이터를 저장하고 D3D11_BUFFER_DESC 구조체 정보를 정의하여 정점 버퍼를 어떻게 활용할 것인지 정해줍니다.

BindFlags - 현재 생성하려는 Buffer가 어떤 것인지 정의
- 여기서는 Vertex Buffer를 만드는 것이기 떄문에 D3D11_BIND_VERTEX_BUFFER를 입력
ByteWidth - 정점의 크기와 갯수
- 위에서 InputLayout에 POSITION, COLOR, TEXCOORD, NORAML 네 개를 정의했기 때문에 시멘틱 하나 당 16byte, 4개면 64byte가 됨
- 나중에 정점이 몇개 들어오는지에 따라 달라지지만, 삼각형 기준으로는 정점이 3개이기 때문에 192byte
CPUAccessFlags - CPU가 버퍼에 접근할 수 있는지에 대해 설정
Usage - 버퍼의 사용 방식 정의
- D3D11_USAGE_DEFAULT는 GPU가 사용하는 버퍼로 설정하여 CPU가 접근 못하도록 함
- DYNAMIC은 CPU가 자주 수정하는 버퍼로 설정

 

이후 CreateBuffer() 함수를 호출하여 Vertex Buffer를 생성합니다.

 

 

[Index Buffer]

 

Index Buffer는 정점을 그리는 순서에 대한 정보를 담는 버퍼입니다.

////////////////////////////////// Ext_DirectXIndexBuffer.h
#pragma once
#include "Ext_ResourceManager.h"

// 인덱스 버퍼(Index Buffer) 생성을 위한 클래스
class Ext_DirectXIndexBuffer : public Ext_ResourceManager<Ext_DirectXIndexBuffer>
{
	friend class Ext_DirectXMesh;

public:
	// constrcuter destructer
	Ext_DirectXIndexBuffer() {}
	~Ext_DirectXIndexBuffer() {}

	// delete Function
	Ext_DirectXIndexBuffer(const Ext_DirectXIndexBuffer& _Other) = delete;
	Ext_DirectXIndexBuffer(Ext_DirectXIndexBuffer&& _Other) noexcept = delete;
	Ext_DirectXIndexBuffer& operator=(const Ext_DirectXIndexBuffer& _Other) = delete;
	Ext_DirectXIndexBuffer& operator=(Ext_DirectXIndexBuffer&& _Other) noexcept = delete;

	// IndexBuffer 생성
	template<typename Type>
	static std::shared_ptr<Ext_DirectXIndexBuffer> CreateIndexBuffer(std::string_view _Name, const std::vector<Type>& _Vertexs)
	{
		std::shared_ptr<Ext_DirectXIndexBuffer> NewIndexBuffer = Ext_ResourceManager::CreateNameResource(_Name);
		NewIndexBuffer->CreateIndexBuffer(&_Vertexs[0], sizeof(Type), static_cast<UINT>(_Vertexs.size()));

		return NewIndexBuffer;
	}

	COMPTR<ID3D11Buffer>& GetIndexBuffer() { return IndexBuffer; }
	UINT GetVertexSize() { return VertexSize; }
	UINT GetVertexCount() { return VertexCount; }
	UINT GetBufferSize() { return IndexBufferInfo.ByteWidth; }

protected:
	
private:
	void CreateIndexBuffer(const void* _Data, UINT _IndexSize, UINT _IndexCount); // IndexBuffer 생성
	
	D3D11_BUFFER_DESC IndexBufferInfo = { 0, };  // 인덱스 버퍼 DESC 저장용
	COMPTR<ID3D11Buffer> IndexBuffer = nullptr;   // 인덱스 버퍼 인터페이스 저장용
	UINT VertexSize = 0;											// 버텍스 사이즈
	UINT VertexCount = 0;										// 버텍스 갯수
	DXGI_FORMAT Format = DXGI_FORMAT::DXGI_FORMAT_R32_UINT;

	UINT Offset = 0;
};

////////////////////////////////// Ext_DirectXIndexBuffer.cpp
#include "PrecompileHeader.h"
#include "Ext_DirectXIndexBuffer.h"
#include "Ext_DirectXDevice.h"

// IndexBuffer 생성
void Ext_DirectXIndexBuffer::CreateIndexBuffer(const void* _Data, UINT _IndexSize, UINT _IndexCount)
{
	VertexSize = _IndexSize;

	switch (VertexSize)
	{
	case 2:
		Format = DXGI_FORMAT_R16_UINT;
		break;
	case 4:
		Format = DXGI_FORMAT_R32_UINT;
		break;
	default:
		break;
	}

	VertexCount = _IndexCount;

	IndexBufferInfo.BindFlags = D3D11_BIND_INDEX_BUFFER;
	IndexBufferInfo.ByteWidth = VertexSize * VertexCount;
	IndexBufferInfo.CPUAccessFlags = 0;
	if (0 == IndexBufferInfo.CPUAccessFlags)
	{
		IndexBufferInfo.Usage = D3D11_USAGE_DEFAULT;
	}
	else 
	{
		IndexBufferInfo.Usage = D3D11_USAGE_DYNAMIC;
	}

	D3D11_SUBRESOURCE_DATA Data;
	Data.pSysMem = _Data;

	if (S_OK != Ext_DirectXDevice::GetDevice()->CreateBuffer(&IndexBufferInfo, &Data, IndexBuffer.GetAddressOf()))
	{
		MsgAssert("버텍스 버퍼 생성에 실패했습니다.");
	}
}

 

마찬가지로 버퍼이기 때문에 VertexBuffer와 생성 방식이 유사합니다. 차이는 BindFlags가 INDEX_BUFFER인 것 밖에 없습니다.

 

 

[버퍼 생성하기]

 

InputLayout으로 정점 정보를 정의했고, VertexBuffer, IndexBuffer를 생성하는 클래스를 만들었으니 아래와 같이 호출하면 버퍼를 생성할 수 있습니다.

// 삼각형
std::vector<Ext_DirectXVertexData> Vertices;
Vertices.resize(3);

Vertices[0] = { { 0.0f, 0.5f, 0.0 }, { 1, 0, 0, 1 } };
Vertices[1] = { { 0.5f, -0.5f, 0.0f }, { 0, 1, 0, 1 } };
Vertices[2] = { { -0.5f, -0.5f, 0.0f }, { 0, 0, 1, 1 } };

std::vector<UINT> ArrIndex = { 0, 1, 2 };

Ext_DirectXVertexBuffer::CreateVertexBuffer("Triangle", Vertices);
Ext_DirectXIndexBuffer::CreateIndexBuffer("Triangle", ArrIndex);

//,,,

// 사각형
std::vector<Ext_DirectXVertexData> Vertices;
Vertices.resize(4);

ArrVertex[0] = { { 0.5f,  0.5f, -0.5f, 1.0f}, {1, 0, 0, 1}, {1, 0}, {0, 0, -1} };
ArrVertex[1] = { {-0.5f,  0.5f, -0.5f, 1.0f}, {0, 1, 0, 1}, {0, 0}, {0, 0, -1} };
ArrVertex[2] = { {-0.5f, -0.5f, -0.5f, 1.0f}, {0, 0, 1, 1}, {0, 1}, {0, 0, -1} };
ArrVertex[3] = { { 0.5f, -0.5f, -0.5f, 1.0f}, {1, 1, 0, 1}, {1, 1}, {0, 0, -1} };

std::vector<UINT> ArrIndex = { 0, 1, 2, 0, 2, 3, };

Ext_DirectXVertexBuffer::CreateVertexBuffer("Rect", Vertices);
Ext_DirectXIndexBuffer::CreateIndexBuffer("Rect", ArrIndex);

 

IndexBuffer에 대해, 삼각형은 { 0, 1, 2 }을 전달하고 있습니다. 이러면 3개의 정점에 대해 그리는 순서를 정의해주는 것인데, 그리는 순서가 0 -> 1 -> 2가 됩니다.

 

그러면 사각형은 { 0, 1, 2, 3 }으로 설정하여 0 -> 1 -> 2 -> 3이 될 것 같지만, 아닙니다.

 

Direct3D에서 면으로 그려질 수 있는 가장 작은 단위는 삼각형이고, 렌더링 과정에서는 정점들을 최대한 재사용할 수 있도록 설계되어 있습니다. 따라서 사각형도 나눠보면 삼각형 두 개로 그릴 수 있기 때문에, 삼각형 두 개를 그리기 위한 정보를 전달해줘야 합니다. 여기서는 { 0, 1, 2, 0, 2, 3 }를 전달해주고 있습니다. 이러면 왼쪽 위에 하나, 오른쪽 아래에 하나로 총 두개의 삼각형이 그려져서 사각형을 이루게 됩니다.

 

 

[셰이더 컴파일]

 

이제 정점 정보들을 GPU가 어떻게 처리할 것인지를 정의해줘야 합니다. 이를 위해 Shader가 필요한데, 자세한 설명은 나중에 자세히 하는 것으로 하고 일단 셰이더를 만들어보겠습니다. 아래와 같이 Vertex Shader, Pixel Shader를 만들어줍니다.

///////////// BaseVertexShader.hlsl
struct VSInput
{
    float3 Position : POSITION;
    float4 Color : COLOR;
};

struct PSInput
{
    float4 Position : SV_POSITION;
    float4 Color : COLOR;
};

PSInput main(VSInput _Input)
{
    PSInput Output;
    Output.Position = float4(_Input.Position, 1.0f);
    Output.Color = _Input.Color;
    return Output;
}

///////////// BasePixelShader.hlsl
struct PSInput
{
    float4 Position : SV_POSITION;
    float4 Color : COLOR;
};

float4 main(PSInput _Input) : SV_TARGET
{
    return _Input.Color;
}

 

struct 내에는 시멘틱 정보들인 POSITION, COLOR, TEXCOORD, NORMAL을 모두 안써도 되고, "순서대로"만 쓰면 됩니다. 현재 사용할 정보는 POSITION과 COLOR이기 때문에 2개만 작성해줍니다. Vertex Shader와 Pixel Shader의 엔트리 포인트 모두 그냥 값을 그대로 받아서 다시 그대로 return하는 임시적인 형태로 만들었습니다.

 

다음으로 셰이더를 컴파일해줍니다. 컴파일은 아래와 같이 실시해주면 됩니다.

void Ext_DirectXResourceLoader::ShaderCompile() 
{
	unsigned int Flag = 0;

#ifdef _DEBUG
	Flag = D3D10_SHADER_DEBUG; // 디버그 정보를 포함
#endif
	Flag |= D3DCOMPILE_PACK_MATRIX_ROW_MAJOR; // 행우선 매트릭스 정렬 방식 사용 (HLSL ↔ C++ 호환 용이)

	// ID3DBlob : 셰이더 바이트코드를 담는 인터페이스
	COMPTR<ID3DBlob> ErrorBlob = nullptr;
	COMPTR<ID3DBlob> VSBlob = nullptr;
	if (S_OK != D3DCompileFromFile(L"../Shader/BaseVertexShader.hlsl", nullptr, D3D_COMPILE_STANDARD_FILE_INCLUDE, "main", "vs_5_0", Flag, 0, VSBlob.GetAddressOf(), ErrorBlob.GetAddressOf()))
	{
		MsgAssert("VertexShader 컴파일 실패");
		return;
	}
	// <<설명>>
	/*1. 파일 경로 입력*/
	/*2. 기본 include 처리 방식 설정*/
	/*3. 함수명*/
	/*4. 셰이더 모델, 비주얼 스튜디오 2022에서 기본 생성하면 Vertex Shader Model 5.0이기 떄문에 vs_5_0*/
	/*5. 컴파일 플레그 설정*/
	/*6. 컴파일 플레그 설정*/
	/*7. 출력 바이트코드*/
	/*8. 출력 에러*/

	// 컴파일된 바이트코드로 GPU용 Vertex Shader 생성
	Ext_DirectXDevice::GetDevice()->CreateVertexShader(VSBlob->GetBufferPointer(), VSBlob->GetBufferSize(), nullptr, &BaseVertexShader);

	COMPTR<ID3DBlob> PSBlob = nullptr;
	if (S_OK != D3DCompileFromFile(L"../Shader/BasePixelShader.hlsl", nullptr, D3D_COMPILE_STANDARD_FILE_INCLUDE, "main", "ps_5_0", Flag, 0, PSBlob.GetAddressOf(), ErrorBlob.GetAddressOf()))
	{
		MsgAssert("PixelShader 컴파일 실패");
		return;
	}

	// 컴파일된 바이트코드로 GPU용 Pixel Shader 생성
	Ext_DirectXDevice::GetDevice()->CreatePixelShader(PSBlob->GetBufferPointer(), PSBlob->GetBufferSize(), nullptr, &BasePixelShader);

	// CreateInputLayout은 정점 버퍼 구조와 셰이더 입력 구조 간의 매핑을 정의
	// 일단 생성해야되서 적음, 구조 잡으면서 나중에 옮길 예정
	Ext_DirectXDevice::GetDevice()->CreateInputLayout
	(
		Ext_DirectXInputLayout::GetInputLayoutData().GetInputLayoutDescs().data(),
		static_cast<UINT>(Ext_DirectXInputLayout::GetInputLayoutData().GetInputLayoutDescs().size()),
		VSBlob->GetBufferPointer(),
		VSBlob->GetBufferSize(),
		&InputLayout
	);

	// 각 결과물은 일단 이곳의 클래스에서 저장했다. 다른곳에서 써야함
	// 구조 잡으면서 나중에 옮길 예정
	// static COMPTR<ID3D11VertexShader> BaseVertexShader;
	// static COMPTR<ID3D11PixelShader> BasePixelShader;
	// static COMPTR<ID3D11InputLayout> InputLayout;
}

 

여기서 Path를 넣어주는 부분에 상대 경로를 넣어줘도 되지만, Unicode 형태로 넣어줘야 합니다. 안그러면 경로가 제대로 읽히지 않아 셰이더 컴파일이 실패하게 됩니다.

 

 

[CreateInputLayout]

 

정보들을 정의하고 값을 담아준 뒤 어떻게 그릴지, GPU가 어떻게 쓸 지를 모두 정해줬으니 이걸로 InputLayout을 실제로 만들어줘야합니다. 아래의 함수를 호출해줍니다.

void Ext_DirectXInputLayout::CreateInputLayout(std::shared_ptr<Ext_DirectXVertexBuffer> _VertexBuffer, std::shared_ptr< Ext_DirectXVertexShader> _VertexShader)
{
	Relase(); // 한번 해줘야 CreateInputLayout가 정상 동작함

	if (nullptr == _VertexBuffer->GetInputLayout())
	{
		MsgAssert("레이아웃 정보를 만들수 없는 버텍스 버퍼 입니다.");
	}

	const std::vector<D3D11_INPUT_ELEMENT_DESC>& LayOutInfo = _VertexBuffer->GetInputLayout()->GetInputLayoutDescs();

	// 정점 버퍼의 각 요소(Vertex Data)를 정점 셰이더의 입력 파라미터와 매핑할 수 있도록 연결하는 구조를 만드는 함수
	HRESULT hr = Ext_DirectXDevice::GetDevice()->CreateInputLayout
	(
		&LayOutInfo[0],
		static_cast<UINT>(LayOutInfo.size()),
		_VertexShader->GetBinaryCode()->GetBufferPointer(),
		_VertexShader->GetBinaryCode()->GetBufferSize(),
		InputLayout.GetAddressOf()
	);
	// <<설명>>
	/*1. const D3D11_INPUT_ELEMENT_DESC* : 정점 입력 요소 배열의 시작 주소, 각 요소가 어떤 데이터인지 정의*/
	/*2. UINT : 위 배열의 요소 수(입력 요소가 몇개인가)*/
	/*3. const void* : 정점 셰이더의 바이트 코드 시작 주소, 여기에는 입력 시그니처가 포함되어 있어야 함*/
	/*4. SIZE_T : 위 바이트코드의 길이*/
	/*5. ID3D11InputLayout** : 호출의 결과로 생성될 InputLayout 객체의 포인터 주소 저장->IASetInputLayout에서 활용*/

	if (S_OK != hr)
	{
		char Buffer[256] = {};
		sprintf_s(Buffer, "CreateInputLayout failed: 0x%08X", hr);
		MsgAssert(Buffer)
		return;
	}
}

 

CreateInputLayout() 함수에 인자들을 전달하여 InputLayout을 만들 수 있는데, 각 인자들의 정보는 다음과 같습니다.

const D3D11_INPUT_ELEMENT_DESC* - 정점 입력 요소 정보를 담은 배열의 시작 주소
- 전역 컨테이너인 InputLayOutDesc에 정보들을 담아줬기 때문에, 이걸 넣어주면 됨
UINT - InputLayOutDesc의 요소 갯수
const void* - Vertex Shader의 ByteCode 시작 주소
SIZE_T - Vertex Shader의 ByteCode 길이
ID3D11InputLayout** - 생성 결과물을 담을 포인터 전달

 

+) CreateInputLayout() 호출 과정에서 값을 받아볼 InputLayout의 경우, 명시적으로 한 번 Reset을 실시해주는 것이 좋을 수도 있습니다. 일전에 계속 생성에 실패해서 그냥 Reset을 한 뒤 넣으니 정상적으로 생성됐습니다. 아마 초기화가 제대로 되지 않아 쓰레기값이 담겨 있어서 그런 것으로 생각됩니다.

 

 

[결과 출력]

 

여기까지 진행했으면 바로 삼각형, 사각형을 출력해볼 수 있습니다. 아래와 같이 작성해줍니다.

void Ext_Core::RenderTest()
{
	// 1. 메인 렌더 타겟 가져오기
	std::shared_ptr<Ext_DirectXRenderTarget> MainRenderTarget = Ext_DirectXDevice::GetMainRenderTarget();
	COMPTR<ID3D11RenderTargetView> RTV = MainRenderTarget->GetTexture(0)->GetRTV();
	COMPTR<ID3D11DepthStencilView> DSV = MainRenderTarget->GetDepthTexture()->GetDSV();
	D3D11_VIEWPORT* ViewPort = MainRenderTarget->GetViewPort(0);

	// 2. 렌더 타겟 및 뎁스 클리어
	float ClearColor[4] = { 0.0f, 0.0f, 1.0f, 1.0f }; // 파란색
	Ext_DirectXDevice::GetContext()->ClearRenderTargetView(RTV.Get(), ClearColor);
	Ext_DirectXDevice::GetContext()->ClearDepthStencilView(DSV.Get(), D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0);
    
	// 3. 렌더 타겟 세팅하기
	Ext_DirectXDevice::GetContext()->OMSetRenderTargets(1, RTV.GetAddressOf(), DSV.Get());
	Ext_DirectXDevice::GetContext()->RSSetViewports(1, ViewPort);

	// 4. ==========렌더링==============
	std::shared_ptr<Ext_DirectXVertexBuffer> VB = Ext_DirectXVertexBuffer::Find("Triangle");
	std::shared_ptr<Ext_DirectXIndexBuffer> IB = Ext_DirectXIndexBuffer::Find("Triangle");
	COMPTR<ID3D11Buffer>& VertexBuffer = VB->GetVertexBuffer();
	UINT stride = VB->GetVertexSize();
	UINT Offset = 0;

	Ext_DirectXDevice::GetContext()->IASetVertexBuffers(0, 1, VertexBuffer.GetAddressOf(), &stride, &Offset);
	Ext_DirectXDevice::GetContext()->IASetIndexBuffer(IB->GetIndexBuffer().Get(), DXGI_FORMAT_R32_UINT, 0);

	Ext_DirectXDevice::GetContext()->IASetInputLayout(Ext_DirectXResourceLoader::GetInputLayout().Get());
	Ext_DirectXDevice::GetContext()->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

	Ext_DirectXDevice::GetContext()->VSSetShader(Ext_DirectXResourceLoader::GetVertexShader(), nullptr, 0);
	Ext_DirectXDevice::GetContext()->PSSetShader(Ext_DirectXResourceLoader::GetPixelShader(), nullptr, 0);

	Ext_DirectXDevice::GetContext()->DrawIndexed(IB->GetVertexCount(), 0, 0);
	// ==========렌더링 끝==============

	// 5. 화면 출력
	Ext_DirectXDevice::GetSwapChain()->Present(1, 0);
}

 

1 ~ 3. 기존과 동일함

 

4-1. 렌더링 시작 준비

: Triangle 정보가 담긴 Vertex Buffer와 Index Buffer를 가져옵니다.

 

4-2. Vertex Buffer와 Index Buffer 바인딩

: IASetVertexBuffer()와 IASetIndexBuffer() 함수를 통해 렌더링 파이프라인에 두 버퍼를 바인딩해줍니다. IASetVertexBuffer()의 경우 전달 인자 값이 다음과 같습니다.

> 1번 인자 : Vertex Buffer 슬롯, 해당 프레임워크에서는 하나만 사용하기 때문에 그냥 0으로 넣어줌

> 2번 인자 : Vertex Buffer 갯수, 하나만 사용

> 3번 인자 : Vertex Buffer 주소(CreateBuffer()할 때 반환받은 값)

> 4번 인자 : 정점 하나당 크기, stride라고 함

> 5번 인자 : 버퍼 시작 오프셋, 보통 0임

 

IASetIndexBuffer()는 전달 인자 값이 다음과 같습니다.

> 1번 인자 : Index Buffer 주소(CreateBuffer()할때 반환받은 값)

> 2번 인자 : Index Buffer 데이터 타입, 보통 DXGI_FORMAT_R32_UINT 전달하면 됨(UINT)

> 3번 인자 : 버퍼 시작 오프셋, 보통 0임

 

4-3. InputLayout 바인딩

: IASetInputLayout() 함수를 호출하여 정점의 정보를 바인딩해줍니다. CreateInputLayout()을 통해 반환받은 값을 넣어줍니다.

 

4-4. 출력 단위 설정

: IASetPrimitiveTopology() 함수를 호출하여 출력 단위를 설정합니다. 기본적으로는 삼각형을 사용하기 때문에, 정점들을 삼각형으로 해석할 수 있도록 D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST를 전달해줍니다(라인, 포인트 등도 존재함).

 

4-5. 셰이더 바인딩

: VSSetShader() 함수와 PSSetShader() 함수를 통해 셰이더를 바인딩해줍니다. 앞서 CreateVertexShader()와 CreatePixelShader()를 통해 반환받은 포인터를 넣고 나머지 인자로 nullptr, 0을 전달합니다. 셰이더에 대해 따로 사용할 클래스가 있으면 전달하지만, 해당 프레임워크에서는 따로 만들어서 사용하지 않을 예정이니 이렇게 전달해주면 됩니다.

 

4-6. DrawIndexed 호출

: 위에서 렌더링 파이프라인에 필요한 정보들을 바인딩 했으니, DrawIndexed()를 호출하면 결과물이 화면에 출력됩니다.

 

5-1. 삼각형 결과 확인

 

5-2. 시각형 결과 확인

: Vertex Buffer와 Index Buffer를 Rect로 전달해주면 사각형으로 그려집니다.

 

 

[예상치 못한 결과들]

 

출력 결과가 이상하게 보일 것입니다. 우리의 목표는 정삼각형, 정사각형이었지만 화면에서는 삼각형과 직사각형이 그러지기 때문입니다. 이것은 아직 뷰, 프로젝션 행렬을 적용하지 않았기 때문에 좌표 변환이 정상적으로 이뤄지지 않았기 때문입니다. 

 

현재 화면 비율은 16:9 비율로 ViewPort가 설정되어 있습니다. Vertex Buffer에 정의한 값이 그대로 NDC(Normalized Device Coordinates) 좌표에 그려지지만, ViewPort 변환이 수행되기 떄문에 위의 출력 결과물들 처럼 옆으로 늘어진 형태가 되는 것입니다. 해당 문제에 대해서는 이후 포스팅에서 자세히 다루면서 문제를 해결해보도록 하겠습니다.

 

또한 정점 별로 색을 지정해줬는데, 결과물은 보간된 값이 나옵니다. 이건 어떻게 보면 정상적인 출력 결과인데, Rasterizer 단계에서 픽셀 단위로 보간이 수행되기 때문입니다. 픽셀이 정점들과 얼마나 가까운지 계산되면서 Color값이 자동으로 선형 보간되기 때문에 결과와 같이 혼합된 색상이 나오는 것입니다.

+ Recent posts