엔진 구조를 완벽히 분석하는 것은 실제 상용 엔진을 이해하는데 매우 큰 도움이 된다.
Component
유니티에서는 어떤 오브젝트에 Component를 여러개를 조립해서 하나의 생명을 불어넣을 수 있다.
이전 포스팅에서 다뤘던 Transform도 하나의 Component라고 할 수 있다. 각 Component는 하나의 특정 역할을 하는 것이 대부분이다.
그래서 지금 작업중인 GameObject를 수정해보자. 현재 GameObject는 Mesh에 대한 정보, 즉 한번만 로드하면 될 정보들도 같이 들고 있다. 이는 고블린의 쉐이더나 정점정보를 각 고블린 당 하나씩 들고 있다는 점이 문제인 것이다. 이는 나중에 MeshRenderer로 수정할 것이다.
enum class ComponentType : uint8
{
Transform,
MeshRenderer,
Camera,
Animator,
// ...
Script,
End,
};
enum
{
FIXED_COMPONENT_COUNT = static_cast<uint8>(ComponentType::End) - 1
};
이렇게 enum을 지정해 둬 Component가 어떤 것이 있고 중복되는 것과 아닌 것을 구분한다.
class Component
{
public:
Component(ComponentType type);
virtual ~Component();
virtual void Awake() {}
virtual void Start() {}
virtual void Update() {}
virtual void LateUpdate() {}
virtual void FixedUpdate() {}
shared_ptr<GameObject> GetGameObject();
shared_ptr<Transform> GetTransform();
private:
friend class GameObject;
void SetGameObject(shared_ptr<GameObject> gameObject) { _gameObject = gameObject; }
protected:
ComponentType _type;
weak_ptr<GameObject> _gameObject;
};
유니티를 따라 해보기 위해 각종 함수를 만들어주겠다.
유니티에서는 Script들이 MonoBehaviour를 상속받아 사용한다.
#pragma once
#include "Component.h"
class MonoBehaviour : public Component
{
using Super = Component;
public:
MonoBehaviour();
~MonoBehaviour();
virtual void Awake() override;
virtual void Update() override;
};
#pragma once
class MonoBehaviour;
class GameObject : public enable_shared_from_this<GameObject>
{
public:
GameObject(ComPtr<ID3D11Device> device, ComPtr<ID3D11DeviceContext> deviceContext);
~GameObject();
void Awake();
void Start();
void Update();
void LateUpdate();
void FixedUpdate();
shared_ptr<Component> GetFixedComponent(ComponentType type);
shared_ptr<Transform> GetTransform();
shared_ptr<Transform> GetOrAddTransform();
void AddComponent(shared_ptr<Component> component);
void Render(shared_ptr<Pipeline> pipeline);
private:
private:
// SRT
TransformData _transformData;
shared_ptr<ConstantBuffer<TransformData>> _constantBuffer;
protected:
// 중복되지 않는 컴포넌트 관리 하기 위함.
std::array<shared_ptr<Component>, FIXED_COMPONENT_COUNT> _components;
// 스크립트는 여러개를 넣어도 되니까 따로 관리
vector<shared_ptr<MonoBehaviour>> _scripts;
};
#include "pch.h"
#include "GameObject.h"
#include "MonoBehaviour.h"
#include "Transform.h"
GameObject::GameObject(ComPtr<ID3D11Device> device, ComPtr<ID3D11DeviceContext> deviceContext)
: _device(device)
{
// 물체의 기하학적인 모형을 만들어줘
_geometry = make_shared<Geometry<VertexTextureData>>();
GeometryHelper::CreateRectangle(_geometry);
// GPU야 이런 모양으로 버퍼를 만들어줘
_vertexBuffer = make_shared<VertexBuffer>(device);
_vertexBuffer->Create(_geometry->GetVertices());
_indexBuffer = make_shared<IndexBuffer>(device);
_indexBuffer->Create(_geometry->GetIndices());
// VertexShader는 이거야
_vertexShader = make_shared<VertexShader>(device);
_vertexShader->Create(L"Default.hlsl", "VS", "vs_5_0");
// 쉐이더를 바탕으로 도형을 어떻게 분석할지 알려줄게
_inputLayout = make_shared<InputLayout>(device);
_inputLayout->Create(VertexTextureData::descs, _vertexShader->GetBlob());
// PixelShader는 이거야
_pixelShader = make_shared<PixelShader>(device);
_pixelShader->Create(L"Default.hlsl", "PS", "ps_5_0");
// RS는 이렇게 세팅할게
_rasterizerState = make_shared<RasterizerState>(device);
_rasterizerState->Create();
// BS는 이렇게 세팅할게
_blendState = make_shared<BlendState>(device);
_blendState->Create();
// 상수버퍼 생성할게
_constantBuffer = make_shared<ConstantBuffer<TransformData>>(device, deviceContext);
_constantBuffer->Create();
// 사용할 텍스처 세팅할게
_texture1 = make_shared<Texture>(device);
_texture1->Create(L"Skeleton.png");
// 샘플링은 이렇게 해
_samplerState = make_shared<SamplerState>(device);
_samplerState->Create();
}
GameObject::~GameObject()
{
}
void GameObject::Awake()
{
for (shared_ptr<Component>& component : _components)
{
if (component)
component->Awake();
}
for (shared_ptr<MonoBehaviour>& script : _scripts)
{
script->Awake();
}
}
void GameObject::Start()
{
for (shared_ptr<Component>& component : _components)
{
if(component)
component->Start();
}
for (shared_ptr<MonoBehaviour>& script : _scripts)
{
script->Start();
}
}
void GameObject::Update()
{
for (shared_ptr<Component>& component : _components)
{
if (component)
component->Update();
}
for (shared_ptr<MonoBehaviour>& script : _scripts)
{
script->Update();
}
_transformData.matWorld = GetOrAddTransform()->GetWorldMatrix();
_constantBuffer->CopyData(_transformData);
}
void GameObject::LateUpdate()
{
for (shared_ptr<Component>& component : _components)
{
if (component)
component->LateUpdate();
}
for (shared_ptr<MonoBehaviour>& script : _scripts)
{
script->LateUpdate();
}
}
void GameObject::FixedUpdate()
{
for (shared_ptr<Component>& component : _components)
{
if (component)
component->FixedUpdate();
}
for (shared_ptr<MonoBehaviour>& script : _scripts)
{
script->FixedUpdate();
}
}
shared_ptr<Component> GameObject::GetFixedComponent(ComponentType type)
{
uint8 index = static_cast<uint8>(type);
assert(index < FIXED_COMPONENT_COUNT);
return _components[index];
}
shared_ptr<Transform> GameObject::GetTransform()
{
shared_ptr<Component> component = GetFixedComponent(ComponentType::Transform);
return static_pointer_cast<Transform>(component);
}
shared_ptr<Transform> GameObject::GetOrAddTransform()
{
if (GetTransform() == nullptr)
{
shared_ptr<Transform> transform = make_shared<Transform>();
AddComponent(transform);
}
return GetTransform();
}
void GameObject::AddComponent(shared_ptr<Component> component)
{
component->SetGameObject(shared_from_this());
uint8 index = static_cast<uint8>(component->GetType());
if (index < FIXED_COMPONENT_COUNT)
{
_components[index] = component;
}
else
{
_scripts.push_back(dynamic_pointer_cast<MonoBehaviour>(component));
}
}
void GameObject::Render(shared_ptr<Pipeline> pipeline)
{
PipelineInfo info;
info.inputLayout = _inputLayout;
info.vertexShader = _vertexShader;
info.pixelShader = _pixelShader;
info.rasterizerState = _rasterizerState;
info.blendState = _blendState;
pipeline->UpdatePipeline(info);
pipeline->SetVertexBuffer(_vertexBuffer);
pipeline->SetIndexBuffer(_indexBuffer);
pipeline->SetConstantBuffer(0, SS_VertexShader, _constantBuffer);
pipeline->SetTexture(0, SS_PixelShader, _texture1);
pipeline->SetSamplerState(0, SS_PixelShader, _samplerState);
pipeline->DrawIndexed(_geometry->GetIndexCount(), 0, 0);
}
Camera
기본 컴포넌트중 하나인 카메라를 만들어보자. 유니티에서도 카메라가 존재하지 않는 다면 어떠한 오브젝트이든 화면에 보일 수 없게된다. 매우 중요한 컴포넌트이니 제작해보자.
카메라는 행렬에서도 매우 중요한데 특히 View 행렬과 Projection 행렬이 연관되어 있다. 다른 말로 하자면 다른 오브젝트들은 View 행렬이나 Projection 행렬을 가지고 있을 필요가 없다는 것이다. 또한 가지고 있다고 하더라도 이는 성능 부하를 일으킬 수 있다.
#pragma once
#include "Component.h"
enum class ProjectionType
{
Perspective, // 원근 투영
Orthographic, // 직교 투영
};
class Camera : public Component
{
using Super = Component;
public:
Camera();
virtual ~Camera();
virtual void Update() override;
void SetProjectionType(ProjectionType type) { _type = type; }
ProjectionType GetProjectionType() { return _type; }
void UpdateMatrix();
private:
ProjectionType _type = ProjectionType::Orthographic;
public:
static Matrix S_MatView;
static Matrix S_MatProjection;
};
#include "pch.h"
#include "Camera.h"
Matrix Camera::S_MatView = Matrix::Identity;
Matrix Camera::S_MatProjection = Matrix::Identity;
Camera::Camera() : Super(ComponentType::Camera)
{
}
Camera::~Camera()
{
}
void Camera::Update()
{
UpdateMatrix();
}
void Camera::UpdateMatrix()
{
// 둘 중 뭘 해도 상관 없다.
Vec3 eyePosition = GetTransform()->GetPosition();
Vec3 focusPosition = eyePosition + GetTransform()->GetLook();
Vec3 upDirection = GetTransform()->GetUp();
S_MatView = ::XMMatrixLookAtLH(eyePosition, focusPosition, upDirection);
//S_MatView = GetTransform()->GetWorldMatrix().Invert();
if (_type == ProjectionType::Perspective)
S_MatProjection = ::XMMatrixPerspectiveFovLH(XM_PI / 4.f, 800.f / 600.f, 1.f, 100.f);
else
S_MatProjection = ::XMMatrixOrthographicLH(800, 600, 0.f, 1.f);
}
아직 완벽히 카메라가 되진 않았지만 이제 쉐이더 쪽에서 수정만 해주면 카메라 처럼 사용이 가능할 것이다.
MeshRenderer
지금까지는 GameObject에서 렌더링에 대한 정보를 전부 다 들고 있었다. 이는 모든 물체가 계속 한번씩 해줘야하는 단계가 아니기에 줄여보자.
즉, MeshRenderer를 가지고 있는 친구만 렌더링을 하도록 하자.
#pragma once
#include "Component.h"
class MeshRenderer : public Component
{
using Super = Component;
public:
MeshRenderer(ComPtr<ID3D11Device> device, ComPtr<ID3D11DeviceContext> deviceContext);
virtual ~MeshRenderer();
virtual void Update() override;
public:
void Render(shared_ptr<Pipeline> pipeline);
private:
ComPtr<ID3D11Device> _device;
// Mesh
shared_ptr<Geometry<VertexTextureData>> _geometry;
shared_ptr<VertexBuffer> _vertexBuffer;
shared_ptr<IndexBuffer> _indexBuffer;
// Material
shared_ptr<InputLayout> _inputLayout;
shared_ptr<VertexShader> _vertexShader;
shared_ptr<RasterizerState> _rasterizerState;
shared_ptr<PixelShader> _pixelShader;
shared_ptr<Texture> _texture1;
shared_ptr<SamplerState> _samplerState;
shared_ptr<BlendState> _blendState;
private:
// Camera
CameraData _cameraData;
shared_ptr<ConstantBuffer<CameraData>> _cameraBuffer;
// SRT
TransformData _transformData;
shared_ptr<ConstantBuffer<TransformData>> _transformBuffer;
};
void Game::Render()
{
_graphics->RenderBegin();
{
_monster->GetMeshRenderer()->Render(_pipeline);
}
_graphics->RenderEnd();
}
이렇게 카메라는 빼고 몬스터만 렌더링하게 할 수 있다.
'C++ > DirectX 11' 카테고리의 다른 글
[DirectX] 엔진 구조로 제작하기 - Material, Animation, Data (0) | 2024.09.10 |
---|---|
[DirectX] 엔진 구조로 제작하기 - Managers (0) | 2024.09.06 |
[DirectX] 프레임워크 제작기 - Transform (0) | 2024.09.04 |
[DirectX] 프레임워크 제작기 - Shader, Pipeline, GameObject (1) | 2024.09.03 |
[DirectX] 프레임워크 제작기 - Graphics, IA, Geomotry (1) | 2024.09.02 |