Material
렌더를 할 때 중복되는 부분인 Material과 Mesh를 Resource의 개념으로 따로 분리하도록 하자.
MeshRenderer에서 사용하는 Mesh를 따로 설정하기 위해 Mesh라는 클래스를 만든다.
#pragma once
#include "ResourceBase.h"
class Mesh : public ResourceBase
{
using Super = ResourceBase;
public:
Mesh(ComPtr<ID3D11Device> device);
virtual ~Mesh();
void CreateDefaultRectangle();
shared_ptr<VertexBuffer> GetVertexBuffer() { return _vertexBuffer; }
shared_ptr<IndexBuffer> GetIndexBuffer() { return _indexBuffer; }
private:
ComPtr<ID3D11Device> _device;
// Mesh
shared_ptr<Geometry<VertexTextureData>> _geometry;
shared_ptr<VertexBuffer> _vertexBuffer;
shared_ptr<IndexBuffer> _indexBuffer;
};
#include "pch.h"
#include "Mesh.h"
Mesh::Mesh(ComPtr<ID3D11Device> device)
: Super(ResourceType::Mesh), _device(device)
{
}
Mesh::~Mesh()
{
}
void Mesh::CreateDefaultRectangle()
{
_geometry = make_shared<Geometry<VertexTextureData>>();
GeometryHelper::CreateRectangle(_geometry);
_vertexBuffer = make_shared<VertexBuffer>(_device);
_vertexBuffer->Create(_geometry->GetVertices());
_indexBuffer = make_shared<IndexBuffer>(_device);
_indexBuffer->Create(_geometry->GetIndices());
}
그리고 MehsRenderer에서 기존에 사용중인 Mesh 부분을 수정한다.
#pragma once
#include "Component.h"
class Mesh;
class MeshRenderer : public Component
{
using Super = Component;
public:
MeshRenderer(ComPtr<ID3D11Device> device, ComPtr<ID3D11DeviceContext> deviceContext);
virtual ~MeshRenderer();
virtual void Update() override;
void SetMesh(shared_ptr<Mesh> mesh) { _mesh = mesh; }
shared_ptr<Mesh> GetMesh() { return _mesh; }
private:
friend class RenderManager;
ComPtr<ID3D11Device> _device;
//Mesh
shared_ptr<Mesh> _mesh;
// Material
shared_ptr<InputLayout> _inputLayout;
shared_ptr<VertexShader> _vertexShader;
shared_ptr<PixelShader> _pixelShader;
shared_ptr<Texture> _texture1;
};
마지막으로 ResourceManager에서 특정 mesh를 만들 수 있는 함수를 만들어준다.
void ResourceManager::CreateDefaultMesh()
{
shared_ptr<Mesh> mesh = make_shared<Mesh>(_device);
mesh->SetName(L"Rectangle");
mesh->CreateDefaultRectangle();
Add(mesh->GetName(), mesh);
}
이렇게 하면 이제 어떤 오브젝트를 만들고 MeshRenderer Component를 추가하고 그 MeshRenderer에 mesh를 Resource에서 가져와 mesh를 등록하여 사용할 수 있다.
shared_ptr<GameObject> monster = make_shared<GameObject>(_graphics->GetDevice(), _graphics->GetDeviceContext());
{
monster->GetOrAddTransform();
auto meshRenderer = make_shared<MeshRenderer>(_graphics->GetDevice(), _graphics->GetDeviceContext());
monster->AddComponent(meshRenderer);
auto mesh = RESOURCES->Get<Mesh>(L"Rectangle");
meshRenderer->SetMesh(mesh);
scene->AddGameObject(monster);
}
Material은 어떤 재질을 의미한다. 유리인지, 나무인지 등을 알 수 있는 인자이다. 이는 어떻게 보면 쉐이더와 연관되어 있고 구성되어 있다는 것을 의미한다.
현재 우리가 사용하는 Shader는 별개 없어 Material이 간단하지만 만약 lighting이나 어떤 다양한 요소들이 들어가면 매우 복잡해질 것이다.
#pragma once
#include "ResourceBase.h"
class Shader;
class Texture;
class Material : public ResourceBase
{
using Super = ResourceBase;
public:
Material();
virtual ~Material();
auto GetShader() { return _shader; }
auto GetTexture() { return _texture; }
void SetShader(shared_ptr<Shader> shader) { _shader = shader; }
void SetTexture(shared_ptr<Texture> texture) { _texture = texture; }
private:
shared_ptr<Shader> _shader;
// 쉐이더에 넘기는 온갖 인자들
shared_ptr<Texture> _texture;
};
Animation
애니메이션은 단순히 여러 개의 이미지를 연속해서 트는 개념일 뿐이다.
이를 실습하기 위해 이 이미지를 이용해보겠다.
유니티에서는 이미지를 Sprite editor를 이용해 이미지를 원하는 사이즈 만큼 자를 수 있다.
그리고 그러한 이미지를 Animator의 Animation을 이용해 애니메이션을 만들 수 있다.
#pragma once
#include "ResourceBase.h"
struct Keyframe
{
Vec2 offset = Vec2(0.f, 0.f);
Vec2 size = Vec2(0.f, 0.f);
float time = 0.f;
};
class Texture;
class Animation : public ResourceBase
{
using Super = ResourceBase;
public:
Animation();
virtual ~Animation();
virtual void Load(const wstring& path) override;
virtual void Save(const wstring& path) override;
void SetLoop(bool loop) { _loop = loop; }
bool IsLoop() { return _loop; }
void SetTexture(shared_ptr<Texture> texture) { _texture = texture; }
shared_ptr<Texture> GetTexture() { return _texture; }
Vec2 GetTextureSize();
const Keyframe& GetKeyframe(int32 index);
int32 GetKeyframeCount();
void AddKeyframe(const Keyframe& keyframe);
private:
bool _loop = false;
shared_ptr<Texture> _texture;
vector<Keyframe> _keyframes;
};
#include "pch.h"
#include "Animation.h"
#include "Texture.h"
Animation::Animation() : Super(ResourceType::Animation)
{
}
Animation::~Animation()
{
}
void Animation::Load(const wstring& path)
{
}
void Animation::Save(const wstring& path)
{
}
Vec2 Animation::GetTextureSize()
{
return _texture->GetSize();
}
const Keyframe& Animation::GetKeyframe(int32 index)
{
return _keyframes[index];
}
int32 Animation::GetKeyframeCount()
{
return static_cast<int32>(_keyframes.size());
}
void Animation::AddKeyframe(const Keyframe& keyframe)
{
_keyframes.push_back(keyframe);
}
#pragma once
#include "Component.h"
#include "Animation.h"
class Animator : public Component
{
using Super = Component;
public:
Animator();
virtual ~Animator();
void Init();
void Update();
shared_ptr<Animation> GetCurrentAnimation();
const Keyframe& GetCurrentKeyframe();
void SetAnimation(shared_ptr<Animation> animation) { _currentAnimation = animation; }
private:
float _sumTime = 0.f;
int32 _currentKeyframeIndex = 0;
shared_ptr<Animation> _currentAnimation;
};
#include "pch.h"
#include "Animator.h"
#include "Game.h"
#include "TimeManager.h"
Animator::Animator()
: Super(ComponentType::Animator)
{
}
Animator::~Animator()
{
}
void Animator::Init()
{
}
void Animator::Update()
{
shared_ptr<Animation> animation = GetCurrentAnimation();
if (animation == nullptr)
return;
const Keyframe& keyframe = animation->GetKeyframe(_currentKeyframeIndex);
float deltaTime = TIME->GetDeltaTime();
_sumTime += deltaTime;
if (_sumTime >= keyframe.time)
{
_currentKeyframeIndex++;
int32 totalCount = animation->GetKeyframeCount();
if (totalCount <= _currentKeyframeIndex)
{
if (animation->IsLoop())
_currentKeyframeIndex = 0;
else
_currentKeyframeIndex = totalCount - 1;
}
_sumTime = 0.f;
}
}
std::shared_ptr<Animation> Animator::GetCurrentAnimation()
{
return _currentAnimation;
}
const Keyframe& Animator::GetCurrentKeyframe()
{
return _currentAnimation->GetKeyframe(_currentKeyframeIndex);
}
애니메이터의 Update가 가장 중요하다.
이제 애니메이션을 설정해서 실행만 해주면 된다.
우리는 저 이미지에서 처음부터 100 만큼떨어진 위치의 이미지를 계속 반복하고 싶은 것이니까 이렇게 설정해주면 된다.
void ResourceManager::CreateDefaultAnimation()
{
shared_ptr<Animation> animation = make_shared<Animation>();
animation->SetName(L"SankeAnim");
animation->SetTexture(Get<Texture>(L"Snake"));
animation->SetLoop(true);
animation->AddKeyframe(Keyframe{ Vec2{0.f,0.f}, Vec2{100.f,100.f }, 0.1f });
animation->AddKeyframe(Keyframe{ Vec2{100.f,0.f}, Vec2{100.f,100.f }, 0.1f });
animation->AddKeyframe(Keyframe{ Vec2{200.f,0.f}, Vec2{100.f,100.f }, 0.1f });
animation->AddKeyframe(Keyframe{ Vec2{300.f,0.f}, Vec2{100.f,100.f }, 0.1f });
}
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();
// Animation
shared_ptr<Animator> animator = gameObject->GetAnimator();
if (animator)
{
const Keyframe& keyframe = animator->GetCurrentKeyframe();
_animationData.spriteOffest = keyframe.offset;
_animationData.spriteSize = keyframe.size;
_animationData.textureSize = animator->GetCurrentAnimation()->GetTextureSize();
_animationData.useAnimation = 1.f;
PushAnimationData();
_pipeline->SetConstantBuffer(2, SS_VertexShader, _animationBuffer);
_pipeline->SetTexture(0, SS_PixelShader, animator->GetCurrentAnimation()->GetTexture());
}
else
{
_animationData.spriteOffest = Vec2(0.f, 0.f);
_animationData.spriteSize = Vec2(0.f, 0.f);
_animationData.textureSize = Vec2(0.f, 0.f);
_animationData.useAnimation = 0.f;
_pipeline->SetConstantBuffer(2, SS_VertexShader, _animationBuffer);
_pipeline->SetTexture(0, SS_PixelShader, meshRenderer->GetTexture());
}
PipelineInfo info;
info.inputLayout = meshRenderer->GetInputLayout();
info.vertexShader = meshRenderer->GetVertexShader();
info.pixelShader = meshRenderer->GetPixelShader();
info.rasterizerState = _rasterizerState;
info.blendState = _blendState;
_pipeline->UpdatePipeline(info);
_pipeline->SetVertexBuffer(meshRenderer->GetMesh()->GetVertexBuffer());
_pipeline->SetIndexBuffer(meshRenderer->GetMesh()->GetIndexBuffer());
_pipeline->SetConstantBuffer(0, SS_VertexShader, _cameraBuffer);
_pipeline->SetConstantBuffer(1, SS_VertexShader, _transformBuffer);
//_pipeline->SetTexture(0, SS_PixelShader, meshRenderer->GetTexture());
_pipeline->SetSamplerState(0, SS_PixelShader, _samplerState);
_pipeline->DrawIndexed(meshRenderer->GetMesh()->GetIndexBuffer()->GetCount(), 0, 0);
}
}
애니메이션의 여부에 따라 다르게 설정해주면 된다.
Data
각종 데이터를 저장하고 읽을 수 있게 변경해보자.
C++ 에서는 xml을 다룰수 있는 공식적인 라이브러리가 없다보니 tinyxml을 이용한다.
void Animation::Load(const wstring& path)
{
tinyxml2::XMLDocument doc;
string pathStr(path.begin(), path.end());
XmlError error = doc.LoadFile(pathStr.c_str());
assert(error == XmlError::XML_SUCCESS);
XMLElement* root = doc.FirstChildElement();
string nameStr = root->Attribute("Name");
_name = wstring(nameStr.begin(), nameStr.end());
_loop = root->Attribute("Loop");
_path = path;
// Load Texture
XMLElement* node = root->FirstChildElement();
for(; node != nullptr; node = node->NextSiblingElement())
{
Keyframe keyframe;
keyframe.offset.x = node->FloatAttribute("OffsetX");
keyframe.offset.y = node->FloatAttribute("OffsetY");
keyframe.size.x = node->FloatAttribute("SizeX");
keyframe.size.y = node->FloatAttribute("SizeY");
keyframe.time = node->FloatAttribute("Time");
AddKeyframe(keyframe);
}
}
void Animation::Save(const wstring& path)
{
tinyxml2::XMLDocument doc;
XMLElement* root = doc.NewElement("Animation");
doc.LinkEndChild(root);
string nameStr(GetName().begin(), GetName().end());
root->SetAttribute("Name", nameStr.c_str());
root->SetAttribute("Loop",_loop);
root->SetAttribute("TexturePath", "TODO");
for (const auto& keyframe : _keyframes)
{
XMLElement* node = doc.NewElement("Keyframe");
root->LinkEndChild(node);
node->SetAttribute("OffsetX", keyframe.offset.x);
node->SetAttribute("OffsetY", keyframe.offset.y);
node->SetAttribute("SizeX", keyframe.size.x);
node->SetAttribute("SizeY", keyframe.size.y);
node->SetAttribute("Time", keyframe.time);
}
string pathStr(path.begin(), path.end());
auto result = doc.SaveFile(pathStr.c_str());
assert(result == XmlError::XML_SUCCESS);
}
이런 식으로 모든 Resource 파일에 다 적용할 수 있을 것이다.
'C++ > DirectX 11' 카테고리의 다른 글
[DirectX] DirectX 11 3D 입문 - 카메라, 텍스처, Geometry (0) | 2024.09.12 |
---|---|
[DirectX] DirectX 11 3D 입문 - 프로젝트 설정 및 간단한 실습 (0) | 2024.09.11 |
[DirectX] 엔진 구조로 제작하기 - Managers (0) | 2024.09.06 |
[DirectX] 엔진 구조로 제작하기 - Component, MeshRenderer (2) | 2024.09.05 |
[DirectX] 프레임워크 제작기 - Transform (0) | 2024.09.04 |