Render를 관리하는 RenderManager를 만든다.
00.Engine / Manager필터에 Render필터를 만든다.
Render를 RenderManager에서 하게 하는 것뿐만 아니라 MeshRenderer의 코드 수정이 필요하다.
MeshRenderer를 보면 Mesh가 있고 일부분이 Material로 빠져야 한다.
Render필터에 RenderManager클래스를 만든다.
SceneManager처럼 물체들을 들고 있는 건데, RenderManager는 그리는 물체들을 전용적으로 들고 있다는 차이가 있다.
조명이 생긴다거나, 카메라가 여러개여러 개 등장하거나, 렌더링이 되어야 하는데 MeshRenderer가 여러 개 있거나 여러 상황을 케어해 주는 게 목표다.
1. RenderHelper 클래스
Render 필터에 RenderHelper 클래스를 하나 더 만든다.
Struct.h 중에서 CameraData와 TransformData가 공용이라기보다는 렌더링과 관련된 부분이다. 쉐이더에 넘겨줘야 하는 데이터라 볼 수 있다.
RenderHelper로 옮겨준다.
#pragma once
struct CameraData
{
Matrix matView = Matrix::Identity;;
Matrix matProjection = Matrix::Identity; // 항등행렬
};
struct TransformData
{
Matrix matWorld = Matrix::Identity;;
};
2. RenderManager 클래스
RenderManager 코드로 돌아가서 작업을 몇 가지 더 해본다.
렌더링과 관련된 걸 들고 있을 것이다.
지난 시간까지 어떻게 만들었는지 생각을 해보면,
Game.h에 Graphics, Pipeline이 있고
MeshRenderer.h에서 많은 기능들을 들고 있엇는데, CameraData, TrasnfromData로 만든 버퍼 _cameraBuffer, _transformBuffer, 얘네들을 GPU랑 통신을 하기 위한 버퍼다..
버퍼를 만들어서 그 버퍼를 통해 메모리 고속복사를 해서 cameraBuffer의 내용을 다른데 옮기고 이런 작업을 했었다.
물체가 1000개가 되면 1000개의 버퍼를 만들 필요가 있을까?
DX12로 가면 빠르게 처리하기 위해 버퍼 개수를 늘려서 처리하는데 지금 단계에서는 그렇게 복잡하게 생각할 필요 없이 한번 복사하고 처리하면 되니 공용으로 하나만 관리하면 되지 않을까 생각이 든다.
1) CameraData, TransformData , State들 이주, _renderObjects 선언
그리고 MeshRenderer.h의 CameraData, TransformData에 관련된 내용들을 RenderManager.h로 이주한다.
private:
// Camera
CameraData _cameraData;
shared_ptr<ConstantBuffer<CameraData>> _cameraBuffer;
// SRT
TransformData _transformData;
shared_ptr<ConstantBuffer<TransformData>> _transformBuffer;
Animation 관련된 부분들도 넣어주면 된다.
MeshRenderer.h 에 RaterizerState부터 State와 관련된 건 어떻게 보면, 파이프라인 자체와 연관성이 있는 거니까, RenderManager.h에 옮기는 게 어울린 거 같다. 이주해 준다.
private:
shared_ptr<RasterizerState> _rasterizerState;
shared_ptr<SamplerState> _samplerState;
shared_ptr<BlendState> _blendState;
여기에
vector<shared_ptr<GameObject>> _renderObjects;
를 추가한다.
물체 중에서 그려야 할 물체들을 여기다가 갖고 오도록 한다.
2) 생성자, Init, Update, _device, _deviceContext, _pipeline 선언
class RenderManager
{
public:
RenderManager(ComPtr<ID3D11Device> device, ComPtr<ID3D11DeviceContext> deviceContext);
void Init();
void Update(shared_ptr<Graphics> graphics);
private:
ComPtr<ID3D11Device> _device;
ComPtr<ID3D11DeviceContext> _deviceContext;
shared_ptr<Pipeline> _pipeline;
이렇게 추가한다.
원래 Rendering을 할 때 항상 Game::Update를 보면
void Game::Update()
{
_graphics->RenderBegin();
TIME->Update();
INPUT->Update();
SCENE->Update();
_graphics->RenderEnd();
}
RenderBegin과 RenderEnd를 항상 시작과 끝에서 해줬다.
그거 조차도 RenderManager에서 담당하게 하고 싶은 것이기 때문에
그래서 RenderManager에서 Update를 할 때 인자로 Graphics를 받아주게 했다.
3) _cameraData, _transformData 버퍼로 복사하는 함수, Render 가능한 오브젝트 거름망 함수, Render 하는 함수 선언
그리고 Camera나 STR 정보들을 복사해줘야 한다. _cameraData를 _cameraBuffer에, transformData를 _transformBuffer에 복사해야 한다. 그거를 하는 함수를 파준다.
그리고 렌더 가능한 오브젝트를 긁어오고, 그 오브젝트를 렌더 하는 함수들도 정의한다.
private:
void PushCameraData();
void PushTransformData();
void GatherRenderablObject();
void RenderObjects();
RenderManager.h는 이렇게 된다.
#pragma once
#include "RenderHelper.h"
class RenderManager
{
public:
RenderManager(ComPtr<ID3D11Device> device, ComPtr<ID3D11DeviceContext> deviceContext);
void Init();
void Update(shared_ptr<Graphics> graphics);
private:
void PushCameraData();
void PushTransformData();
void GatherRenderableObjects();
void RenderObjects();
private:
ComPtr<ID3D11Device> _device;
ComPtr<ID3D11DeviceContext> _deviceContext;
shared_ptr<Pipeline> _pipeline;
private:
// Camera
CameraData _cameraData;
shared_ptr<ConstantBuffer<CameraData>> _cameraBuffer;
// SRT
TransformData _transformData;
shared_ptr<ConstantBuffer<TransformData>> _transformBuffer;
// Animation
private:
shared_ptr<RasterizerState> _rasterizerState;
shared_ptr<SamplerState> _samplerState;
shared_ptr<BlendState> _blendState;
vector<shared_ptr<GameObject>> _renderObjects;
};
4) pipeline, buffer, state 변수 만들어주는 Init 함수 구현
함수들을 구현한다.
void RenderManager::Init()
{
_pipeline = make_shared<Pipeline>(_deviceContext);
}
가끔 생성자에서 만들지 않고 이렇게 따라 Init 함수에서 만드는 게 유용할 때가 있다. shared_from_this 같은 거 사용할 때 생성자에서 아직 weak_pointer가 채워지지 않은 상태에서 shared_from_this를 하면 크래쉬가 나기 때문이다. 대부분의 경우 생성자에서 만들어도 큰 문제는 없다.
버퍼를 만들어 주는 내용은 MeshRenderer의 생성자의 코드를 복붙 하면 된다.
RasterizerState, BlendState, SamplerState와 관련된 부분도 MeshRenderer의 생성자의 코드에서 가져온다.
void RenderManager::Init()
{
_pipeline = make_shared<Pipeline>(_deviceContext);
_cameraBuffer = make_shared<ConstantBuffer<CameraData>>(_device, _deviceContext);
_cameraBuffer->Create();
_transformBuffer = make_shared<ConstantBuffer<TransformData>>(_device, _deviceContext);
_transformBuffer->Create();
_rasterizerState = make_shared<RasterizerState>(_device);
_rasterizerState->Create();
_blendState = make_shared<BlendState>(_device);
_blendState->Create();
_samplerState = make_shared<SamplerState>(_device);
_samplerState->Create();
}
물체 별로 분리되어야 하는 부분과 중간에서 관리하는 부분을 나눠서 하고 있다고 생각하면 된다.(여기가 중간에서 관리하는 부분)
5) 버퍼에 데이터 복사하는 PushCameraData함수 구현
void RenderManager::Update(shared_ptr<Graphics> graphics)
{
graphics->RenderBegin();
PushCameraData();
GatherRenderableObjects();
RenderObjects();
graphics->RenderEnd();
}
RenderManager에서 Camera도 관리한다고 하면, 갖고 있는 카메라 중 에서 첫 번째 거를 골라 넣어준다거나 하는 식으로 구현하는 걸 고려하면 된다.
지금은 Camera는 1개만 있고, View, Projection을 static으로 관리하고 있으니까, 다음과 같이 넣어주면 된다.
void RenderManager::PushCameraData()
{
_cameraData.matView = Camera::S_MatView;
_cameraData.matProjection = Camera::S_MatProjection;
_cameraBuffer->CopyData(_cameraData);
}
6) 버퍼에 데이터 복사하는 PushTransformData 함 구현
void RenderManager::PushTransformData()
{
_transformBuffer->CopyData(_transformData);
}
_transformData는 물체마다 다르니 알아서 채워주면 된다.
7) GatherRenderableObjects 함수 구현
void RenderManager::GatherRenderableObjects()
{
_renderObjects.clear();
auto& gameObjects = SCENE->GetActiveScene()->GetGameObjects();
for (const shared_ptr<GameObject>& gameObject : gameObjects)
{
shared_ptr<MeshRenderer> meshRenderer = gameObject->GetMeshRenderer();
if (meshRenderer)
_renderObjects.push_back(gameObject);
}
}
MeshRenderer에서 코드를 복사해 준 부분들은 삭제한다.
8) RenderObjects 함수 구현
MeshRenderer::Render의 코드를 void RenderManager::RenderObjects()로 옮긴다.
필요한 변수들이 MeshRenderer.h에 들어가 있다. 그런데 이 함수들을 사용하기 위해 Get 함수로 다 만들기는 힘드니까, MeshRenderer.h에 RenderManager를 friend로 등록을 해 놓는다.
friend class RenderManager;
이렇게 하면 private 변수도 RenderManager에서 꺼내 쓸 수 있다.
어차피 Mesh, Material로 묶어서 고쳐야 하기 때문에 임시로 meshRenderer→이렇게 바꿔서 사용할 수 있다.
void RenderManager::RenderObjects()
{
for (const shared_ptr<GameObject>& gameObject : _renderObjects)
{
shared_ptr<MeshRenderer> meshRenderer = gameObject->GetMeshRenderer();
if (meshRenderer == nullptr)
continue;
shared_ptr<Transform> transform = gameObject->GetTransform();
if (transform == nullptr)
continue;
// SRT
_transformData.matWorld = transform->GetWorldMatrix();
PushTransformData();
// IA - VS - RS - PS - OM
PipelineInfo info;
info.inputLayout = meshRenderer->_inputLayout;
info.vertexShader = meshRenderer->_vertexShader;
info.pixelShader = meshRenderer->_pixelShader;
info.rasterizerState = _rasterizerState;
info.blendState = _blendState;
_pipeline->UpdatePipeline(info);
_pipeline->SetVertexBuffer(meshRenderer->_vertexBuffer);
_pipeline->SetIndexBuffer(meshRenderer->_indexBuffer);
_pipeline->SetConstantBuffer(0, SS_VertexShader, _cameraBuffer);
_pipeline->SetConstantBuffer(1, SS_VertexShader, _transformBuffer);
_pipeline->SetTexture(0, SS_PixelShader, meshRenderer->_texture1);
_pipeline->SetSamplerState(0, SS_PixelShader, _samplerState);
_pipeline->DrawIndexed(meshRenderer->_geometry->GetIndexCount(), 0, 0);
}
}
임시 코드긴 하지만 중요한 건 RenderManager에서 모든 물체들을 대상으로 중앙에서 처리를 해주겠다는 게 관건이다.
공용회로 관리하는 부분과 Mesh, Material로 빼줘야 하는 부분이 섞여있기 때문에 헷갈리지 않게 깔끔하게 빼줬다.
9) MeshRenderer::Update에서 Render호출 주석처리하고, MeshRenderer::Render 함수 삭제
void MeshRenderer::Update()
{
// Render(GGame->GetPipeline());
}
MeshRenderer::Update에서 Render 하던 부분을 주석처리하고
코드를 옮겨 비어있는 MeshRenderer::Render 함수를 삭제한다.
어차피 Update에서 Render를 호출하는 게 아니라 MeshRenderer 컴포넌트가 있다는 사실만으로 걔를 그려주는 물체라고 판단해서 RenderManager에서 걔네들을 긁어 가지고 사용할 것임이 보장이 되고 있기 때문에 Update에서 Render를 호출해서 Pipeline을 꺼내서 하는 부분을 스킵할 수 있다.
10) Game에 RenderManager 넣기
RenderManager를 사용할 준비를 해야 한다.
Game.h으로 가서
class RenderManager;
를 전방선언을 하고,
shared_ptr<RenderManager> GetRenderManager() { return _render; }
를 추가하고,
shared_ptr<RenderManager> _render;
을 추가한다.
shared_ptr<Pipeline> _pipeline;
shared_ptr<Pipeline> GetPipeline() { return _pipeline; }
_pipeline, GetPipeline은 없어도 되니 삭제한다.
RenderManager에서 만들어서 사용하기 때문이다.
Game.cpp로 가서
#include "RenderManager.h"
Game::Init에서
_render = make_shared<RenderManager>(_graphics->GetDevice(), _graphics->GetDeviceContext());
_render->Init();
를 추가하고
_pipeline = make_shared<Pipeline>(_graphics->GetDeviceContext());
를 삭제한다.
11) GAME->GetRenderManager 매크로 만들기
pch.h로 가서
#define RENDER GAME->GetRenderManager()
를 추가한다.
12) Game::Update에서 RenderBegin, End 삭제하고, Game::Render에서 RENDER->Update호출하기
Game::Update에서
_graphics->RenderBegin();
_graphics->RenderEnd();
를 삭제하고,
Game::Render에서
void Game::Render()
{
RENDER->Update(_graphics);
}
이렇게 하면 된다.
빌드 후 실행을 하면 정상적으로 이미지가 보인다.
나중에 조명이 추가된다고 하면 그 조명들도 RenderManager에서 들고 있게 서 편리하게 작업할 준비가 끝났다고 볼 수 있다.
Scene에서 해도 안될 건 아니긴 하지만,
RenderManager에서는 따로 기능을 빼서, 그리는 용도의 애들까지 다 들고 있게끔 이렇게 만들어주고 있는 상황이라고 보 된다.
구조는 정해진 게 아니라 새로운 내용을 알거나 새로운 필요가 생기면 나중에 가면 딴 데 있는 게 더 편할 수 있기 때문에 왔다 갔다 하면서 고찰할 필요가 있다.
중요한 건 Buffer 같은 건 공용으로 관리하다 보니까 MeshRenderer가 아무리 많아도 Buffer를 여러 개를 관리할 필요가 없었구나 라는 걸 알게 된 거고,
Mesh, Material만 물체마다 고유하게 들고 있어야 하는 부분이라고 생각할 수 있다. 물체마다 고유하다기보다는 리소스를 들고 있다는 게 맞는 거다. 리소스를 재활용해서 들고 있게끔 하는 게 중요하다.
'DirectX' 카테고리의 다른 글
30. 엔진구조_Animation (0) | 2024.01.28 |
---|---|
29. 엔진구조_Material (0) | 2024.01.26 |
27. 엔진구조_ResourceManager (0) | 2024.01.22 |
26. 엔진구조_SceneManager (0) | 2024.01.21 |
25. 엔진구조_MeshRenderer (0) | 2024.01.19 |
댓글