헛둘이 2023. 1. 18. 11:00

기존 방식으로는 물체를 만들 때 Shader, Texture, Mesh를 외부에 선언해서 합쳐주는 방식으로 사용했었음(테스트 목적)

그런데 이렇게 하면 물체가 많아지면 감당할 수 없음

 

그럼 하나의 객체에 묶어줘야 하나? 반은 맞다

하나의 묶어주는 대신 Material이라는 객체를 별도로 파서 같은 성질을 가진 객체들은 그 Material을 적용하는게 좋음

 

즉, Material은 쉐이더에서 관리하는 인자들을 모아서 하나의 클래스로 관리하는 것이다.

 

*Material을 추가하면서 발생하는 변경점

1. ConstantBuffer의 Init에서 어떤 레지스터에 사용할 것인지를 넣어준다

->Transform 용도, Material 용도 등

->Init 함수에서 enum class를 받는데, 그 이유는 용도에 따라 확실히 구분하기 위해서( 실수하지 않기 위해서 )

->넣어준 reg는 이후 Push_Data에서 사용됨

void ConstantBuffer::Init(CBV_REGISTER reg, uint32 size, uint32 count)
{
	_reg = reg;
	_elementSize = (size + 255) & ~255;
	_elementCount = count;

	CreateBuffer();
	CreateView();
}

-

2. Push_Data에서 TableDescriptorHeap으로 바로 쏴준다

-> 기존에는 데이터를 버퍼에 저장하고 그 핸들을 반환해서 외부에서 테이블에 넣어주는 방식이었다면

-> 이제는 Push_Data에서 그냥 Init에서 받은 레지스터에 바로 쏴주는 방식

void ConstantBuffer::PushData(void* buffer, uint32 size)
{
	assert(_currentIndex < _elementCount);
	assert(_elementSize == ((size + 255) & ~255));

	memcpy(&_mappedBuffer[_currentIndex * _elementSize], buffer, size);

	D3D12_CPU_DESCRIPTOR_HANDLE cpuHandle = GetCpuHandle(_currentIndex);
	GEngine->GetTableDescHeap()->SetCBV(cpuHandle, _reg);

	_currentIndex++;
}

 

3. ConstantBuffer도 이제 Transfrom 용도, Material 용도 등 많은 버퍼들이 등장하므로 벡터로 관리

CreateConstantBuffer(CBV_REGISTER::b0, sizeof(Transform), 256);
CreateConstantBuffer(CBV_REGISTER::b1, sizeof(MaterialParams), 256);

- b0번 레지스터는 Transform 용도로 관리하며,

- b1번 레지스터는 Material에 관한 용도로 관리.

- MaterialParams는 각각의 자료형에 대응하는 데이터들을 배열 형태로 가지고 있게 된다.

enum
{
	MATERIAL_INT_COUNT = 5,
	MATERIAL_FLOAT_COUNT = 5,
	MATERIAL_TEXTURE_COUNT = 5,
};

//버퍼에 넘겨줄 파라미터

struct MaterialParams
{
	void SetInt(uint8 index, int32 value) { intParams[index] = value; }
	void SetFloat(uint8 index, float value) { floatParams[index] = value; }

	array<int32, MATERIAL_INT_COUNT> intParams;
	array<float, MATERIAL_FLOAT_COUNT> floatParams;
};

- 따라서 MaterialParams는 그 자체로 b1 파라미터로 전달될 수 있게 되고,

- 인자는 Material 클래스의 헬퍼함수를 통해 전달된다

cbuffer TEST_B0 : register(b0)
{
    float4 offset0;
}

cbuffer MATERIAL_PARAMS : register(b1)
{
    int int_0;
    int int_1;
    int int_2;
    int int_3;
    int int_4;
    float float_0;
    float float_1;
    float float_2;
    float float_3;
    float float_4;
}

Texture2D tex_0 : register(t0);
Texture2D tex_1 : register(t1);
Texture2D tex_2 : register(t2);
Texture2D tex_3 : register(t3);
Texture2D tex_4 : register(t4);



SamplerState sam_0 : register(s0);

- 때문에 쉐이더 코드도 위와 같이 변하게 된다.

 

void Material::Update()
{
	CONST_BUFFER(CONSTANT_BUFFER_TYPE::MATERIAL)->PushData(&_params, sizeof(_params));

	for (size_t i = 0; i < _textures.size(); ++i)
	{
		if (nullptr == _textures[i])
			continue;

		SRV_REGISTER reg = SRV_REGISTER(static_cast<int8>(SRV_REGISTER::t0) + i);
		GEngine->GetTableDescHeap()->SetSRV(_textures[i]->GetCpuHandle(), reg);
	}

	_shader->Update();
}

- 그리고 인자 전달은 Mesh의 Render에서 Material의 Update를 호출함으로써

1. Push_Data에서 TableDescriptorHeap으로 데이터를 쏴준다

2. for문을 통해 Texture로 데이터를 세팅한다

3. shader의 Update를 통해 쉐이더의 변경점을 적용 (이후에 사용될 것 같음)

 

4. 게임에서 Mesh, Shader를 묶어주는 부분 수정

mesh->Init(vec, indexVec);

shared_ptr<Shader> shader = make_shared<Shader>();
shared_ptr<Texture> texture = make_shared<Texture>();
shader->Init(L"..\\Resources\\Shader\\default.hlsli");
texture->Init(L"..\\Resources\\Texture\\veigar.jpg");

shared_ptr<Material> material = make_shared<Material>();	
material->SetShader(shader);
material->SetFloat(0, 0.1f);
material->SetFloat(1, 0.2f);	
material->SetFloat(2, 0.3f);
material->SetTexture(0, texture);
mesh->SetMaterial(material);