[목차]

 

- 클래스 정의

- Ext_DirectXMesh

- Ext_DirectXMaterial

- Set Unit

 

 

[클래스 정의]

 

렌더링 파이프라인 설정값을 렌더링될 Object마다 원하는 세팅으로 설정해주면 좋을 것입니다. 이렇게 하는게 가능한 이유가 해당 프레임워크에서는 리소스를 만들때마다 리소스 매니저를 통해 값들을 이름으로 저장하고 있기 때문입니다.

 

이걸 십분 활용해서 Ext_DirectXMesh 클래스와 Ext_DirectXMaterial 클래스를 만들었습니다. Mesh 클래스는 InputAssembler 단계에 대한 정보를 담은 클래스, Material 클래스는 그 외의 모든 단계에 대한 정보를 담은 클래스입니다.

 

사용 방식은 다음과 같습니다.

#include "PrecompileHeader.h"
#include "CubeActor.h"

#include <DirectX11_Extension/Ext_MeshComponent.h>
#include <DirectX11_Extension/Ext_Transform.h>

void CubeActor::Start()
{
	GetTransform()->SetWorldPosition({ 0.f, 0.f, 200.0f });
	GetTransform()->SetWorldScale({ 100.f, 100.f, 100.f }); // 크기 확대
	std::shared_ptr<Ext_MeshComponent> MeshComp = CreateComponent<Ext_MeshComponent>("BasicMesh", true);
	MeshComp->CreateMeshComponentUnit("Box", "Basic");
}

 

현재의 프레임워크 구조에서, Actor 내부에서 Component를 Create 하는 함수를 호출하면 Component가 생성됩니다. 여기서 MeshComponent의 경우 CreateMeshComponentUnit() 함수를 호출하면서 Mesh와 Material을 이름으로 하여 세팅하도록 설계해뒀습니다. 이러면 이름으로 값을 찾아서 내부에서 세팅해줍니다. 물론 먼저 만들어둔 값이 있어야 할 것입니다.

 

 

[Ext_DirectXMesh]

 

먼저 Mesh 클래스입니다.

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

// 만들어진 버텍스들의 정보를 저장하기 위한 클래스
class Ext_DirectXMesh : public Ext_ResourceManager<Ext_DirectXMesh>
{
	friend class Ext_MeshComponentUnit;

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

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

	// 버텍스 버퍼와 인덱스 버퍼 정보 입력 및 메시 생성
	static std::shared_ptr<Ext_DirectXMesh> CreateMesh(std::string_view _Name)
	{
		return CreateMesh(_Name, _Name, _Name);
	}

	// 버텍스 버퍼와 인덱스 버퍼 정보 입력 및 생성
	static std::shared_ptr<Ext_DirectXMesh> CreateMesh(std::string_view _Name, std::string_view _VBName, std::string_view _IBName)
	{
		std::shared_ptr<Ext_DirectXMesh> NewMesh = Ext_ResourceManager::CreateNameResource(_Name);
		NewMesh->VertexBufferPtr = Ext_DirectXVertexBuffer::Find(_VBName);
		NewMesh->IndexBufferPtr = Ext_DirectXIndexBuffer::Find(_IBName);

		if ((nullptr == NewMesh->VertexBufferPtr) || (nullptr == NewMesh->IndexBufferPtr))
		{
			MsgAssert("메시 생성 실패");
		}

		return NewMesh;
	}

	// Getter
	std::shared_ptr<class Ext_DirectXVertexBuffer> GetVertexBuffer() { return VertexBufferPtr; }
	std::shared_ptr<class Ext_DirectXIndexBuffer> GetIndexBuffer() { return IndexBufferPtr; }

protected:
	
private:
	D3D11_PRIMITIVE_TOPOLOGY Topology = D3D11_PRIMITIVE_TOPOLOGY::D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
	std::shared_ptr<class Ext_DirectXVertexBuffer> VertexBufferPtr; // 상수버퍼 데이터 저장용
	std::shared_ptr<class Ext_DirectXIndexBuffer> IndexBufferPtr; // 인덱스버퍼 데이터 저장용
	
	void MeshSetting(); // InputAssembler1(), InputAssembler2() 호출
	void InputAssembler1(); // 인풋어셈블러 1단계 실시, IASetVertexBuffers(), IASetPrimitiveTopology() 실시
	void InputAssembler2(); // 인풋어셈블러 2단계 실시, IndexBufferSetting() 호출
};

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

void Ext_DirectXMesh::MeshSetting()
{
	InputAssembler1();
	InputAssembler2();
}

void Ext_DirectXMesh::InputAssembler1()
{
	if (nullptr == VertexBufferPtr)
	{
		MsgAssert("버텍스 버퍼가 존재하지 않아서 인풋어셈블러1 과정을 실행할 수 없습니다.");
		return;
	}

	VertexBufferPtr->VertexBufferSetting();

	Ext_DirectXDevice::GetContext()->IASetPrimitiveTopology(Topology);
}

void Ext_DirectXMesh::InputAssembler2()
{
	// 그리는 순서에 대한 데이터를 넣어준다 // 012023
	if (nullptr == IndexBufferPtr)
	{
		MsgAssert("인덱스 버퍼가 존재하지 않아서 인풋 어셈블러2 과정을 실행할 수 없습니다.");
		return;
	}

	IndexBufferPtr->IndexBufferSetting();
}

 

Mesh 클래스는 Ext_DirectXVertexBuffer, Ext_DirectXIndexBuffer 정보를 가지고 있는 클래스입니다. 아래와 같이 사용해봅시다.

// 삼각형
{
	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, 2, 1 };

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

// Rect
{
	std::vector<Ext_DirectXVertexData> ArrVertex;
	ArrVertex.resize(4);

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

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

	Ext_DirectXVertexBuffer::CreateVertexBuffer("Rect", ArrVertex);
	Ext_DirectXIndexBuffer::CreateIndexBuffer("Rect", ArrIndex);
	Ext_DirectXMesh::CreateMesh("Rect");
}

 

CreateVertexBuffer(), CreateIndexBuffer()을 실시할 때 Triangle, Rect라는 이름으로 저장해줬습니다. 이러면 CreateMesh()를 실시할 때 동일한 이름을 넣어줘서 내부에서 Triangle, Rect라는 이름의 Vertex Buffer, Index Buffer를 Find()한 뒤 저장해두는 것입니다. 이러면 Mesh 클래스 하나로 두 가지 클래스를 바로 활용할 수 있게 됩니다.

 

 

[Ext_DirectXMaterial]

 

다음은 Material 클래스입니다.

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

// 렌더링 파이프라인 세팅 실시를 위한 클래스
class Ext_DirectXMaterial : public Ext_ResourceManager<Ext_DirectXMaterial>
{
	friend class Ext_MeshComponentUnit;

public:
	// constrcuter destructer
	Ext_DirectXMaterial() {};
	~Ext_DirectXMaterial() {};

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

	// 머티리얼 생성
	static std::shared_ptr<class Ext_DirectXMaterial> CreateMaterial(const std::string_view& _Name)
	{
		std::shared_ptr<class Ext_DirectXMaterial> NewRes = Ext_ResourceManager<Ext_DirectXMaterial>::CreateNameResource(_Name);
		return NewRes;
	}

	// 머티리얼 세팅 실시
	void SetVertexShader(std::string_view _Name);
	void SetPixelShader(std::string_view _Name);
	void SetBlendState(std::string_view _Name);
	void SetDepthState(std::string_view _Name);
	void SetRasterizer(std::string_view _Name);

	// Getter, Setter
	std::shared_ptr<class Ext_DirectXVertexShader> GetVertexShader() { return VertexShader; }
	std::shared_ptr<class Ext_DirectXPixelShader> GetPixelShader() { return PixelShader; }

protected:
	
private:
	// 렌더링 파이프라인 세팅
	void MaterialSetting(); // 아래의 함수들 차례대로 실행
	void VertexShaderSetting();
	void HullShaderSetting();
	void TessellatorSetting();
	void DomainShaderSetting();
	void GeometryShaderSetting();
	void RasterizerSetting();
	void PixelShaderSetting();
	void OutputMergerSetting();

	std::shared_ptr<class Ext_DirectXVertexShader> VertexShader; // 버텍스 셰이더 저장
	std::shared_ptr<class Ext_DirectXPixelShader> PixelShader; // 픽셀 셰이더 저장
	
};

////////////////////// Ext_DirectXMaterial.cpp
#include "PrecompileHeader.h"
#include "Ext_DirectXMaterial.h"
#include <DirectX11_Base/Base_String.h>

#include "Ext_DirectXVertexShader.h"
#include "Ext_DirectXPixelShader.h"

void Ext_DirectXMaterial::SetVertexShader(std::string_view _Name)
{
	std::string UpperName = Base_String::ToUpper(_Name);
	VertexShader = Ext_DirectXVertexShader::Find(UpperName);

	if (nullptr == VertexShader)
	{
		MsgAssert("존재하지 않는 버텍스셰이더를 세팅할 순 없음");
		return;
	}
}

void Ext_DirectXMaterial::SetPixelShader(std::string_view _Name)
{
	std::string UpperName = Base_String::ToUpper(_Name);
	PixelShader = Ext_DirectXPixelShader::Find(UpperName);

	if (nullptr == PixelShader)
	{
		MsgAssert("존재하지 않는 픽셀 셰이더를 세팅할 순 없음");
	}
}

void Ext_DirectXMaterial::SetBlendState(std::string_view _Name)
{
}

void Ext_DirectXMaterial::SetDepthState(std::string_view _Name)
{
}

void Ext_DirectXMaterial::SetRasterizer(std::string_view _Name)
{
}

void Ext_DirectXMaterial::MaterialSetting()
{
	VertexShaderSetting();
	// HullShader();
	// Tessellator();
	// DomainShader();
	// GeometryShader();
	// Rasterizer();
	PixelShaderSetting();
	OutputMergerSetting();
}

void Ext_DirectXMaterial::VertexShaderSetting()
{
	if (nullptr == VertexShader)
	{
		MsgAssert("버텍스 쉐이더가 존재하지 않아서 버텍스 쉐이더 과정을 실행할 수 없습니다.");
		return;
	}

	VertexShader->VertexShaderSetting();
}

void Ext_DirectXMaterial::HullShaderSetting()
{

}
void Ext_DirectXMaterial::TessellatorSetting()
{

}
void Ext_DirectXMaterial::DomainShaderSetting()
{

}
void Ext_DirectXMaterial::GeometryShaderSetting()
{
}

void Ext_DirectXMaterial::RasterizerSetting()
{
}

void Ext_DirectXMaterial::PixelShaderSetting()
{
	if (nullptr == PixelShader)
	{
		MsgAssert("픽셀 쉐이더가 존재하지 않아서 픽셀 쉐이더 과정을 실행할 수 없습니다.");
		return;
	}

	PixelShader->PixelShaderSetting();
}

void Ext_DirectXMaterial::OutputMergerSetting()
{
}

 

머티리얼 클래스는 InputAssembler 이외의 단계를 진행하기 위한 정보들을 담고, 렌더링 파이프라인 세팅 순간에 그 정보들을 가져와 Setting을 실시해주는 클래스입니다.

 

물론 각 정보들은 이미 만들어져 있어야하며, 지금은 Vertex Shader와 Pixel Shader만 만들어졌기 때문에 이 값들만 저장하고 있는 클래스가 될 것입니다. 나중에 단계들을 배우고 추가하게 되면 여기에 바로바로 추가해주면 됩니다. 우선은 아래와 같이 사용하면 됩니다.

// 일반(단일 메시)
{
	std::shared_ptr<Ext_DirectXMaterial> NewRenderingPipeline = Ext_DirectXMaterial::CreateMaterial("Basic");
	NewRenderingPipeline->SetVertexShader("Basic_VS");
	NewRenderingPipeline->SetPixelShader("Basic_PS");
	NewRenderingPipeline->SetBlendState("BaseBlend"); // 아직 없음
	NewRenderingPipeline->SetDepthState("EngineDepth"); // 아직 없음
	NewRenderingPipeline->SetRasterizer("EngineRasterizer"); // 아직 없읍
}

// 다양한 텍스쳐가 있는 메시
{
	std::shared_ptr<Ext_DirectXMaterial> NewRenderingPipeline = Ext_DirectXMaterial::CreateMaterial("PBR");
	NewRenderingPipeline->SetVertexShader("Basic_VS");
	NewRenderingPipeline->SetPixelShader("PBR_PS");
	NewRenderingPipeline->SetBlendState("BaseBlend"); // 아직 없음
	NewRenderingPipeline->SetDepthState("EngineDepth"); // 아직 없음
	NewRenderingPipeline->SetRasterizer("EngineRasterizer"); // 아직 없음
}

 

이러면 Basic, PBR이라는 이름으로 Material들이 값들을 하나로 묶어서 저장하기 때문에, 클래스 하나만 써서 바로 활용할 수 있게 됩니다.

 

 

[Set Unit]

 

이제 다시 MeshComponentUnitInitialize() 함수로 돌아가보겠습니다. 함수 내에서는 앞서 생성해둔 Mesh와 Material 정보를 가져와 자신에게 세팅해줍니다. 그리고 InputLayout을 Create해서 Mesh, Material, InputLayout 값을 지니고 있게 됩니다.

// 메시 컴포넌트 유닛 생성 시 호출, Mesh, Material, ConstantBuffer 세팅
void Ext_MeshComponentUnit::MeshComponentUnitInitialize(std::string_view _MeshName, std::string_view _MaterialName)
{
	Mesh = Ext_DirectXMesh::Find(_MeshName); // 메시 설정
	Material = Ext_DirectXMaterial::Find(_MaterialName); // 머티리얼 설정
	
	if (nullptr == Mesh || nullptr == Material)
	{
		MsgAssert("존재하지 않는 메시나 머티리얼을 메시유닛에 넣을 수는 없습니다.")
	}

	InputLayout->CreateInputLayout(Mesh->GetVertexBuffer(), Material->GetVertexShader()); // InputLayout 설정

	// 상수버퍼 세팅
	// [1] 버텍스 셰이더 정보 가져오기
	const Ext_DirectXBufferSetter& VertexShaderBuffers = Material->GetVertexShader()->GetBufferSetter();
	BufferSetter.Copy(VertexShaderBuffers);

	// [2] 픽셀 셰이더 정보 가져오기
	const Ext_DirectXBufferSetter& PixelShaderBuffers = Material->GetPixelShader()->GetBufferSetter();
	BufferSetter.Copy(PixelShaderBuffers);

	// [3] 트랜스폼 상수버퍼 세팅하기
	const TransformData& Data = *(OwnerMeshComponent.lock()->GetTransform()->GetTransformData().get());
	BufferSetter.SetConstantBufferLink("TransformData", Data);

	// [4] 카메라에 넣기
	GetOwnerMeshComponent().lock()->GetOwnerCamera().lock()->PushMeshComponentUnit(GetSharedFromThis<Ext_MeshComponentUnit>(), RenderPath::Unknown);
}

 

이러면 MeshComponent의 계층 구조는 다음과 같아질 것입니다.

MeshComponent
└── MeshComponentUnit
    ├── Ext_DirectXMesh
    │   ├── Ext_DirectXVertexBuffer
    │   └── Ext_DirectXIndexBuffer
    ├── Ext_DirectXMaterial
    │   ├── Ext_DirectXVertexShader
    │   ├── Ext_DirectXPixelShader
    │   ├── Ext_DirectXBlend
    │   ├── Ext_DirectXDepth
    │   └── Ext_DirectXRasterizer
    └── Ext_DirectXInputLayout

 

InputLayout을 여기서 Create() 하는 이유는 InputLayout이 Vertex Shader 컴파일 후 호출되어야 하기 때문입니다.

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();

	// CreateInputLayout은 정점 버퍼 구조와 셰이더 입력 구조 간의 매핑을 정의
	HRESULT hr = Ext_DirectXDevice::GetDevice()->CreateInputLayout
	(
		&LayOutInfo[0],
		static_cast<UINT>(LayOutInfo.size()),
		_VertexShader->GetBinaryCode()->GetBufferPointer(),
		_VertexShader->GetBinaryCode()->GetBufferSize(),
		&InputLayout
	);

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

 

const D3D11_INPUT_ELEMENT_DESC* 정점 입력 요소 배열의 시작 주소, 각 요소가 어떤 데이터인지 정의
UINT 위 배열의 요소 수(입력 요소가 몇개인가)
const void* 정점 셰이더의 바이트 코드 시작 주소, 여기에는 입력 시그니처가 포함되어 있어야 함
SIZE_T 위 바이트코드의 길이
ID3D11InputLayout** 호출의 결과로 생성될 InputLayout 객체의 포인터 주소 저장->IASetInputLayout에서 활용

 

위의 과정이 완료되면 셰이더 컴파일 진행 후 생성된 리소스 정보들(바인딩 슬롯은 몇번째인지, 슬롯 이름은 무엇인지)을 가져와서 저장해줍니다.

// 버퍼 세팅 복사/붙여넣기 실시
void Ext_DirectXBufferSetter::Copy(const Ext_DirectXBufferSetter& _OtherBufferSetter)
{
	for (const std::pair<std::string, ConstantBufferSetter>& Setter : _OtherBufferSetter.ConstantBufferSetters)
	{
		ConstantBufferSetters.insert(Setter);
	}
    //...
}

 

이러면 Unit에 있는 Setter에 값이 복사/붙여넣기 될 것입니다.

 

상수버퍼에 한해서는 매 프레임마다 실시간으로 렌더링 과정에 적용될 수 있도록 플래그를 세워뒀습니다. 근데 당연히 이 값은 어딘가에 존재해야하기 떄문에(실체가 있어야지 활용할 것), 할당된 상태여야 합니다.

 

하지만 리플렉션 과정에서는 값이 할당된 상태가 이니기 떄문에 주소 위치와 크기를 특정할 수 없어서 실제로 이 값을 활용하는 Unit에서 SetConstantBufferLink()를 실시해줍니다.

// 호출
const TransformData& Data = *(OwnerMeshComponent.lock()->GetTransform()->GetTransformData().get());
BufferSetter.SetConstantBufferLink("TransformData", Data);

// 상수 버퍼에 한하여 호출, cbuffer 슬롯 이름과 크기를 나중에 따로 지정해주기 위해 호출하는 함수
void Ext_DirectXBufferSetter::SetConstantBufferLink(std::string_view _Name, const void* _Data, UINT _Size)
{
	std::string UpperName = Base_String::ToUpper(_Name);
	std::multimap<std::string, ConstantBufferSetter>::iterator FindIter = ConstantBufferSetters.find(UpperName);

	if (ConstantBufferSetters.end() == FindIter)
	{
		MsgAssert("존재하지 않는 상수버퍼를 세팅하려고 했습니다." + UpperName);
		return;
	}

	std::multimap<std::string, ConstantBufferSetter>::iterator NameStartIter = ConstantBufferSetters.lower_bound(UpperName);
	std::multimap<std::string, ConstantBufferSetter>::iterator NameEndIter = ConstantBufferSetters.upper_bound(UpperName);

	for (; NameStartIter != NameEndIter; ++NameStartIter)
	{
		ConstantBufferSetter& BufferSetter = NameStartIter->second;

		if (BufferSetter.ConstantBuffer->GetBufferSize() != _Size)
		{
			MsgAssert("상수버퍼와 세팅하려는 데이터의 크기가 다릅니다. 상수버퍼 : " + std::to_string(BufferSetter.ConstantBuffer->GetBufferSize()) + "유저가 세팅한 데이터" + std::to_string(_Size) + UpperName);
			return;
		}

		BufferSetter.CPUData = _Data;
		BufferSetter.CPUDataSize = _Size;
	}
}

 

여기까지 완료했다면 정상적으로 Unit 마다의 렌더링 파이프라인 단계를 진행할 준비가 마무리 된 것입니다.

+ Recent posts