6. Material
기존 방식으로는 물체를 만들 때 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);