오늘은 전에 학습했던 드로우콜이 작동하도록 DX코드를 수정해주는 것이 목표이다.
1.MeshRenderer
드로우콜은 mesh와 material이 같다면 묶어서 관리해주고 관리 해주는 것 마다 인스턴스 버퍼가 있으며 이를 통해 인스턴싱이 이루어지도록 해야한다.
Vertex Buffer의 Create 함수의 매개변수 을 다르게 해서 사용하는 InstanceBuffer에서 Create 함수의 매개변수에서 CPU write를 true로 설정해주면 이 값을 오버라이트해서 데이터를 계속 밀어넣을 수 있다. 기존의 방식처럼 맵, 언맵, 데이터
카피 후 밀어넣기의 과정을 통해 데이터를 갱신해주면 된다.
중요한 점은 위의 변수들을 하나로 묶어서 관리해주어야 한다는 것이다. 이런 기능을 하기 위해 인스턴싱 매니저 클래스를 만들어주자.
일단은 우리가 InstanceBuffer와 World를 묶어서 관리해준 부분을 InstancingBuffer 라는 클래스를 만들어서 관리해주자. 이 클래스는 매 프레임마다 데이터 초기화 및 데이터를 넣어주고 이 데이터를 GPU쪽으로 밀어주는 기능을 만들어주자.
InstancingBuffer.h
#pragma once
class VertexBuffer;
struct InstancingData
{
Matrix world;
};
#define MAX_MESH_INSTANCE 500
class InstancingBuffer
{
public:
InstancingBuffer();
~InstancingBuffer();
private:
void CreateBuffer(uint32 maxCount = MAX_MESH_INSTANCE);
public:
//매 프레임마다 데이터 갱신 및 밀어넣기
void ClearData();
void AddData(InstancingData& data);
void PushData();
public:
uint32 GetCount() { return static_cast<uint32>(_data.size()); }
shared_ptr<VertexBuffer> GetBuffer() { return _instanceBuffer; }
void SetID(uint64 instanceId) { _instanceId = instanceId; }
uint64 GetID() { return _instanceId; }
private:
uint64 _instanceId = 0;
shared_ptr<VertexBuffer> _instanceBuffer;
uint32 _maxCount = 0;
vector<InstancingData> _data;
};
InstancingBuffer.cpp
#include "pch.h"
#include "InstancingBuffer.h"
InstancingBuffer::InstancingBuffer()
{
CreateBuffer(MAX_MESH_INSTANCE);
}
InstancingBuffer::~InstancingBuffer()
{
}
void InstancingBuffer::ClearData()
{
_data.clear();
}
void InstancingBuffer::AddData(InstancingData& data)
{
_data.push_back(data);
}
void InstancingBuffer::PushData()
{
const uint32 dataCount = GetCount();
if (dataCount > _maxCount)
CreateBuffer(dataCount);
D3D11_MAPPED_SUBRESOURCE subResource;
//뚜겅 열기
DC->Map(_instanceBuffer->GetComPtr().Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &subResource);
{
::memcpy(subResource.pData, _data.data(), sizeof(InstancingData) * dataCount);
}
DC->Unmap(_instanceBuffer->GetComPtr().Get(), 0);
//닫기
_instanceBuffer->PushData();
}
void InstancingBuffer::CreateBuffer(uint32 maxCount /*= MAX_MESH_INSTANCE*/)
{
_maxCount = maxCount;
_instanceBuffer = make_shared<VertexBuffer>();
vector<InstancingData> temp(maxCount);
_instanceBuffer->Create(temp, /*slot*/1, /*cpuWrite*/true);
}
다시 InstancingManager 코드로 돌아와서 매쉬와 Material을 둘씩 묶어서 관리해주는 코드를 작성해보자. 이때 관리하기 위한 아이디를 발급 해주어야 하는데 스마트 포인터를 사용하는 것을 활용해서 주소값을 통해 같은 주소이면 같은 값이라고 판단하게 하자. 이를 위해 변수를 설정해주자
Types.h
// MeshID / MaterialID
using InstanceID = std::pair<uint64, uint64>;
★꿀팁 - 스마트포인터를 사용할 때 &를 붙여주는게 복사가 일어나지 않기 때문에 효율이 좋다
map을 통해 짝을 만들어주고 실제로 렌더하는 함수에서 우선 기존 데이터를 날려주고 씬의 모든 오브젝트를 순회하면서 짝이 맞는 애들을 분류하고 그런애들을 모아서 그려주도록 하자.
일단 분류하기 위해 MeshRenderer에서 InstanceID를 만들어주기 위한 변수와 헬퍼 함수를 만들어주자. 그리고 지금의 MeshRenderer는 Update를 통해 자기 자신만을 그려주고 있는데 나와 비슷한 오브젝트도 그려주어야 하기 때문에 이제 별도의 함수를 통해 Render하도록 바꿔주자.
MeshRenderer.h
#pragma once
#include "Component.h"
class Mesh;
class Shader;
class Material;
class MeshRenderer : public Component
{
using Super = Component;
public:
MeshRenderer();
virtual ~MeshRenderer();
virtual void Update() override;
void SetMesh(shared_ptr<Mesh> mesh) { _mesh = mesh; }
void SetMaterial(shared_ptr<Material> material) { _material = material; }
void SetPass(uint8 pass) { _pass = pass; }
InstanceID GetInstanceID();
private:
shared_ptr<Mesh> _mesh;
shared_ptr<Material> _material;
uint8 _pass = 0;
};
MeshRenderer.cpp
#include "pch.h"
#include "MeshRenderer.h"
#include "Camera.h"
#include "Game.h"
#include "Mesh.h"
#include "Shader.h"
#include "Material.h"
MeshRenderer::MeshRenderer() : Super(ComponentType::MeshRenderer)
{
}
MeshRenderer::~MeshRenderer()
{
}
void MeshRenderer::RenderInstancing(shared_ptr<class InstancingBuffer>& buffer)
{
if (_mesh == nullptr || _material == nullptr)
return;
auto shader = _material->GetShader();
if (shader == nullptr)
return;
// Light
_material->Update();
// IA
_mesh->GetVertexBuffer()->PushData();
_mesh->GetIndexBuffer()->PushData();
buffer->PushData();
shader->DrawIndexedInstanced(0, _pass, _mesh->GetIndexBuffer()->GetCount(), buffer->GetCount());
}
InstanceID MeshRenderer::GetInstanceID()
{
//주소값 전달
return make_pair((uint64)_mesh.get(), (uint64)_material.get());
}
이를 활용하여 InstancingManager 클래스를 완성해주자.
InstancingManager.h
#pragma once
#include "InstancingBuffer.h"
class GameObject;
class InstancingManager
{
DECLARE_SINGLE(InstancingManager);
public:
void Render(vector<shared_ptr<GameObject>>& gameObjects);
void Clear() { _buffers.clear(); }
void ClearData();
private:
void RenderMeshRenderer(vector<shared_ptr<GameObject>>& gameObjects);
private:
void AddData(InstanceID instanceId, InstancingData& data);
private:
//짝 만들어주기
map<InstanceID/*instanceId*/, shared_ptr<InstancingBuffer>> _buffers;
};
InstancingManager.cpp
#include "pch.h"
#include "InstancingManager.h"
#include "InstancingBuffer.h"
#include "GameObject.h"
#include "MeshRenderer.h"
#include "ModelRenderer.h"
#include "ModelAnimator.h"
#include "Transform.h"
#include "Camera.h"
void InstancingManager::Render(vector<shared_ptr<GameObject>>& gameObjects)
{
ClearData();
RenderMeshRenderer(gameObjects);
}
void InstancingManager::RenderMeshRenderer(vector<shared_ptr<GameObject>>& gameObjects)
{
//임시용- 물체걸러주기
map<InstanceID, vector<shared_ptr<GameObject>>> cache;
//분류
for (shared_ptr<GameObject>& gameObject : gameObjects)
{
if (gameObject->GetMeshRenderer() == nullptr)
continue;
const InstanceID 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()->RenderSingle();
//}
//else
{
const InstanceID instanceId = pair.first;
for (int32 i = 0; i < vec.size(); i++)
{
const shared_ptr<GameObject>& gameObject = vec[i];
InstancingData data;
data.world = gameObject->GetTransform()->GetWorldMatrix();
AddData(instanceId, data);
}
//그려주기- 대표로 그려주기
shared_ptr<InstancingBuffer>& buffer = _buffers[instanceId];
vec[0]->GetMeshRenderer()->RenderInstancing(buffer);
}
}
}
void InstancingManager::AddData(InstanceID instanceId, InstancingData& data)
{
//인스턴스 아이디가 있는지 이미 로드했던게 아니라면
if (_buffers.find(instanceId) == _buffers.end())
_buffers[instanceId] = make_shared<InstancingBuffer>();
_buffers[instanceId]->AddData(data);
}
void InstancingManager::ClearData()
{
for (auto& pair : _buffers)
{
shared_ptr<InstancingBuffer>& buffer = pair.second;
buffer->ClearData();
}
}
이렇게 해주고 우리의 메인코드를 수정해주면 된다.
MeshInstancingDemo.h
#pragma once
class MeshInstancingDemo : 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;
private:
};
MeshInstancingDemo.cpp
#include "pch.h"
#include "MeshInstancingDemo.h"
#include "GeometryHelper.h"
#include "Camera.h"
#include "GameObject.h"
#include "CameraScript.h"
#include "MeshRenderer.h"
#include "Mesh.h"
#include "Material.h"
#include "Model.h"
#include "ModelRenderer.h"
#include "ModelAnimator.h"
#include "Mesh.h"
#include "Transform.h"
#include "VertexBuffer.h"
#include "IndexBuffer.h"
void MeshInstancingDemo::Init()
{
RESOURCES->Init();
_shader = make_shared<Shader>(L"20. MeshInstancingDemo.fx");
// Camera
_camera = make_shared<GameObject>();
_camera->GetOrAddTransform()->SetPosition(Vec3{ 0.f, 0.f, -5.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 < 500; i++)
{
auto obj = make_shared<GameObject>();
obj->GetOrAddTransform()->SetPosition(Vec3(rand() % 100, 0, rand() % 100));
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 MeshInstancingDemo::Update()
{
_camera->Update();
RENDER->Update();
{
LightDesc lightDesc;
lightDesc.ambient = Vec4(0.4f);
lightDesc.diffuse = Vec4(1.f);
lightDesc.specular = Vec4(0.1f);
lightDesc.direction = Vec3(1.f, 0.f, 1.f);
RENDER->PushLightData(lightDesc);
}
//INSTANCING
INSTANCING->Render(_objs);
}
void MeshInstancingDemo::Render()
{
}
이렇게 해주면 안정적으로 인스턴싱이 이루어지고 있는 모습을 볼 수 있다.
중요한 점은 이제 Update에서 자기 자신만을 렌더해주는 것이 아닌 인스턴싱 매니저를 통해 자신과 비슷한 오브젝트도 로드할 수 있게 만들어줘야 한다는 것이다.
2.ModelRenderer
이제 우리가 가져온 fbx도 인스턴싱이 가능하도록 코드를 수정해주자. ModelRenderer에서 Update부분에서 자기 자신을 렌더하기 위한 코드에서 새로운 함수를 통해 인스턴싱이 가능하도록 하자. 이때 World 좌표는 인스턴싱매니저에서 관리해주고 있기 때문에 빼주고 IA부분과 World 좌표를 넣어주는 부분, 인스턴싱으로 그려주는 부분을 넣어주자.
ModelRenderer.h
#pragma once
#include "Component.h"
class Model;
class Shader;
class Material;
class ModelRenderer : public Component
{
using Super = Component;
public:
ModelRenderer(shared_ptr<Shader> shader);
virtual ~ModelRenderer();
void SetModel(shared_ptr<Model> model);
void SetPass(uint8 pass) { _pass = pass; }
void RenderInstancing(shared_ptr<class InstancingBuffer>& buffer);
InstanceID GetInstanceID();
private:
shared_ptr<Shader> _shader;
uint8 _pass = 0; //통로
shared_ptr<Model> _model;
};
ModelRenderer.cpp
void ModelRenderer::RenderInstancing(shared_ptr<class InstancingBuffer>& buffer)
{
if (_model == nullptr)
return;
//Bones -> shader
BoneDesc boneDesc;
const uint32 boneCount = _model->GetBoneCount();
for (uint32 i = 0; i < boneCount; i++)
{
shared_ptr<ModelBone> bone = _model->GetBoneByIndex(i);
boneDesc.transforms[i] = bone->transform;
}
RENDER->PushBoneData(boneDesc);
//부품
const auto& meshes = _model->GetMeshes();
for (auto& mesh : meshes)
{
if (mesh->material)
mesh->material->Update();
//BoneIndex
_shader->GetScalar("BoneIndex")->SetInt(mesh->boneIndex);
//IA
mesh->vertexBuffer->PushData();
mesh->indexBuffer->PushData();
//World position 넣어주기
buffer->PushData();
_shader->DrawIndexedInstanced(0, _pass, mesh->indexBuffer->GetCount(), buffer->GetCount());
}
}
InstanceID ModelRenderer::GetInstanceID()
{
return make_pair((uint64)_model.get(), (uint64)_shader.get());
}
이렇게 해주고 이 Model Renderer를 통해 모델을 렌더해주도록 메인코드를 수정해주면 된다.
ModelInstancingDemo.cpp
#include "pch.h"
#include "ModelInstancingDemo.h"
#include "GeometryHelper.h"
#include "Camera.h"
#include "GameObject.h"
#include "CameraScript.h"
#include "MeshRenderer.h"
#include "Mesh.h"
#include "Material.h"
#include "Model.h"
#include "ModelRenderer.h"
#include "ModelAnimator.h"
#include "Mesh.h"
#include "Transform.h"
#include "VertexBuffer.h"
#include "IndexBuffer.h"
void ModelInstancingDemo::Init()
{
RESOURCES->Init();
_shader = make_shared<Shader>(L"21. ModelInstancingDemo.fx");
// Camera
_camera = make_shared<GameObject>();
_camera->GetOrAddTransform()->SetPosition(Vec3{ 0.f, 0.f, -5.f });
_camera->AddComponent(make_shared<Camera>());
_camera->AddComponent(make_shared<CameraScript>());
shared_ptr<class Model> m1 = make_shared<Model>();
m1->ReadModel(L"Tower/Tower");
m1->ReadMaterial(L"Tower/Tower");
for (int32 i = 0; i < 500; i++)
{
auto obj = make_shared<GameObject>();
obj->GetOrAddTransform()->SetPosition(Vec3(rand() % 100, 0, rand() % 100));
obj->GetOrAddTransform()->SetScale(Vec3(0.01f));
obj->AddComponent(make_shared<ModelRenderer>(_shader));
{
obj->GetModelRenderer()->SetModel(m1);
}
_objs.push_back(obj);
}
RENDER->Init(_shader);
}
void ModelInstancingDemo::Update()
{
_camera->Update();
RENDER->Update();
{
LightDesc lightDesc;
lightDesc.ambient = Vec4(0.4f);
lightDesc.diffuse = Vec4(1.f);
lightDesc.specular = Vec4(0.1f);
lightDesc.direction = Vec3(1.f, 0.f, 1.f);
RENDER->PushLightData(lightDesc);
}
//INSTANCING
INSTANCING->Render(_objs);
}
void ModelInstancingDemo::Render()
{
}
이렇게 해주면 안정적인 프레임으로 모델이 인스턴싱되는것을 볼 수 있다.
'게임공부 > Directx11' 카테고리의 다른 글
[Directx11][C++][3D]20. 통합 & 정리 (1) | 2024.09.28 |
---|---|
[Directx11][C++][3D]19. 인스턴싱(ModelAnimator) (0) | 2024.09.26 |
[Directx11][C++][3D]17. 인스턴싱과 드로우콜 (1) | 2024.09.23 |
[Directx11][C++][3D]16. 애니메이션(활용4,스카이박스) (0) | 2024.09.21 |
[Directx11][C++][3D]15. 애니메이션(활용2~3) (0) | 2024.09.19 |