DirectX

24. 엔진구조_Component

devRiripong 2024. 1. 17.
반응형

DirectX나 OpenGL로 연습하면 좋은 두가지 의미

  1. 그래픽스 지식
  2. 엔진 구조를 잡는 거 자체가 고민 거리가 많다.

아직 GameObject에 렌더링 기능이 몰빵이 되어 있다.

Camera, Animation도 지원을 하지 않고 있다.

2D sprite로 애니메이션을 트는 것까지가 이번 시간의 목표이다.

 

그와 별개로 구조에 대한 얘기를 더 해본다.

지난 시간에 transform 이라는 빈 컴포넌트를 만들어 보았다.

유니티를 보면 빈 GameObject에 여러 컴포넌트들이 붙는다.

유니티에서 스크립트를 GameObject에 붙일 수 있는데 스크립트를 열어 보면 MonoBehaviour를 상속 받고 있고, MonoBehaviour를 타고 가면 Component를 상속 받고 있는 것을 볼 수 있다.

 

GameObject를 개선을 더 해보자면,

ComPtr<ID3D11Device> _device; 

shared_ptr<Geometry<VertexTextureData>> _geometry;
// shared_ptr<Geometry<VertexColorData>> _geometry;
shared_ptr<VertexBuffer> _vertexBuffer;
shared_ptr<IndexBuffer> _indexBuffer;
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;

이 렌더링 부분은 다른 컴포넌트로 옮길 것이다.

 

유니티에서 MeshRender 컴포넌트는 그려주는 것과 연관이 있다.

지금은 테스트를 위해 위의 코드처럼 GameObject에 _device부터 _blendState까지 전부 몰아 넣어 놨는데 GameObject는 다양한 용도로 사용될 것이기 때문에 이렇게 몰빵이 되면 안된다. 그래서 그려주는 렌더링에 관련된 부품을 MeshRender라고 해서 만들것이다.

 

일단 지난 시간에 GameObject.h에 선언한 _parent와 이걸 사용해서 테스트 했던 부분을 GameObject클래스에서 삭제한다.

 

두 가지를 만들 것이다.

 

1. Component 클래스를 GameObject에 붙일 수 있게 보강 

Component클래스를 통해 여러 개의 Compnent들을 붙일 수 있을 것이다. Component.h에 enum으로 ComponentType에 대한 이름을 지어 준다.

enum class ComponentType : uint8
{
	Transform, 
	MeshRenderer, 
	Camera, 
	Animator,	// 여기까진 1개 밖에 있을 수 없는 고정
	// ...
	Script,		// 여기 부터는 동적으로 여러개 붙일 수 있는 애

	End,

};

그 다음에

enum
{
	FIXED_COMPONENT_COUNT = static_cast<uint8>(ComponentType::End)-1 // 1을 빼서 4개가 고정적인 애라고 표현 
};

이렇게 enum을 추가한다.

유니티와 비슷하게 하기 위해서 Component에서 Init을 rename으로 Awake로 바꿔주고, abstract를 삭제한다. 그리고 Start, LateUpdate, FixedUpdate 함수를 추가한다.

 	virtual void Awake() {}
	virtual void Start() {}
	virtual void Update() {}
	virtual void LateUpdate() {}
	virtual void FixedUpdate() {}

인게임에 배치하지 않고 메모리 상에 만들기만 해도 Awake는 호출이 된다. 생성이 되는 시점에 호출이 된다.

Update도 마찬가지로 LateUpdate라고 Update보다 더 나중에 실행이 되는 거였고, 이런 식으로 계층을 여러 개로 나눠서 제공이 되는 식이다.

이렇게 시점을 관리할 수 있게 열어주는 게 유니티의 핵심이다.

 

카메라가 일반적인 물체랑 동일한 시점에 업데이트 되면 물체를 다 찍기 전에 카메라가 먼저 업데이트가 될 수 있다. 그래서 카메라는 한 프레임을 다 찍을 때 까지 가만히 있게 하려면, 일반적 Update가 아니라 더 뒤에 있는 시점인 LateUpdate에 밀어 넣어서 카메라 이동을 구현하는 식으로 응용을 할 수 있다.

 

그리고 Compont.h에

ComponentType _type;

이 변수를 추가해서 어떠한 용도로 활용 되는지 추적을 한다.

 

weak_ptr<GameObject> _owner;도 _gameObject로 rename한다.

 

void SetGameObject(shared_ptr<GameObject> gameObject) { _gameObject = gameObject; }

이 함수를 추가해 Component가 주인 gmaeObject를 기억하고 있게 한다.

 

그리고 GameObject에서 Compnent의 여러 함수를 편리하게 호출하게 하고 싶다고 하면,

private:
	friend class GameObject;

이러면 GameObject에서 Component에서 만들어준 애들에 접근할 수 있다.

 

shared_ptr<Transform> GetTransform();

이 함수를 추가해 주인의 위치를 알 수 있게 지원을 해준다.

 

Component.h의 윗줄에

class Transform;

도 추가한다.

 

GetGameObject의 구현부를 Component.cpp로 옮기고, GetTransform도 구현한다.

shared_ptr<GameObject> Component::GetGameObject()
{
     return _gameObject.lock(); 
}

shared_ptr<Transform> Component::GetTransform()
{
    return _gameObject.lock()->GetTransform();
}

아직 GameObject의 GetTransform은 구현해주지 않았기 때문에 error가 나는 상태이다.

 

 

2. GameObject가 Component들을 가지고 있을 수 있게 보강

GameObject.h에 가서는 무엇을 해줘야 하는가

protected:
	std::array<shared_ptr<Component>, FIXED_COMPONENT_COUNT> _components;

이걸 추가해주고,

pch.h에 가서

#include <array>

를 추가한다.

 

그리고 GameObject.h의 protected:에

vector<shared_ptr<Component>> _scripts;

를 추가한다.

 

MonoBehavior라고 해서 script를 만들어서 붙여서 관리 했었는데 그 기능을 하는 걸 따로 모아 놓을 것이다.

 

고정 크기로 1개씩만 있어야 할 부품들을 묶어 놓을 _components 배열을 만들었다.

그리고 여러 개를 밀어 넣을 수 있는 _scripts를 만들었다.

 

Component 필터에 Animation, Camera, Rendering 필터를

00.Engnie 필터에는 Component를 상속받은 MonoBehaviour 클래스를 생성한다.

 

3. 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; 
};

이렇게 사용할 함수들은 override해준다.

 

유니티에도 Start, Update를 꼭 사용해야 하는 건 아니지만 함수 안에 기능을 넣어주면 실행이 된다.

 

Compnent의 생성자에서 무조건 ComponentType을 받아주게 해준다. 그래야지만 예상하지 못한 애가 안들어 온다는 걸 보장할 수 있다.

Component.h

Component(ComponentType type);

 

Component.cpp

Component::Component(ComponentType type) : _type(type)
{
}

type을 여기서 연결을 해주었으니까 MonoBehaviour.cpp에서도

MonoBehaviour::MonoBehaviour() : Super(ComponentType::Script)
{
}

이런식으로 만들어 주면 된다.

 

언리얼 엔진에서는 상속이 가장 핵심이 되어 가지고, GameObject가 아니라 Actor였고, 그 Actor를 여러 용도에 따라서 상속을 받아서 사용했었다. Pawn이나 Character를 상속 받아서 모든 게 태생적으로 결정이 되어 있었다면,

유니티는 모든 애들이 GameObject라는 빈 깡통에서 시작해서 부품을 주워서 만드는 방식이다.

언리얼 엔진이면 기능 붙일 때 Actor를 상속 받은 클래스에서 무엇인가를 해줘야 했지만, 얘는 별도의 Comonent, 부품을 만들어서, 그 부품을 붙이는 식으로 작업을 한다는게 차이다.

 

이런 유니티 방식의 장점은 재사용이 가능하다.

컨텐츠 작업 측면에서 장점이 많다. 그래서 MonoBehaviour만들어서 컨텐츠 만들 때 상속 받아서 만들면 된다.

 

4. Transform 컴포넌트 보강

Transform.h에서

class Transform : public Component
{
	using Super = Component;

라고 해서 부모님을 Super라고 지칭하게 한다. 이건 언리얼 방식이다.

Transform.cpp에서

Transform::Transform() : Super(ComponentType::Transform)
{
}

Transform의 경우는 유니티에서 아무리 빈 GameObject를 만든다고 해도 무조건 Transform 하나는 들어 있었다. 그게 Transform이라는 부품이다. 중요도가 가장 높다고 볼 수 있다.

 

5. Transform 컴포넌트 분석

1)Right, Up, Look 구하는 두가지 방식

지난 시간에 Transform의 부품을 만들어 줬는데 두고두고 반복적으로 연습하고 분석을 할 필요가 있다.

_right, _up, _look을 구하는 방식이 다양하게 있을 수 있다.

필요하다면 별도 함수로 빼는 걸 고려할 수도 있다.

 

지난 시간엔 _right를 구할 때 (1,0,0)이라는 기본적인 좌표 벡터를 만들어 주고, 거기에 월드 행렬을 곱해 줬다.

(1, 0, 0) * MatWorld

이렇게 월드를 기준으로 하는 오른쪽 화살표를 구했었다.

이렇게 구해줬었는데 방식이 하나 더 있다.

 

만약 World 행렬을 알고 있다고 하면

[01 02 03 04]

[05 06 07 08]

[09 10 11 12]

[13 14 15 16]

이중에서 오른쪽을 나타내는 건 첫쨰 줄 01 02 03 이 right라고 했었다. (수학 복습)

Vec3 GetRight() { return _matWorld.Right(); }

이렇게 구할 수 있는데

Right()를 꺼내는 게 실제로 01 02 03을 꺼내 주는 것인지 라이브러리 함수의 구현부를 f12로 들어가 보면

Vector3 Right() const noexcept { return Vector3(_11, _12, _13); }

이렇게 되어 있는 것을 볼 수 있다.

행렬은 응용할 수 있는 범위가 많다 보니까, 다양한 식으로 연산을 할 수 있다.

나머지도

 	Vec3 GetRight() { return _matWorld.Right(); }
	Vec3 GetUp() { return _matWorld.Up(); }
	Vec3 GetLook() { return _matWorld.Backward(); }

이렇게 해줄 수 있다. Backward로 한 이유는 라이브러리는 오른손 좌표계로 되어 있어서 방향히 반대기 때문이다.

_right = Vec3::TransformNormal(Vec3::Right, _matWorld); // 월드 기준으로 한 오른쪽이 나온다. 
_up = Vec3::TransformNormal(Vec3::Up, _matWorld);  
_look = Vec3::TransformNormal(Vec3::Backward, _matWorld); // 라이브러리의 좌표계가 오른손 좌표계라서

양 쪽을 이해할 필요가 있다.

 

_right, _up, _look 값을 굳이  Cache할 필요 없으니

	Vec3 _right; 
	Vec3 _up; 
	Vec3 _look;

를 Transform.h에서 삭제하고, 

void Transform::UpdateTransform()의 

_right = Vec3::TransformNormal(Vec3::Right, _matWorld); // 월드 기준으로 한 오른쪽이 나온다. 
_up = Vec3::TransformNormal(Vec3::Up, _matWorld);  
_look = Vec3::TransformNormal(Vec3::Backward, _matWorld); // 라이브러리의 좌표계가 오른손 좌표계라서

이것도 삭제한다. 

2) TransformNormal과 TransformCoord의 차이

위에서 _right, _up, _look을 구할 때 TransformNormal 함수를 사용했는데 TransformCoord의 차이는 무엇인가 ?

여기서는 왜 TransformNormal 을 사용했을까? 방향을 나타내는 것이기 때문에 13 14 15인 Translation 정보를 적용하면 안되기 떄문이다 .

(1, 0, 0, 0) * MatWorld를 할 때 1, 0, 0, 0에서 4번째를 0으로 할지 1로 할지와 연관이 있었다.

 

Transform은 생성자에서 Super에 ComponentType::Transform 을 넣어준 부분만 바뀌었고,

MonoBehaviour 는 Super에 ComponentType::Script를 넣어줘서 Script와 관련된 부분들을 넣어주게 될 것이라는 걸 표현 해줬다고 볼 수가 있다.

 

6. GameObject 클래스에 Component를 제어할 함수 보강

1) Component와 동일한 함수

GameObject도 마찬가지로 Awake부터 Start 등의 함수들이 똑같이 들어가 있어야 한다.

	void Awake(); 
	void Start(); 
	void Update();
	void LateUpdate(); 
	void FixedUpdate();

어떤 콤포넌트이건 간에 같은 이름의 함수를 호출할 수 있게 된다.

void GameObject::Start()
{
	for (shared_ptr<Component>& component : _components)
	{
		component->Start(); 
	}

	for (shared_ptr<MonoBehaviour>& script : _scripts)
	{
		script->Start();
	}
}

 

GameObject.h에서

vector<shared_ptr<Component>> _scripts;

vector<shared_ptr<MonoBehaviour>> _scripts;

이렇게 수정하고

전방선언도 해준다.

class MonoBehaviour;

 

GameObject.cpp에

#include "MonoBehaviour.h"
#include "Transform.h"

이렇게 추가해줘서 준비를 한다.

 

Awake도 마찬가지다.

void GameObject::Awake()
{
	for (shared_ptr<Component>& component : _components)
	{
		component->Awake();
	}

	for (shared_ptr<MonoBehaviour>& script : _scripts)
	{
		script->Awake();
	}
}

시점에 따라 이렇게 함수를 호출해 주는 거를 준비해 주면 된다.

 

Update도 마찬가지로 해준다.

void GameObject::Update()
{
	for (shared_ptr<Component>& component : _components)
	{
		component->Update();
	}

	for (shared_ptr<MonoBehaviour>& script : _scripts)
	{
		script->Update();
	}

	_transformData.matWorld = _transform->GetWorldMatrix(); 

	_constantBuffer->CopyData(_transformData);
}

불순물이 끼어 있는데 나중에 옮겨줄 것이다.

 

void GameObject::LateUpdate()
{
	for (shared_ptr<Component>& component : _components)
	{
		component->LateUpdate();
	}

	for (shared_ptr<MonoBehaviour>& script : _scripts)
	{
		script->LateUpdate();
	}
}

void GameObject::FixedUpdate()
{
	for (shared_ptr<Component>& component : _components)
	{
		component->FixedUpdate();
	}

	for (shared_ptr<MonoBehaviour>& script : _scripts)
	{
		script->FixedUpdate();
	}
}

이런식으로 부품을 넣어주면, 사실상 GameObject가 왕이 되어서 관리하게 될 것이기 때문에,

GameObject에서 Update를 한다면 모든 부품을 대상으로 업데이트를 전달하는 역할을 하게 된다라고 볼 수 있다.

 

GameObject의 Render는 옮길 것이다. Rendering을 관리하는 부품을 만들어줘서, 부품이 있으면 화면에 그려주고, 없으면 안 그려지는 식으로 관리를 해줘야 한다.

카메라 역할이라 Rendering 할 필요 없다면 Rendering 컴포넌트를 안붙이면 된다.

 

2) Component를 Get 하는 함수

GameObject에 추가적으로 함수를 만들어 본다.

shared_ptr<Component> GetFixedComponent(ComponentType type); 
shared_ptr<Transform> GetTransform();

GetFixedComponent는 ComponentType을 지정해주면 그 type에 해당하는 FixedComponent중 하나를 골라서 전달해 주게 될 것인데

shared_ptr<Component> GameObject::GetFixedComponent(ComponentType type)
{
	uint8 index = static_cast<uint8>(type); 
	assert(index < FIXED_COMPONENT_COUNT); 
	return _components[index]; 
}

 

 

GetTransform도 GetFixedComponent를 이용해 구현해 보면,

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

이렇게 구현할 수 있다.

 

유니티를 보면 주인과 Component를 왔다갔다 할 기능들이 준비되어 있다.

 

Component.cpp에

#include "GameObject.h"

를 추가해서 특정 컴포넌트에서도 transform을 얻어 올 수 있게 했다.

 

3) Component를 Add하는 함수

현재 Game클래스에서 모든 오브젝트를 관리하고 있는데,

Game.h에서

shared_ptr<GameObject> _gameObject;

 

이렇게 _gameObject를 하나 만들었다고 가정을 하면,

_gmaeObject에 부품을 붙이게 될 것인데, 여러가지 부품이 있겠지만 카메라가 됐건 트랜스폼이 됐건 그런 부품들을 얘기하는 것이다.

여기서는 일단 Transform을 붙이는 작업을 해 볼 것인데,

void AddComponent(shared_ptr<Component> component);

GameObjec.h에 이걸 추가해주고, 구현을 해준다.

void GameObject::AddComponent(shared_ptr<Component> component)
{
	component->SetGameObject(shared_from_this())
}

shared_from_this에서 에러가 나는데,

GameObject.h 에

class GameObject : public enable_shared_from_this<GameObject>

이렇게 : public enable_shared_from_this<GameObject> 이거를 추가해 줘야 한다.

만약에

void GameObject::AddComponent(shared_ptr<Component> component)
{
	component->SetGameObject(this);
}

이렇게 this로 넘겨주게 된다면,

reference count를 2중으로 관리하게 되기 때문에 메모리 누수가 일어나게 되어서 해지하지 말아야 할 얘를 해지하게 되는 등 난리가 난다.

반드시 현재 내가 사용하는 스마트 포인터로 변환하는 작업을 해야 하는데 shared_from_this를 이용한다.

enable-shared_from_this를 추가해야 하고, f12로 내부적으로 보면

mutable weak_ptr<_Ty> _Wptr;

weak_ptr 하나를 들고 있는 걸 볼 수 있다.

생성하는 단계에서 자기 자신을 weak_ptr에 채워주는 식으로 동작을 한다.

그렇기 때문에 shared_from_this()라는 걸 생성자에서 호출하게 된다면 크래시가 난다.

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)); 
	}
}

이렇게 둘 중 하나에 넣어주고,

GetType에서 에러가 나는 것은

Component.h에서

public:
	ComponentType GetType() { return _type; }

이렇게 추가해주면 된다.

 

4) GetOrAddTransform

GameObject.h에

shared_ptr<Transform> GetOrAddTransform();

를 추가한다.

shared_ptr<Transform> GameObject::GetOrAddTransform()
{
	if (GetTransform() == nullptr)
	{
		shared_ptr<Transform> transform = make_shared<Transform>(); 
		AddComponent(transform); 
	}

	return GetTransform(); 
}

이렇게 정의하고

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);
}

이렇게 _transform을 GetOrAddTransform()으로 바꾼다.

 

5) component 유무 체크

그리고 component가 없을 수 있으니까,

‘if(component)를 통해 null 체크를 해준다.

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();
	}
}

 

 

https://docs.unity3d.com/Manual/ExecutionOrder.html

여기를 보면, 초기화 되는 순서를 알 수 있다.

지금 코드에서는 복잡한 걸 간단하게 묘사하고 있는 것이다.

실행해 보면 똑같이 이미지가 나온다.

 

 

Game.h의 _gameObject를 관리하는 곳이 Game::Init인데 부품들을 추가해주는 걸 여기서 해주면 된다.

void Game::Init(HWND hwnd)
{
	_hwnd = hwnd;

	_graphics = make_shared<Graphics>(hwnd); 
	_pipeline = make_shared<Pipeline>(_graphics->GetDeviceContext());

	_gameObject = make_shared<GameObject>(_graphics->GetDevice(), _graphics->GetDeviceContext());
	{
		_gameObject->GetOrAddTransform(); 
		// ..
	}
}

 

_gameObject는 어디서 관리해야 할까?

Scene이라는 개념을 만들어서 관리한다.

 

Component를 다 채워줘야 한다 .

Camera라는 두번째 아이를 등장 시킬거야.

 

7. Component를 상속받은 Camera 클래스 

Camera 필터에 Component를 상속받은 Camera 클래스를 생성한다.

유니티에서 보면 MainCamera가 Transform이랑 Camera라는 컴포넌트를 들고 있다. AudioListener라고 소리 듣는 기능도 있지만 일단 무시한다.

카메라라는게 없으면 게임 화면에 아무것도 안보이게 된다 .

이 Transform Component , Camera Component들은 Camera가 Camera맨 역할 을 할 수 있게 하는 부품이라고 볼 수 있다.

기본적으로 Camera 클래스를 만들어 본다.

 

Default.hlsl에서

cbuffer TransformData : register(b0) // 상수 버서 TransfromData를 받아 줄 건데, 버퍼의 약자인 b0를 받아 주도록 할거야. 
{ 
    row_major matrix matWorld;
    row_major matrix matView;
    row_major matrix matProjection;
}

matWorld는 물체마다 다르지만, matView, matProjection은 별도의 카메라 버퍼로 빼서 한 번만 넘겨줘도 무방하다.

카메라 위치와 카메라가 바라보는 정점을 안다면 중요한 건 파란색 선 look 벡터다. 카메라 기준으로 하는 좌표계란 카메라의 위치가 0, 0, 0이고 카메라가 바라보는 방향을 look 벡터로 안다는 거고, 그거 두개를 안다면, right와 up을 구해주면 된다.

위 방향을 대충 넘겨주면 그걸 이용해서 right를 구하고, right와 look을 이용해 up을 구할 수 있다.

XMMatrixLookAtLH함수를 이용해서 구할 수 있다.

 

Camera 클래스 코드는 다음과 같다.

#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);
}

이걸 적용해 보자.

 

8. Game클래스에서 _camera 오브젝트 만들어서 Transform과 Camera컴포넌트 붙이기

Game.h에서

_gameObject의 이름을 _monster라는 이름으로 바꿔준다.

//TEMP
shared_ptr<GameObject> _monster; 
shared_ptr<GameObject> _camera;

그리고 _camera라는 오브젝트를 만든다고 하면,

이건 임시적으로만 여기서 관리하는 거고 나중에 가면 Scene에다가 배치를 하고, 툴을 이용해서 뭔가 할 수 있게끔 만들어 줘야 한다.

테스트를 해보자면

Game.cpp에

#include "Camera.h"

를 추가해주고,

Game::Init에서

void Game::Init(HWND hwnd)
{
	_hwnd = hwnd;

	_graphics = make_shared<Graphics>(hwnd); 
	_pipeline = make_shared<Pipeline>(_graphics->GetDeviceContext());

	_monster = make_shared<GameObject>(_graphics->GetDevice(), _graphics->GetDeviceContext());
	{
		_monster->GetOrAddTransform(); 
		// ..
	}

	_camera = make_shared<GameObject>(_graphics->GetDevice(), _graphics->GetDeviceContext());
	{
		_camera->GetOrAddTransform();
		_camera->AddComponent(make_shared<Camera>());
	}
}

이렇게 Camera 컴포넌트를 넣어줬다는 건,

유니티에서 MainCamera 오브젝트에 Camera 컴포넌트가 세팅이 된 것 처럼 되었다고 볼 수 있다.

void Game::Update()
{
	_monster->Update(); 
	_camera->Update(); 
}

업데이트가 된다고 하면,

 

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);
}

모든 컴포넌트들에 대한 Update를 실행해주게 된다.

 

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

_camera 오브젝트에 Camera 컴포넌트도 붙여놨기 때문에 Camera::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);
}

매 프레임마다 이 카메라가 갱신이 된다는 걸 예측을 할 수 있다.

 

9. 셰이더 수정(다음 시간에)

카메라가 갱신이 된다는 거 까지 알았다. 이제 Shader를 수정해 준다.

원래라면

cbuffer TransformData : register(b0) // 상수 버서 TransfromData를 받아 줄 건데, 버퍼의 약자인 b0를 받아 주도록 할거야. 
{ 
    row_major matrix matWorld;
    row_major matrix matView;
    row_major matrix matProjection;
}

이런식으로 넣어줬었는데 따로 분리가 되어서 들어가야 한다.

이건 나중에 해주기로 하고,

GameObject::Update에서 matWorld를 넣어주고 있었다.

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);
}

지금은 GameObject가 camera랑 gameObject랑 두개를 동시에 사용하는 용도로 사용하고 있기 때문에 matWorld를 넣어주는 부분을 다른 데로 옮겨줘야 한다.

 

우리가 한 것은 Camera라는 새로운 component를 만들어 주고, 그걸 이용해서 _camera오브젝트를 분리해서 만들어 주는 것 까지 성공을 했지만 camera에 맞게 셰이더를 고치려면, 수정이 필요하다

GameObject와 Component의 관계를 좀 더 명확하게 이해를 하고 구분해서 만들게끔 수정을 하면 된다.

반응형

'DirectX' 카테고리의 다른 글

26. 엔진구조_SceneManager  (0) 2024.01.21
25. 엔진구조_MeshRenderer  (0) 2024.01.19
22. 프레임워크 제작_GameObject  (0) 2024.01.05
21. 프레임워크 제작_Pipeline  (0) 2024.01.05
20. 프레임워크 제작_Shader  (0) 2024.01.03

댓글