동일한 개체를 여러개 렌더링해야할 경우 개체 하나 하나를 렌더링하는 것은 굉장히 많은 연산을 요구하게 된다.
이에 대한 해결방안으로 Draw Call을 그려야할 개체 수 만큼 하지 않고 한번의 Draw Call로 여러개의 개체를 그리도록 요청하는 기술을 Instancing이라고 한다.
//InstancingManager.cpp
void InstancingManager::Render(vector<shared_ptr<GameObject>>& gameObjects)
{
map<uint64, vector<shared_ptr<GameObject>>> cache;
for (shared_ptr<GameObject>& gameObject : gameObjects)
{
const uint64 instanceId = gameObject->GetMeshRenderer()->GetInstanceID();
cache[instanceId].push_back(gameObject);
}
for (auto& pair : cache)
{
const vector<shared_ptr<GameObject>>& vec = pair.second;
// 물체가 하나
if (vec.size() == 1)
{
vec[0]->GetMeshRenderer()->Render();
}
else // 물체가 두개 이상
{
const uint64 instanceId = pair.first;
for (const shared_ptr<GameObject>& gameObject : vec)
{
InstancingParams params;
params.matWorld = gameObject->GetTransform()->GetLocalToWorldMatrix();
params.matWV = params.matWorld * Camera::S_MatView;
params.matWVP = params.matWorld * Camera::S_MatView * Camera::S_MatProjection;
AddParam(instanceId, params);
}
shared_ptr<InstancingBuffer>& buffer = _buffers[instanceId];
vec[0]->GetMeshRenderer()->Render(buffer);
}
}
}
void InstancingManager::ClearBuffer()
{
for (auto& pair : _buffers)
{
shared_ptr<InstancingBuffer>& buffer = pair.second;
buffer->Clear();
}
}
void InstancingManager::AddParam(uint64 instanceId, InstancingParams& data)
{
if (_buffers.find(instanceId) == _buffers.end())
_buffers[instanceId] = make_shared<InstancingBuffer>();
_buffers[instanceId]->AddData(data);
}
//MeshRender.cpp
void MeshRenderer::Render()
{
GetTransform()->PushData();
_material->PushGraphicsData();
_mesh->Render();
}
void MeshRenderer::Render(shared_ptr<InstancingBuffer>& buffer)
{
buffer->PushData();
_material->PushGraphicsData();
_mesh->Render(buffer);
}
// InstancingBuffer.cpp
void InstancingBuffer::PushData()
{
const uint32 dataCount = GetCount();
if (dataCount > _maxCount)
Init(dataCount);
const uint32 bufferSize = dataCount * sizeof(InstancingParams);
void* dataBuffer = nullptr;
D3D12_RANGE readRange{ 0, 0 };
_buffer->Map(0, &readRange, &dataBuffer);
memcpy(dataBuffer, &_data[0], bufferSize);
_buffer->Unmap(0, nullptr);
_bufferView.BufferLocation = _buffer->GetGPUVirtualAddress();
_bufferView.StrideInBytes = sizeof(InstancingParams);
_bufferView.SizeInBytes = bufferSize;
}
InstancingManager.cpp 를 보면 하나의 개체에 대한 렌더링 요청과 두개 이상의 개체에 대한 렌더링 요청을 오버로딩된 Render함수를 사용하여 각기 다른 방식으로 처리하는 것을 알 수 있다.
MeshRenderer.cpp 를 통해 Instancing 객체의 PushData를 활용하여 Instance의 개수를 버퍼에 넣어준다.