버텍스, 인덱스, 메테리얼 등이 동일한 물체가 여러 개로 존재한다면,
해당 정보들을 계속해서 받는 것이 아니라 한번 받은 정보를 재사용하여
그려주도록 함
인풋 어셈블러에 정점 정보들과
해당 정점들을 사용하는 물체들의 position 값을 같이 넘겨주어
instancing 할 수 있음
instancing buffer 클래스
struct InstancingParams
{
Matrix matWorld;
Matrix matWV;
Matrix matWVP;
};
class InstancingBuffer
{
public:
...
void Init(uint32 maxCount = 10);
void Clear();
void AddData(InstancingParams& params);
void PushData();
public:
...
private:
uint64 _instanceId = 0;
ComPtr<ID3D12Resource> _buffer;
D3D12_VERTEX_BUFFER_VIEW _bufferView;
uint32 _maxCount = 0;
vector<InstancingParams> _data;
};
인풋 어셈블러에 넣을 물체들의 position 관련 행렬들을 버텍스 버퍼로 구현
instancing manager 클래스
카메라가 하던 렌더 역할을 이전한 싱글턴 매니저 클래스
class InstancingManager
{
DECLARE_SINGLE(InstancingManager);
public:
void Render(vector<shared_ptr<GameObject>>& gameObjects);
...
private:
map<uint64, shared_ptr<InstancingBuffer>> _buffers;
};
물체를 ID 별로 매핑하여 인스턴싱 버퍼에 넣어 관리
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)
{
...
if (vec.size() == 1)
{
vec[0]->GetMeshRenderer()->Render();
}
else
{
const uint64 instanceId = pair.first;
for (const shared_ptr<GameObject>& gameObject : vec)
{
InstancingParams params;
...
AddParam(instanceId, params);
}
shared_ptr<InstancingBuffer>& buffer = _buffers[instanceId];
vec[0]->GetMeshRenderer()->Render(buffer);
}
}
}
렌더 시 동일 물체가 중복되는 경우
버퍼에 position 관련 정보를 넣고 instancing 방식으로 렌더
object 클래스 수정
class Object
{
...
protected:
uint32 _id = 0;
};
Object::Object(OBJECT_TYPE type) : _objectType(type)
{
static uint32 idGenerator = 1;
_id = idGenerator;
idGenerator++;
}
오브젝트마다 각각 아이디를 갖도록 구현
mesh, mesh renderer 클래스 수정
메쉬와 메테리얼을 렌더하던 메쉬 렌더러가 인스턴싱 버퍼를 통하여 렌더할 수 있도록 수정
uint64 MeshRenderer::GetInstanceID()
{
...
InstanceID instanceID{ _mesh->GetID(), _material->GetID() };
return instanceID.id;
}
void MeshRenderer::Render(shared_ptr<InstancingBuffer>& buffer)
{
buffer->PushData();
...
_mesh->Render(buffer);
}
void Mesh::Render(shared_ptr<InstancingBuffer>& buffer)
{
D3D12_VERTEX_BUFFER_VIEW bufferViews[] = { _vertexBufferView, buffer->GetBufferView() };
GRAPHICS_CMD_LIST->IASetVertexBuffers(0, 2, bufferViews);
GRAPHICS_CMD_LIST->IASetIndexBuffer(&_indexBufferView);
...
GRAPHICS_CMD_LIST->DrawIndexedInstanced(_indexCount, buffer->GetCount(), 0, 0, 0);
}
메쉬 렌더러는 메쉬와 메테리얼 가지고 인스턴스 아이디를 생성
메쉬와 메테리얼이 모두 같은 객체는 인스턴싱 버퍼 뷰를 사용하게 됨
정점 정보가 들어가는 버텍스 버퍼 뷰와
위치 관련 행렬이 들어가는 인스턴싱 버퍼 뷰를
D3D12_VERTEX_BUFFER_VIEW에 같이 넣어 draw 요청
shader 클래스 수정
void Shader::CreateGraphicsShader(const wstring& path, ShaderInfo info, const string& vs, const string& ps, const string& gs)
{
...
D3D12_INPUT_ELEMENT_DESC desc[] =
{
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
{ "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
{ "NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 20, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
{ "TANGENT", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 32, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
{ "W", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, 0, D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA, 1},
{ "W", 1, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, 16, D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA, 1},
{ "W", 2, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, 32, D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA, 1},
{ "W", 3, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, 48, D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA, 1},
{ "WV", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, 64, D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA, 1},
{ "WV", 1, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, 80, D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA, 1},
{ "WV", 2, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, 96, D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA, 1},
{ "WV", 3, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, 112, D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA, 1},
{ "WVP", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, 128, D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA, 1},
{ "WVP", 1, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, 144, D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA, 1},
{ "WVP", 2, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, 160, D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA, 1},
{ "WVP", 3, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, 176, D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA, 1},
};
...
}
1번 슬롯에는 행렬들의 행들을 각각 넣어주도록 수정
D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA 플래그를 통하여
정보가 인스턴스마다라는 것을 알려줌
deferred shader 파일 수정
struct VS_IN
{
...
row_major matrix matWorld : W;
row_major matrix matWV : WV;
row_major matrix matWVP : WVP;
uint instanceID : SV_InstanceID;
};
추가된 정보들을 받을 수 있도록 VS_IN 데이터 추가
SV_InstanceID는 systemp value로 자동으로 쉐이더에서 붙게 됨
VS_OUT VS_Main(VS_IN input)
{
...
if (g_int_0 == 1)
{
output.pos = mul(float4(input.pos, 1.f), input.matWVP);
output.uv = input.uv;
...
}
...
return output;
}
전역 변수 g_int_0을 통해서 인스턴싱 판별
인스턴싱이 적용된 경우 input에서 데이터를 사용
또한 정점마다 자신의 순서에 맞는 인스턴스 데이터를 사용
camera 클래스 수정
void Camera::Render_Deferred()
{
...
GET_SINGLE(InstancingManager)->Render(_vecDeferred);
}
void Camera::Render_Forward()
{
...
GET_SINGLE(InstancingManager)->Render(_vecForward);
...
}
카메라가 렌더 시에 인스턴싱 매니져를 통하여 렌더
engine 클래스 수정
void Engine::Update()
{
...
GET_SINGLE(InstancingManager)->ClearBuffer();
Render();
...
}
엔진 클래스에서 프레임마다 인스턴싱된 버퍼들을 청소
for (int32 i = 0; i < 50; i++)
{
...
shared_ptr<MeshRenderer> meshRenderer = make_shared<MeshRenderer>();
shared_ptr<Mesh> sphereMesh = GET_SINGLE(Resources)->LoadSphereMesh();
meshRenderer->SetMesh(sphereMesh);
shared_ptr<Material> material = GET_SINGLE(Resources)->Get<Material>(L"GameObject");
material->SetInt(0, 1);
meshRenderer->SetMaterial(material);
obj->AddComponent(meshRenderer);
scene->AddGameObject(obj);
}
메테리얼을 미리 만들어두고 해당 메테리얼을 불러와서 사용
쉐이더를 디버깅하기 위한 프로그램
GPU 캡쳐 후 파이프라인 탭에 가면 해당 프레임이 만들어지기까지의 히스토리를 볼 수 있음
아웃풋 머져에서 픽셀을 누른 후 debug pixel을 누르면
해당 픽셀을 만든 픽셀 쉐이더를 디버깅해볼로 수 있음