매 프레임마다 동일한 메쉬와 머티리얼을 가진 오브젝트들을 하나의 버퍼에 묶어 GPU에 전달해야 한다. 이 역할을 하는 클래스가 InstancingBuffer다.
struct InstancingData
{
Matrix world; // 인스턴스 개별의 월드 행렬
};
class InstancingBuffer
{
public:
void ClearData(); // 프레임마다 초기화
void AddData(InstancingData&); // 오브젝트 world 추가
void PushData(); // GPU에 Map/Unmap 후 전송
private:
vector<InstancingData> _data;
shared_ptr<VertexBuffer> _instanceBuffer;
uint32 _maxCount;
};
CreateBuffer: 최대 인스턴스 수 기준으로 CPU write가 가능한 버퍼 생성PushData: Map → memcpy → Unmap → GPU 전송이 Buffer 하나만 잘 만들면, 다수의 오브젝트를 대표 1명이 GPU에 한 번에 전송 가능하다.
기존에는 각각의 GameObject가 알아서 렌더링을 호출했지만, 인스턴싱에서는 대표 1명이 호출해야 한다. 이를 위해 InstancingManager가 등장한다.
Render(vector<GameObject>) 함수 하나로 모든 인스턴싱 관리Mesh + Material을 가진 오브젝트들을 묶고, 대표를 정해 그리게 한다void InstancingManager::RenderMeshRenderer(vector<shared_ptr<GameObject>>& objs)
{
map<InstanceID, vector<shared_ptr<GameObject>>> cache;
// 1. InstanceID 기준으로 분류
for (auto& obj : objs)
{
if (!obj->GetMeshRenderer()) continue;
auto id = obj->GetMeshRenderer()->GetInstanceID();
cache[id].push_back(obj);
}
// 2. 각각의 그룹 처리
for (auto& [id, group] : cache)
{
for (auto& obj : group)
{
InstancingData data{ obj->GetTransform()->GetWorldMatrix() };
AddData(id, data);
}
// 대표가 인스턴싱으로 렌더링
group[0]->GetMeshRenderer()->RenderInstancing(_buffers[id]);
}
}
기존 방식은 각 오브젝트가 자기 Update()에서 렌더링을 호출했기 때문에, 수천 개 오브젝트가 모두 Draw 호출을 반복하게 됨 → 성능 저하
RenderInstancing(shared_ptr<InstancingBuffer>)void MeshRenderer::RenderInstancing(shared_ptr<InstancingBuffer>& buffer)
{
if (!_mesh || !_material) return;
_material->Update(); // Light, Tex, Shader 세팅
_mesh->GetVertexBuffer()->PushData();
_mesh->GetIndexBuffer()->PushData();
buffer->PushData(); // 인스턴스 데이터 전송
_material->GetShader()->DrawIndexedInstanced(0, _pass,
_mesh->GetIndexBuffer()->GetCount(), buffer->GetCount());
}
그리고 기존 Update()는 제거. InstancingManager가 호출하는 방식으로 완전히 변경.
void MeshInstancingDemo::Update()
{
_camera->Update();
RENDER->Update();
// 조명 세팅
LightDesc light;
light.ambient = Vec4(0.4f);
light.diffuse = Vec4(1.f);
light.specular = Vec4(0.1f);
light.direction = Vec3(1.f, 0.f, 1.f);
RENDER->PushLightData(light);
// 모든 오브젝트를 InstancingManager에 위임
INSTANCING->Render(_objs);
}
기존에 _mesh, _instanceBuffer, _worlds 등을 사용하던 복잡한 코드는 모두 사라졌고, 관리 책임은 전적으로 InstancingManager로 이전.
GameObjects → InstancingManager::Render
↓
분류 (Mesh + Material 기준)
↓
InstancingBuffer에 world 정보 저장
↓
대표 하나가 RenderInstancing 호출
↓
DrawIndexedInstanced로 GPU 한방 렌더링!
ClearData()로 리셋AddData()DrawIndexedInstanced() 호출향후 애니메이션과 같은 기능에서 각 인스턴스의 ID가 필요할 수 있다면 VS_IN에 다음과 같이 추가한다.
uint instanceID : SV_InstanceID;
이 값은 쉐이더에서 현재 인스턴스가 몇 번째인지 알려주는 값이며, 예를 들어 애니메이션 배열에서 자신의 애니메이션 데이터를 가져올 때 유용하게 쓰인다.