Draw() 명령을 보낼 때마다 발생한다.Draw()하면 1000번의 드로우콜이 생긴다._shader->DrawIndexed(0, _pass, mesh->indexBuffer->GetCount(), 0 ,0);
드로우콜은 GPU 성능보다는 CPU → GPU 명령 전달 오버헤드로 인해 병목을 만든다.
// instanceCount에 1000을 넣게 되면, 1000개의 동일 모델을 한 번에 렌더링
deviceContext->DrawIndexedInstanced(indexCount, instanceCount, 0, 0, 0);
| 항목 | 드로우콜 | 인스턴싱 |
|---|---|---|
| 호출 | 횟수 | 객체마다 1번 |
| CPU 오버헤드 | 높음 | 낮음 |
| GPU 처리 | 동일 | 동일 모델을 반복 |
| 사용 예시 | 전부 다른 모델 | 같은 모델 다수 |
| 예시 | 1000개의 다른 나무 | 같은 나무 1000그루 |
- 언리얼에서는
InstancedStaticMeshComponent나HierarchicalInstancedStaticMeshComponent가 이 기능을 활용- DirectX에서는
DrawInstanced()나DrawIndexedInstanced()로 구현- 단, 머티리얼이나 셰이더가 다르면 인스턴싱 불가 (즉, 동일 렌더 상태여야 함).
#pragma once
#include "IExecute.h"
class InstancingDemo : public IExecute
{
public:
void Init() override;
void Update() override;
void Render() override;
private:
shared_ptr<Shader> _shader;
shared_ptr<GameObject> _camera;
vector<shared_ptr<GameObject>> _objs;
};
#include "pch.h"
#include "InstancingDemo.h"
#include "Camera.h"
#include "CameraScript.h"
#include "Material.h"
#include "MeshRenderer.h"
void InstancingDemo::Init()
{
RESOURCES->Init();
_shader = make_shared<Shader>(L"19.Instancing.fx");
// Camera
_camera = make_shared<GameObject>();
_camera->GetOrAddTransform()->SetPosition(Vec3{ 0.f, 0.f, -10.f });
_camera->AddComponent(make_shared<Camera>());
_camera->AddComponent(make_shared<CameraScript>());
// Material
{
shared_ptr<Material> material = make_shared<Material>();
material->SetShader(_shader);
auto texture = RESOURCES->Load<Texture>(L"Veigar", L"../Resources/Textures/veigar.jpg");
material->SetDiffuseMap(texture);
MaterialDesc& desc = material->GetMaterialDesc();
desc.ambient = Vec4(1.f);
desc.diffuse = Vec4(1.f);
desc.specular = Vec4(1.f);
RESOURCES->Add(L"Veigar", material);
}
for (int32 i = 0; i < 100; i++)
{
auto obj = make_shared<GameObject>();
obj->GetOrAddTransform()->SetPosition(Vec3(rand() % 10, 0, rand() % 10));
obj->AddComponent(make_shared<MeshRenderer>());
{
obj->GetMeshRenderer()->SetMaterial(RESOURCES->Get<Material>(L"Veigar"));
}
{
auto mesh = RESOURCES->Get<Mesh>(L"Sphere");
obj->GetMeshRenderer()->SetMesh(mesh);
}
_objs.push_back(obj);
}
RENDER->Init(_shader);
}
void InstancingDemo::Update()
{
_camera->Update();
RENDER->Update();
{
LightDesc lightDesc;
lightDesc.ambient = Vec4(0.f);
lightDesc.diffuse = Vec4(0.f);
lightDesc.specular = Vec4(0.f);
lightDesc.direction = Vec3(1.f, 0.f, 1.f); // right, look
RENDER->PushLightData(lightDesc);
}
for (auto& obj : _objs)
{
obj->Update();
}
}
void InstancingDemo::Render()
{
}
#include "00.Global.fx"
#include "00.Light.fx"
struct VS_In
{
float4 position : POSITION;
float2 uv : TEXCOORD;
float3 normal : NORMAL;
float3 tangent : TANGENT;
// INSTANCING
};
struct VS_OUT
{
float4 position : SV_POSITION;
float3 worldPosition : POSITION1;
float2 uv : TEXCOORD;
float3 normal : NORMAL;
};
VS_OUT VS(VS_In input)
{
VS_OUT output;
output.position = mul(input.position, W);
output.worldPosition = output.position;
output.position = mul(output.position, VP);
output.uv = input.uv;
output.normal = input.normal;
return output;
}
float4 PS(VS_OUT input) : SV_TARGET
{
float4 color = DiffuseMap.Sample(LinearSampler, input.uv);
return color;
}
technique11 T0
{
PASS_VP(P0, VS, PS)
};
void Game::Update()
{
TIME->Update();
INPUT->Update();
ShowFps();
GRAPHICS->RenderBegin();
GUI->Update();
_desc.app->Update();
_desc.app->Render();
GUI->Render();
GRAPHICS->RenderEnd();
}
void Game::ShowFps()
{
uint32 fps = GET_SINGLE(TimeManager)->GetFps();
WCHAR text[100] = L"";
::wsprintf(text, L"FPS : %d", fps);
::SetWindowText(_desc.hWnd, text);
}


shader랑 material은 달라지지않고, World값만 변경해주는 작업Create()에서 slot값을 받아주고, cpu, gpu정보도 넘겨주는 방식으로 수정PushData()를 추가해서 IASetVertexBuffer를 내부에서 해주는 방향으로 수정class VertexBuffer
{
template<typename T>
void Create(const vector<T>& vertices, uint32 slot = 0, bool cpuWrite = false, bool gpuWrite = false)
{
_stride = sizeof(T);
_count = static_cast<uint32>(vertices.size());
_slot = slot;
_cpuWrite = cpuWrite;
_gpuWrite = gpuWrite;
D3D11_BUFFER_DESC desc;
ZeroMemory(&desc, sizeof(desc));
desc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
desc.ByteWidth = (uint32)(_stride * _count);
if (cpuWrite == false && gpuWrite == false)
{
desc.Usage = D3D11_USAGE_IMMUTABLE; // CPU Read, GPU Read
}
else if (cpuWrite == true && gpuWrite == false)
{
desc.Usage = D3D11_USAGE_DYNAMIC; // CPU Write, GPU Read
desc.CPUAccessFlags = D3D10_CPU_ACCESS_WRITE;
}
else if (cpuWrite == false && gpuWrite == true) // CPU Read, GPU Write
{
desc.Usage = D3D11_USAGE_DEFAULT;
}
else
{
desc.Usage = D3D11_USAGE_STAGING;
desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ | D3D11_CPU_ACCESS_WRITE;
}
D3D11_SUBRESOURCE_DATA data;
ZeroMemory(&data, sizeof(data));
data.pSysMem = vertices.data();
HRESULT hr = DEVICE->CreateBuffer(&desc, &data, _vertexBuffer.GetAddressOf());
CHECK(hr);
}
void PushData()
{
DC->IASetVertexBuffers(_slot, 1, _vertexBuffer.GetAddressOf(), &_stride, &_offset);
}
private:
uint32 _slot = 0;
bool _cpuWrite = false;
bool _gpuWrite = false;
}
PushData()함수 추가class IndexBuffer
{
void PushData()
{
DC->IASetIndexBuffer(_indexBuffer.Get(), DXGI_FORMAT_R32_UINT, 0);
}
}
MeshRenderer가 Update를 하는 부분을 대표적으로 한 오브젝트만 업데이트 해주는 방향으로 수정InstanceBuffer를 통해서 PushData()를 해주는 방향으로 수정class InstancingDemo : public IExecute
{
// 추가 부분
private:
// INSTANCING
shared_ptr<Mesh> _mesh;
shared_ptr<Material> _material;
vector<Matrix> _worlds;
shared_ptr<VertexBuffer> _instanceBuffer;
};
void InstancingDemo::Init()
{
// 위 내용은 동일해서 생략 //
RENDER->Init(_shader);
// INSTANCING
_instanceBuffer = make_shared<VertexBuffer>();
for (auto& obj : _objs)
{
Matrix world = obj->GetTransform()->GetWorldMatrix();
_worlds.push_back(world);
}
_instanceBuffer->Create(_worlds, /*slot*/1);
}
void InstancingDemo::Update()
{
_camera->Update();
RENDER->Update();
{
LightDesc lightDesc;
lightDesc.ambient = Vec4(0.f);
lightDesc.diffuse = Vec4(0.f);
lightDesc.specular = Vec4(0.f);
lightDesc.direction = Vec3(1.f, 0.f, 1.f); // right, look
RENDER->PushLightData(lightDesc);
}
/*for (auto& obj : _objs)
{
obj -> update();
}*/
_material->Update();
//auto world = GetTransform()->GetWorldMatrix();
//RENDER->PushTransformData(TransformDesc{ world });
_mesh->GetVertexBuffer()->PushData();
_instanceBuffer->PushData();
_mesh->GetIndexBuffer()->PushData();
// shader->DrawIndexed(0, 0, _mesh->GetIndexBuffer()->GetCount(), 0, 0);
_shader->DrawIndexedInstanced(0, 0, _mesh->GetIndexBuffer()->GetCount(), _objs.size());
}
Input.world를 통해서 물체마다 world를 세팅해줄 수 있다.struct VS_In
{
float4 position : POSITION;
float2 uv : TEXCOORD;
float3 normal : NORMAL;
float3 tangent : TANGENT;
// INSTANCING
matrix world : INST;
};
VS_OUT VS(VS_In input)
{
VS_OUT output;
output.position = mul(input.position, input.world);
output.worldPosition = output.position;
output.position = mul(output.position, VP);
output.uv = input.uv;
output.normal = input.normal;
return output;
}
