일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- FrameResource
- direct3d
- 네트워크 게임 프로그래밍
- 큐브 매핑
- 조명 처리
- Render Target
- effective C++
- Direct3D12
- 동적 색인화
- DirectX12
- 게임 디자인 패턴
- 네트워크
- 게임 프로그래밍
- gitscm
- InputManager
- 노멀 맵핑
- 게임 클래스
- TCP/IP
- 장치 초기화
- DirectX
- 디퍼드 렌더링
- 입방체 매핑
- 직교 투영
- light
- gitlab
- 절두체 컬링
- Deferred Rendering
- Frustum Culling
- Dynamic Indexing
- C++
- Today
- Total
코승호딩의 메모장
[Resources] 본문
이제까지 갖가지 오브젝트(게임 오브젝트, 쉐이더, 메쉬, 머티리얼, 컴포넌트 등)들을 씬 매니저의 LoadTestScene에서 하드 코딩으로 생성하여 리소스들을 관리하였다. 이번 글에서는 이러한 오브젝트들을 Resources라는 매니저 클래스에서 함께 관리할 수 있도록 구조를 다시 설계하려고 한다. 이 리소스들은 최상위 Object 라는 클래스의 상속을 받아 Resources 클래스에서 가지고 있을 수 있도록 한다. 또한 Resources는 모든 오브젝트들을 하나로 관리하기 때문에 싱글턴으로 만들기로 한다. 원래는 파일 입출력을 통해 오브젝트들을 받아주는게 정상이지만 이번 글에서는 다루지 않는다.
Object 클래스
enum class OBJECT_TYPE : uint8
{
NONE,
GAMEOBJECT,
COMPONENT,
MATERIAL,
MESH,
SHADER,
TEXTURE,
END
};
enum
{
OBJECT_TYPE_COUNT = static_cast<uint8>(OBJECT_TYPE::END)
};
우선 오브젝트 타입을 enum 클래스를 통해 지정하였다. 현재까지는 다음과 같은 오브젝트들만 존재한다.
class Object
{
public:
Object(OBJECT_TYPE type);
virtual ~Object();
OBJECT_TYPE GetType() { return _objectType; }
void SetName(const wstring& name) { _name = name; }
const wstring& GetName() { return _name; }
protected:
OBJECT_TYPE _objectType = OBJECT_TYPE::NONE;
wstring _name;
};
이제부터 오브젝트들에 이름을 추가하여 관리하도록 하며 오브젝트 클래스들은 자신에 해당하는 오브젝트 타입을 갖는다. 오브젝트 클래스를 상속받는 오브젝트들은 생성자에서 자신의 오브젝트 타입을 인자로 넘겨주면 된다.
Resources 클래스
다음으로 위 오브젝트 클래스를 상속받은 오브젝트들을 한 번에 관리하는 Resources 클래스이다. 언리얼과 유니티와 같은 게임 엔진에서는 실제로 큐브나 구와 같은 간단한 모형을 바로 사용할 수 있도록 엔진 내에 저장해두기 때문에 비슷하게 약간의 계산을 통해서 큐브와 구를 생성하는 함수를 따로 만들 것이다.
class Resources
{
SINGLETON(Resources);
public:
template<typename T>
sptr<T> Load(const wstring& key, const wstring& path);
template<typename T>
bool Add(const wstring& key, sptr<T> object);
template<typename T>
sptr<T> Get(const wstring& Key);
template<typename T>
OBJECT_TYPE GetObjectType();
sptr<Mesh> LoadCubeMesh();
sptr<Mesh> LoadSphereMesh();
private:
using KeyObjMap = std::map<wstring, sptr<Object>>;
array<KeyObjMap, OBJECT_TYPE_COUNT> _resources;
};
Resources 클래스에서는 키 값으로 오브젝트의 이름을 밸류 값으로 오브젝트들을 갖고 있는 맵 자료구조를 가지고 있도록 한다. 이렇게 설정한 이유는 이름으로 관리되는 오브젝트들을 쉽게 찾기 위해서이다. 그리고 이 맵 자료구조를 정적 배열의 형태로 최대 오브젝트 타입의 수만큼 가지고 있도록 정적 배열을 생성한다.
이번 구현에서는 다음과 같이 LoadCubeMesh와 LoadSphereMesh를 통해 만든 메쉬를 Add를 통하여 _resources에 넣는다. 또한 _resources는 템플릿 멤버 함수를 사용하여 각 오브젝트 타입에 맞게 유연하게 관리할 수 있도록 한다.
template<typename T>
inline OBJECT_TYPE Resources::GetObjectType()
{
if (std::is_same_v<T, GameObject>)
return OBJECT_TYPE::GAMEOBJECT;
else if (std::is_same_v<T, Material>)
return OBJECT_TYPE::MATERIAL;
//...
}
위 함수는 템플릿 인자 T에 원하는 오브젝트 타입을 넣어주면 Type Trait를 통해서 비교하여 해당 오브젝트 타입을 반환한다.
template<typename T>
inline sptr<T> Resources::Load(const wstring& key, const wstring& path)
{
OBJECT_TYPE objectType = GetObjectType<T>();
KeyObjMap& keyObjMap = _resources[static_cast<uint8>(objectType)];
auto findIt = keyObjMap.find(key);
if (findIt != keyObjMap.end())
return static_pointer_cast<T>(findIt->second);
sptr<T> object = make_shared<T>();
object->Load(path);
keyObjMap[key] = object;
return object;
}
우선 아직 사용할 일이 없지만 나중을 위해 Load 함수를 생성하였다. Load 함수는 파일 입출력을 통해 오브젝트를 불러온다. 우선 총 7개의 오브젝트 타입 중 GetObjectType을 통해서 템플릿 인자로 받은 T를 그대로 넣어줘서 enum 클래스로 생성된 오브젝트 타입을 받아온다. 그리고 해당 오브젝트 타입을 가진 맵에서 이미 갖고 있는 키라면 해당 키 값을 가진 오브젝트를 반환하고 갖고 있지 않은 키라면 오브젝트에 Load하여 _resources에 추가한다.
template<typename T>
inline bool Resources::Add(const wstring& key, sptr<T> object)
{
OBJECT_TYPE objectType = GetObjectType<T>();
KeyObjMap& keyObjMap = _resources[static_cast<uint8>(objectType)];
auto findIt = keyObjMap.find(key);
if (findIt != keyObjMap.end())
return false;
keyObjMap[key] = object;
return true;
}
다음은 파일 입출력이 아닌 직접 생성한 오브젝트를 _resources에 추가하는 Add 함수이다. 만약 키 값이 이미 있다면 바로 false를 반환하여 추가하지 않도록 하고 키 값이 없다면 _resources에 추가하고 true를 반환한다.
template<typename T>
inline sptr<T> Resources::Get(const wstring& key)
{
OBJECT_TYPE objectType = GetObjectType<T>();
KeyObjMap& keyObjMap = _resources[static_cast<uint8>(objectType)];
auto findIt = keyObjMap.find(key);
if (findIt != keyObjMap.end())
return static_pointer_cast<T>(findIt->second);
return nullptr;
}
다음은 키 값을 주고 오브젝트를 반환받는 Get 함수이다. 키 값이 이미 있다면 해당 키 값을 가진 오브젝트를 반환한다.
shared_ptr<Mesh> Resources::LoadCubeMesh()
{
shared_ptr<Mesh> findMesh = Get<Mesh>(L"Cube");
if (findMesh)
return findMesh;
float w2 = 0.5f;
float h2 = 0.5f;
float d2 = 0.5f;
vector<Vertex> vec(24);
// front
vec[0] = Vertex(Vec3(-w2, -h2, -d2), Vec2(0.0f, 1.0f), Vec3(0.0f, 0.0f, -1.0f), Vec3(1.0f, 0.0f, 0.0f));
vec[1] = Vertex(Vec3(-w2, +h2, -d2), Vec2(0.0f, 0.0f), Vec3(0.0f, 0.0f, -1.0f), Vec3(1.0f, 0.0f, 0.0f));
vec[2] = Vertex(Vec3(+w2, +h2, -d2), Vec2(1.0f, 0.0f), Vec3(0.0f, 0.0f, -1.0f), Vec3(1.0f, 0.0f, 0.0f));
vec[3] = Vertex(Vec3(+w2, -h2, -d2), Vec2(1.0f, 1.0f), Vec3(0.0f, 0.0f, -1.0f), Vec3(1.0f, 0.0f, 0.0f));
ec2(1.0f, 1.0f), Vec3(1.0f, 0.0f, 0.0f), Vec3(0.0f, 0.0f, 1.0f));
//... back, left right, top, bottom
// front
idx[0] = 0; idx[1] = 1; idx[2] = 2;
idx[3] = 0; idx[4] = 2; idx[5] = 3;
//... back, left right, top, bottom
sptr<Mesh> mesh = make_shared<Mesh>();
mesh->Init(vec, idx);
Add<Mesh>(L"Cube", mesh);
return mesh;
}
게임 엔진에서 큐브와 구와 같은 기본 메쉬를 이미 가지고 있는 것과 같이 직접 함수를 통해 큐브와 구를 생성한다. 우선 _resources에서 해당 키 값이 있는지를 확인한다. 만약 있다면 그 메쉬를 리턴하고 없다면 일련의 과정을 거쳐 정점 정보와 인덱스 정보를 만들어 메쉬를 생성하고 Add 함수를 통해 _resources에 Cube의 키 값을 가진 메쉬를 추가한다. 구도 마찬가지.
shared_ptr<Scene> SceneManager::LoadTestScene()
{
//...
shared_ptr<MeshRenderer> meshRenderer = make_shared<MeshRenderer>();
{
shared_ptr<Mesh> mesh = GET_SINGLE(Resources)->LoadCubeMesh();
meshRenderer->SetMesh(mesh);
}
// Sphere
}
이제 TestScene에서 메쉬를 생성할 때, 직접 씬 매니저 클래스에서 생성하는 것이 아닌 Resources 클래스에서 만들어진 메쉬를 가져오거나 없다면 위에서 만든 LoadCubeMesh를 호출하여 메쉬를 생성하고 이를 MeshRenderer에 설정한다.
struct Vertex
{
Vertex() {}
Vertex(Vec3 p, Vec2 u, Vec3 n, Vec3 t)
: pos(p), uv(u), normal(n), tangent(t)
{
}
Vec3 pos;
Vec2 uv;
Vec3 normal;
Vec3 tangent;
};
그리고 이전까지 Vertex는 position, color, uv 값이었지만 이제부터는 color를 빼고 normal과 tangent를 추가하도록 한다. 입력 정점이 바뀌었으니 당연히 파이프라인 상태 객체에 등록할 인풋 레이아웃도 바뀌어야 한다. 이제 실행을 시키면 입체적인 정육면체와 구가 생성됨을 기대할 수 있을 것이다. 그러나 이것만 하면 심심하니깐 이동과 회전도 스크립트로 생성하자.
void TestAutoMoveScript::LateUpdate()
{
Vec3 pos = GetTransform()->GetLocalPosition();
pos.x = _pivot + sinf(TOTAL_TIME * _moveSpeed) * _distance;
GetTransform()->SetLocalPosition(pos);
}
간단히 정육면체를 좌우로 번갈아가며 이동시키기 위해서 sin함수를 적용하여 현재 위치에서 이동하도록 한다.
void TestRotationScript::LateUpdate()
{
Vec3 rotation = GetTransform()->GetLocalRotation();
rotation.y -= _rotationSpeed * DELTA_TIME;
GetTransform()->SetLocalRotation(rotation);
}
간단히 구를 반시계 방향으로 회전해보도록 하자.
// Cube Object
gameObject->AddComponent(make_shared<TestAutoMoveScript>(gameObject->GetTransform()->GetLocalPosition().x));
// Sphere Object
gameObject->AddComponent(make_shared<TestRotationScript>());
이제 큐브와 구 오브젝트에 위에서 만든 스크립트를 추가하면 다음과 같이 실행이 될 것이다.
이번 글에서는 다양한 오브젝트들을 하나의 오브젝트로 Resources에서 관리하도록 만들었다. 이 전까지는 각자 따로 떨어져 있어 헷갈리는 부분이 많았지만 이렇게 Resources라는 클래스 안에서 전체를 관리할 수 있도록 만드니깐 함수를 사용하기도 쉽고 오브젝트들을 가져오기도 쉬웠다. 빨리 파일 입출력을 통해서 리소스들을 관리할 수 있도록 만들어보고 싶다.
'DirectX12 > DirectX12 응용' 카테고리의 다른 글
[Normal Mapping] (0) | 2023.10.12 |
---|---|
[Light-2] (1) | 2023.10.11 |
[Light-1] (1) | 2023.10.10 |
[Camera] (1) | 2023.10.09 |
[SceneManager] (1) | 2023.10.08 |
[Component] (0) | 2023.10.07 |
[InputManager와 GameTimer] (1) | 2023.10.07 |