DirectX

71_인스턴싱_Scene 구조 정리

devRiripong 2024. 3. 20.
반응형

정리를 한다.

void RenderDemo::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);
}

이런 부분을

Scene이라는 개념으로 관리해서 SceneManager를 만들고 Scnee이 실행되고 있는 게임 화면이라 인지하는 경우가 많은데 언리얼에서는 레벨이란 용어로 표현한다.

그 단위에다가 물체를 배치하면 매번 마다 씬이 업데이트되면서 물체들을 업데이트해주는 식으로 만들면 된다. 작업할 때마다 코드를 복사하지 않게 넣어 볼 것이다.

조명 같은 건 더 효율적으로 관리하고 싶다면 Light 컴포넌트가 있어야 한다. 빈 오브젝트에 라이트 컴포넌트를 추가해서 작업해 본다.

 

1. Component를 상속한 Light 클래스 만들기

먼저 Light Component부터 만들어 본다.

 

Engine/04. Component 필터에 Component를 상속받은 Light 클래스를 만든다.

오늘은 디렉셔널 라이트에서 크게 바뀔 것은 없지만 나중에 스포트 라이트나 다른 형태의 라이트를 만들고 싶다고 하면 공식은 똑같지만 범위만 수학식으로 조절하는 식으로 만들면 된다.

 

Component.h의 ComponentType에 

enum class ComponentType : uint8
{
	Transform, 
	MeshRenderer, 
	Camera, 
	ModelRenderer,
	Animator, 
	Light, 
	// ...
	Script, 

	End, 
};

Light를 추가한다.

 

#pragma once
#include "Component.h"
class Light :   public Component
{
public: 
	Light(); 
	virtual ~Light(); 
	
	virtual void Update(); 

public: 
	LightDesc& GetLightDesc() { return _desc; }

	void SetLightDesc(LightDesc& desc) { _desc = desc; }
	void SetAmbient(const Color& color) { _desc.ambient = color; }
	void SetDiffuse(const Color& color) { _desc.diffuse = color; }
	void SetSpecular(const Color& color) { _desc.specular = color; }
	void SetEmissive(const Color& color) { _desc.emissive = color; }
	void SetLightDirection(Vec3 direction) { _desc.direction = direction; }

private: 
	LightDesc _desc; 
};

 

#include "pch.h"
#include "Light.h"

Light::Light() : Component(ComponentType::Light)
{

}

Light::~Light()
{
}

void Light::Update()
{
	RENDER->PushLightData(_desc); 
}

 

이렇게 Light 클래스를 채워준다.

 

2. GameObject에 GetLight함수 정의하기

새로운 부품을 넣을 때마다 했던 작업을 한다. GameObject.h에 가서

class Light;

전방선언을 하고

	shared_ptr<Light> GetLight(); 

를 선언한다.

 

shared_ptr<Light> GameObject::GetLight()
{
	shared_ptr<Component> component = GetFixedComponent(ComponentType::Light);
	return static_pointer_cast<Light>(component);
}

정의하고

#include "Light.h"

 

Engine을 빌드한다.

 

Scene을 만들어 두면 편한게 온갖 잡동사니들을 들고 있고, 거기서 뭔가를 해주는 경우가 많아질 것이다.

 

 

3. SceneManager 클래스와 Scene 클래스 만들기 

Engine/02. Managers 에다가 SceneManager 클래스를 하나 만들어 준다. SceneManager를 통해서 수정을 해줄 것이다.

유니티에서는 GameManager나 SceneManager는 아마 유니티 자체에서 쓰고 있기 때문에 안 쓰는 게 낫다.

 

Engine/02. Managers에 Scene이라는 클래스를 만든다.

 

SceneManager는 Scene끼리 전환을 할 때 라거나 현재 실행되고 있는 Scene이 무엇인가 등등을 관리하는 애라고 볼 수 있다.

 

Camera 같은 건 Update에 넣어주면 문제가 생길 수 있다. View나 Projection이 바뀌면서 물체들이 미묘하게 위치가 바뀐다. 그렇다는 건 만약에 Update에 넣어 놨다가 물체가 100개가 있는데 50개는 첫 번째 위치에서 Update가 되다가 그다음에 Camera의 Update가 실행되는 바람에 위치가 미묘하게 바뀌게 되면 부들거리는 현상이 일어날 수 있다. 그래서 Camera는 모든 애들이 Update가 끝난 다음 혹은 이전에 LateUpdate 같이 다른 시점에 하는 게 좋다고 볼 수 있다.

 

EnginePch.h에

#include <set>
#include <unordered_set>

를 추가한다.

map과 set의 차이는 set은 키 값만 있고, 키와 Value 값이 동일 할 때 set을 쓴다. unordered가 붙으면 hash 기반으로 가는 것이다. map이랑 set은 red black tree 기반으로 가는 거였다. 그래서 속도가 조금 더 느린 것이다.

 

scene이란 개념을 만들고 이를 이용해서 SceneManager에서 현재 씬을 관리하고 업데이트를 해준다.

 

1) Scene

#pragma once
class Scene
{
public: 
	virtual void Start(); 
	virtual void Update();
	virtual void LateUpdate(); 

	virtual void Add(shared_ptr<GameObject> object); 
	virtual void Remove(shared_ptr<GameObject> object); 

private: 
	unordered_set<shared_ptr<GameObject>> _objects; 
	// Cache Camera
	unordered_set<shared_ptr<GameObject>> _cameras;
	// Cache Light
	unordered_set<shared_ptr<GameObject>> _lights;
	// 이런 식으로 모아 놓으면 편할 때가 많다.

};

#include "pch.h"
#include "Scene.h"

void Scene::Start()
{
	// Start, Updtae, LaterUpdate를 하는 도중에 Add, Remove 되는걸 방지하기 위해
	unordered_set<shared_ptr<GameObject>> objects = _objects;
	
	for (shared_ptr<GameObject> object : objects)
	{
		object->Start(); 
	}
}

void Scene::Update()
{
	unordered_set<shared_ptr<GameObject>> objects = _objects;

	for (shared_ptr<GameObject> object : objects)
	{
		object->Update();
	}

	// INSTANCING
	vector<shared_ptr<GameObject>> temp;
	temp.insert(temp.end(), objects.begin(), objects.end());
	INSTANCING->Render(temp);
}

void Scene::LateUpdate()
{
	unordered_set<shared_ptr<GameObject>> objects = _objects;

	for (shared_ptr<GameObject> object : objects)
	{
		object->LateUpdate();
	}
}

void Scene::Add(shared_ptr<GameObject> object)
{
	_objects.insert(object); 

	if (object->GetCamera() != nullptr)
	{
		_cameras.insert(object); 
	}

	if (object->GetLight() != nullptr)
	{
		_lights.insert(object); 
	}
}

void Scene::Remove(shared_ptr<GameObject> object)
{
	_objects.erase(object); 

	_cameras.erase(object); 

	_lights.erase(object); 
}

 

 

2) SceneManager

#pragma once
#include "Scene.h"

class SceneManager
{
	DECLARE_SINGLE(SceneManager);
	 
public: 
	void Update(); 

	template<typename T>
	void ChangeScene(shared_ptr<T> scene)
	{
		_currentScene = scene; 
		scene->Start(); 	// 분기별로 실행되는 함수들 넣어준다. 
	}
	
	shared_ptr<Scene> GetCurrentScene() { return _currentScene;  }

private: 
	shared_ptr<Scene> _currentScene = make_shared<Scene>(); 

};

#include "pch.h"
#include "SceneManager.h"

void SceneManager::Update()
{
	if (_currentScene == nullptr)
		return;

	_currentScene->Update(); 
	_currentScene->LateUpdate(); 
}

 

 

Define.h에 가서

#define SCENE		GET_SINGLE(SceneManager)

이렇게 넣어주고

현재 씬을 얻어오는 것도 많이 쓰기 때문에

#define CUR_SCENE	SCENE->GetCurrentScene()

이렇게 넣어준다.

 

EnginePch.h에

#include "SceneManager.h"

를 추가한다.

 

4. Game::Run에서 RESOURCE->Init을 호출하고, Game::Update에서 SCENE->Update를 호출하기

이제 SceneManager를 편리하게 사용할 수 있게 되었으니까 이걸 어딘가에 끼워 넣어야 하는데

예를 들면 Game 클래스에서 많은 것들을 하고 있는데

Game::Run 코드를 보면 온갖 것들을 초기화하고 있다.

	RESOURCES->Init();

contents 단에서 호출해 주던 이것도 여기에 넣는다.

 

Scene 같은 경우에도 매 프레임마다 호출이 되는게 맞을 테니까

Game::Update에다

	SCENE->Update(); 

이렇게 넣어주면 물체가 Scene에 배치가 되어 있다고 하면 SceneManager::Update이 실행된다.

void SceneManager::Update()
{
	if (_currentScene == nullptr)
		return;

	_currentScene->Update(); 
	_currentScene->LateUpdate(); 
}

이 부분에 의해서 Update를 하는 부분이 따로 실행이 될 것이다.

 

Engine을 빌드 해본다.

 

윗단에서 컨텐츠를 만들 때는 항상 Scene에다가 뭔가를 배치하는 식으로 만들어 주게 된다.

 

5. SceneDemo 클래스에 Scene, SceneManager 적용하기

Client에서 Scene을 테스트하는 용도로 RenderDemo클래스를 복붙 해서 SceneDemo라고 한다.

Clinet/Game 필터에 넣는다.

이걸 이용해서 물체들을 관리해서 보여주는게 목표다.

조명 같은 게 이제 다 빠져가지고 이제 내부적으로 들고 있을 건 없어야 정상이다.

Main에도 세팅한다.

shader는 23. RenderDemo.fx로 내버려둔다.

SceneDemo에서 Scene으로 빼준 부분들은 삭제한다.

	shared_ptr<GameObject> _camera;
	vector<shared_ptr<GameObject>> _objs;

를 삭제한다.

 

#pragma once
class SceneDemo : public IExecute
{
public:
	void Init() override;
	void Update() override;
	void Render() override;

private:
	shared_ptr<Shader> _shader;

private:
};

 

#include "pch.h"
#include "SceneDemo.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"
#include "Light.h"

void SceneDemo::Init()
{
	RESOURCES->Init();
	_shader = make_shared<Shader>(L"23. RenderDemo.fx");

	// Camera
	{
		auto camera = make_shared<GameObject>();
		camera->GetOrAddTransform()->SetPosition(Vec3{ 0.f, 0.f, -5.f });
		camera->AddComponent(make_shared<Camera>());
		camera->AddComponent(make_shared<CameraScript>());	
		CUR_SCENE->Add(camera); 
	}

	// Light
	{
		auto light = make_shared<GameObject>();
		light->AddComponent(make_shared<Light>());
		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);
		light->GetLight()->SetLightDesc(lightDesc); 
		CUR_SCENE->Add(light);
	}

	// Animation
	shared_ptr<class Model> m1 = make_shared<Model>();
	m1->ReadModel(L"Kachujin/Kachujin");
	m1->ReadMaterial(L"Kachujin/Kachujin");
	m1->ReadAnimation(L"Kachujin/Idle");
	m1->ReadAnimation(L"Kachujin/Run");
	m1->ReadAnimation(L"Kachujin/Slash");

	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)); // Tower는 너무 크기 때문에 크기를 줄여 준다.
		obj->AddComponent(make_shared<ModelAnimator>(_shader));
		{
			obj->GetModelAnimator()->SetModel(m1); // model이 mesh랑 material 관련 부분들을 다 들고 있는 식으로 만들어 놨다.
			obj->GetModelAnimator()->SetPass(2); // 애니메이션
		
		}
		CUR_SCENE->Add(obj);
	}

	// Model
	shared_ptr<class Model> m2 = make_shared<Model>();
	m2->ReadModel(L"Tower/Tower");
	m2->ReadMaterial(L"Tower/Tower");

	for (int32 i = 0; i < 100; 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(m2);
			obj->GetModelRenderer()->SetPass(1);  // Model
		}

		CUR_SCENE->Add(obj);
	}

	// Mesh
	// 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 < 100; i++)
	{
		auto obj = make_shared<GameObject>();
		obj->GetOrAddTransform()->SetLocalPosition(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);
			obj->GetMeshRenderer()->SetPass(0);  // Mesh
		}

		CUR_SCENE->Add(obj);
	}

	RENDER->Init(_shader);
}

void SceneDemo::Update()
{

}

void SceneDemo::Render()
{
}

 

6. RenderManager::Update에서 호출하던 PushGlobalData(Camera::S_MatView, Camera::S_MatProjection)를 Camera::Update에서 호출하기  

void RenderManager::Update()
{
	PushGlobalData(Camera::S_MatView, Camera::S_MatProjection);
}

PushGlobalData 이 부분도

CameraComponent에서 해주는 게 맞으니

void Camera::Update()
{
	UpdateMatrix();

	RENDER->PushGlobalData(Camera::S_MatView, Camera::S_MatProjection);
}

여기에 넣어준다.

 

 

 

Engine을 빌드한다.

 

실행하면 잘 나온다.

 

 

인스턴싱과 더불어서 Scene에 컴포넌트를 이용해 _objects, _cameras, lights를 늘리는 걸 작업해 보았다.

이러면 Light가 추가되더라도 Light.h에 상속을 받거나 enum을 파서 만들어 주면 된다.

 

유니티의 컴포넌트 방식이 직관적이고 이해하기 쉬워서 좋다.

반응형

'DirectX' 카테고리의 다른 글

73_RawBuffer  (0) 2024.03.21
72_Quaternion  (0) 2024.03.20
70_인스턴싱_인스턴싱 통합  (0) 2024.03.20
69_인스턴싱_ModelAnimation(인스턴싱)  (0) 2024.03.18
68_인스턴싱_ModelRenderer(인스턴싱)  (0) 2024.03.16

댓글