DirectX

25. 엔진구조_MeshRenderer

devRiripong 2024. 1. 19. 00:56
반응형

Camera만 Shader와 연동해 적용을 시켜보고

지저분한 코드들을 이전하는 작업을 해볼 것이다.

 

1. Camera Data(View, Projection), TransformData를 각각 따로 받아가지고, 전송하게 하기

1) Shader의 cbuffer TransformData를 CameraData와 TransformData로 분리

Default.hlsl에 가보면

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

지금까지는 View와 Projection을 묶어서 관리하고 있었다.

cbuffer CameraData : register(b0) 
{ 
    row_major matrix matView;
    row_major matrix matProjection;
}

cbuffer TransformData : register(b1) 
{
    row_major matrix matWorld;
}

이렇게 분리를 해준다.

이렇게 해줬을 때 장점은

물체가 몇 천개가 되더라도 카메라는 하나밖에 없을테니까 Camera가 건내준 View와 Projection은 한번만 복사해줄 것이고,

나머지 세부적인 물체마다의 TransformData는 받아 줘야 하는데 View와 Projection은 매번 마다 넣어 줄 필요가 없다.

이거를 하기 위해서

 

2) struct TransformData를 CameraData, TransformData로 분리

struct TransformData
{
	Matrix matWorld = Matrix::Identity;;
	Matrix matView = Matrix::Identity;;
	Matrix matProjection = Matrix::Identity; // 항등행렬
};

Struct.h에 TransformData라는 걸 만들어 놨고, 이걸 셰이더와 연동을 시켰었는데, 얘가 하나가 더 생겨야 한다.

struct CameraData
{
	Matrix matView = Matrix::Identity;;
	Matrix matProjection = Matrix::Identity; // 항등행렬
};

struct TransformData
{
	Matrix matWorld = Matrix::Identity;;
};

 

3) GameObject에서 _cameraData, _cameraBuffer추가 후 _cameraBuffer를 Create 하기

TransformData를 어떻게 만들었는지 보면,

GameObject.h에서(임시코드긴 하다)

private:
	TransformData _transformData;
	shared_ptr<ConstantBuffer<TransformData>> _constantBuffer;

여기다가 하나를 더 복사해서 비슷한 느낌으로 만들어준다.

 

기존의 _constantBuffer를 _transformBuffer로 rename한다.

새로 복사한 애는 _cameraBuffer라고 한다.

private:
	// Camera
	CameraData _cameraData;
	shared_ptr<ConstantBuffer<CameraData>> _cameraBuffer;

	TransformData _transformData;
	shared_ptr<ConstantBuffer<TransformData>> _transformBuffer;

그리고 GameObject 생성자에서

_cameraBuffer = make_shared<ConstantBuffer<CameraData>>(device, deviceContext);
_cameraBuffer->Create();

_transformBuffer = make_shared<ConstantBuffer<TransformData>>(device, deviceContext);
_transformBuffer->Create();

_transformBuffer와 마찬가지로 make_shared해주고, _cameraBuffer를 Create해주면 된다.

 

4) _cameraData에 내용 채우고, _cameraBuffer에 CopyData

_transformData를 어디서 채워줬냐면

 

GameObject::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();

	_transformBuffer->CopyData(_transformData);
}

이렇게 채워주고 있어. 나중엔 옮겨주겠지만 _cameraData도 여기서 임시적으로 채워준다고 하면,

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

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

	_cameraData.matView = Camera::S_MatView;
	_cameraData.matProjection = Camera::S_MatProjection;
	_cameraBuffer->CopyData(_cameraData);

	_transformData.matWorld = GetOrAddTransform()->GetWorldMatrix();
	_transformBuffer->CopyData(_transformData);
}

이렇게 _cameraData를 채워주고,

GameObject.cpp 에

#include "Camera.h"

이렇게 추가해준다.

이런 느낌으로 데이터를 전달하고 넘겨주면 셰이더 쪽에 이 정보들이 들어가겠다고 예상을 할 수 있다.

 

5) Game::Update시 _camera의 경우 _cameraData, _transformData갱신 스킵하게 하기

 

한가지 문제는 GameObject가 공용으로 사용하는 것이기 때문에,

Game::Update에서

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

_monster도 데이트 하고, _camera도 업데이트 하면,

그 떄 마다 GameObject::Update가 실행이 되면서 위에서 써준 _cameraData, _transformData도 갱신해 주는 부분이 마음에 안든다.

그래서 _camera인 경우 원래대로면 Update를 할 때 _transform.matWorld를 갱신하는 부분도 같이 호출되는 게 맞겠지만,

GameObject::Update에서 오브젝트만 대상으로 호출되게끔 임시 코드를 넣어준다.

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

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

	// TEMP
	if(GetComponent

	_cameraData.matView = Camera::S_MatView;
	_cameraData.matProjection = Camera::S_MatProjection;
	_cameraBuffer->CopyData(_cameraData);

	_transformData.matWorld = GetOrAddTransform()->GetWorldMatrix();
	_transformBuffer->CopyData(_transformData);
}

여기서

if(GetComponent를 해서 Camera가 있다라고 하면, 스킵하도록 넘겨주도록 할거다.

GameObject를 대상으로 GetTransform, GetFixComponent를 만들어 놨는데 추가로 Camera도 같은 느낌으로

shared_ptr<Camera> GetCamera();

이렇게 헬퍼 함수를 만들어 준다.

class Transform; 
class Camera;

이 두개를 전방선언을 한다.

GetCamera를 구현한다. GetTransform과 같은 느낌으로 만들면 된다 .

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

Camera가 있는지 여부를 편하게 체크할 수 있느 헬프 함수를 만들고,

 

다시 GameObject::Update로 가서

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

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

	// TEMP
	if (GetCamera())
		return; 

	_cameraData.matView = Camera::S_MatView;
	_cameraData.matProjection = Camera::S_MatProjection;
	_cameraBuffer->CopyData(_cameraData);

	_transformData.matWorld = GetOrAddTransform()->GetWorldMatrix();
	_transformBuffer->CopyData(_transformData);
}

Camera가 있으면 return을 해준다. 야매코드다. 나중에 구조를 정리할 때 해결이 될 것이다.

if(GetCamera())중단점을 찎고 테스트를 해본다.

Camera인 경우는 버퍼에 데이터를 카피하는 코드가 실행되지 않고 나머지 경우에만 실행되는 것을 알 수 있다.

실행을 하면 아무 이미지도 안뜬다.

문제일 때는 Matrix::Identity; 바꿔서 적용한 코드에 문제가 있는지 확인해 보고 범위를 줄여가면 된다.

	//_cameraData.matView = Camera::S_MatView;
	_cameraData.matView = Matrix::Identity;
	//_cameraData.matProjection = Camera::S_MatProjection;
	_cameraData.matProjection = Matrix::Identity;
	_cameraBuffer->CopyData(_cameraData);

 

6) pipeline->SetConstantBuffer로 레지스터와 _cameraBuffer 연결하기

빠진게 있었다.

Default.hlsl의 b0 레지스터와 b1레지스터에 각각 연결해주기로 했는데, 그 부분이 빠져있다.

GameObject::Render에서 _transformBuffer를 사용하는 곳이 하나 더 있었다.

void GameObject::Render(shared_ptr<Pipeline> pipeline)
{// IA - VS - RS - PS - OM
	{
		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, _cameraBuffer);
		pipeline->SetConstantBuffer(1, SS_VertexShader, _transformBuffer);
		
		pipeline->SetTexture(0, SS_PixelShader, _texture1);
		pipeline->SetSamplerState(0, SS_PixelShader, _samplerState);

		pipeline->DrawIndexed(_geometry->GetIndexCount(), 0, 0);
	}
}

이런 식으로 _cameraBuffer를 추가하고, _transformBuffer의 slot인자도 1로 수정한다.

이제 실행하면 이미지가 잘 뜬다.

 

여기까지 View, Projection, World 까지 정상적으로 넘겨서 연산하는 부분은 잘 복원이 되었다.

 

7) 스케일을 조절하여 이미지가 보이게 하기

다시 GameObject::Update로 돌아가서, Identity가 아닌 원래 버전으로 복원을 시켜서

실행을 해보면 이미지가 보이지 않는다.

왜 ViewProjection 에 값을 넣으면 물체가 안보이는 것일까?

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

800 ,600으로 했었다.

뭔가 안되면 수치를 바꿔가며 해본다.

S_MatProjection = ::XMMatrixOrthographicLH(8, 6, 0.f, 1.f);

이렇게 수치를 바꿔서 실행을 해보면,

 

물체가 보이기는 하는데 작게 보인다는 걸 알 수 있다 .

800, 600으로 했다는 건 화면 크기를 800, 600 크기로 가두리 양식장을 만들어 놓고, Size가 어마어마하게 큰 상태이기 때문에 만들어 준 물체는 엄청 작아서 그렇게 되었다고 결론을 내릴 수 있다

화면크기를 작게 하거나, 처음에 물체를 만들 떄 100 늘려서 스케일을 크게 해주는 걸 고려할 수 있다.

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(); 
		_monster->GetTransform()->SetScale(Vec3(100.f, 100.f, 1.f)); 
		// ..
	}

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

이렇게 Scale을 늘린다고 하면

그리고 Camera::UpdateMatrix에서 다시 800, 600으로 돌리더라도

실행을 하면 이제는 이미지가 보인다는 걸 알 수 있다.

 

다시 Game::Init의 SetSclae부분을 주석 처리 하고, Camera::UpdateMatrix에서 8, 6으로 되돌린다. 나중에 바꾸면 된다.

이런식으로 스케일과 연관성 있는 부분이라 코드상의 문제는 아니다.

 

지금까지 한 게 결국 카메라 데이터를 이용해서,

View, Projection

World

를 각각 따로 받아가지고, 전송하게끔 만들어준 부분까지 완료가 되었다고 볼 수 있다.

 

 

2. Rendering pipeline 관련 코드 GameObject에서 MeshRenderer로 옮기기

여기까지 1차적으로 Camera 컴포넌트를 성공적으로 넣어줬다라고 볼 수 있다.

수정해 줘야 할 부분이 많다.

GameObject.h를 보면

	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;

애당초 여기에 들어가 있어야 할 것들이 아니다.

Rendering 하는 거랑만 연관이 있어야 하는 부분이다 보니까 부품으로 빼주도록 한다.

오늘 필요한 부품은 미리미리 빼주고, 정리를 해본다.

이 렌더링 파이프라인과 관련된 부분들은 Default.hlsl에서 화면에다 뭔가를 그리고 싶을 때,IA-VS-RS-PS-OM 단계로 넘어와서 화면이 보이게끔 만드는게 목표이기 때문에 온갖 난리를 쳤다.

하지만 모든 물체마다 다 이 단계를 거칠 필요는 없다.

GameObject::Update에서

if(GetCamera()) return;

으로 Camera는 막아둔 이유도 그것과 무관하지 않았다.

화면에 그려줘야 된다는 걸 넣어준 물체들만 보여야하는데 그걸 유니티에서는 MeshRenderer라는게 있었다.

이 MeshRenderer 컴포넌트를 들고 있는 애들만 렌더링 파이프라인 단계를 타서 그려지게끔 만들어주면 된다는 얘기가 된다.

Rendering 필터에 Component를 상속받은 MeshRenderer라는 클래스를 만들어 준다.

1차적으로 내용을 옮겨 놓은 다음에 다시 한번 수정을 한다.

그려지는 건 하드코딩해 놓은 것을 가져오면 된다.

GameObject는 빈 깡통에 불가하고 Component를 들고 있는 역할만 해야 하는 것이지, 복잡하게 많은 영역들을 들고 있을 필요는 없다.

 

1) GameObject.h에서 RenderingPipeline 관련 변수들 MeshRenderer로 이주

그래서 GameObject.h에서

	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;

private:
	// Camera
	CameraData _cameraData;
	shared_ptr<ConstantBuffer<CameraData>> _cameraBuffer;

	TransformData _transformData;
	shared_ptr<ConstantBuffer<TransformData>> _transformBuffer;

이 부분을 MeshRenderer.h에 옮긴다.

ComPtr<ID3D11Device> _device;

는 복사해서 GameObject.h에도 있게 한다.

 

나중에는 MeshRenderer에서 다 들고 있을 필요 없고, 일부분은 심지어 공통적인 렌더링파이프라인이고, 일부분은 물체에 종속적인 부분이기 때문에 다시 구분하긴 하겠지만 1차적으로 깔끔하게 하기 위해 위의 코드들을 GameObject.h에서 날려주도록 하면, GameObject는 자신이 필요한 애들만 들고 있게 된다.

GameObject.h에

shared_ptr<MeshRenderer> GetMeshRenderer();

를 추가하고,

class MeshRenderer;

를 전방선언 해준다.

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

이렇게 구현한다.

 

GameObject::Update()에서

// TEMP
	if (GetCamera())
		return;

이 코드를 삭제한다.

 

GameObject.h의 코드를 대거 옮겼기 때문에 빨간줄이 많이 뜬다.

그 부분들을 꺼내서 이전을 시켜주면 된다 .

 

2) GameObject의 생성자 내용 MeshRenderer 생성자로 옮기기

GameObject 생성자의 내용들을 싹 MeshRenderer의 생성자로 이주시킨다.

이주하고 보니 MeshRenderer에 Device와 DeviceContext가 필요하다.

Device와 DeviceContext를 생성자로 받아줬던 GameObject의 생성자 부분과 같게 만들어 준다.

생성자 인자로 받는게 싫으면 싱글턴으로 만드는 것도 고려할 수 있다.

지금은 싱글턴이나 전역변수를 되도록 사용 않는 방향으로 해본다.

MeshRenderer::MeshRenderer(ComPtr<ID3D11Device> device, ComPtr<ID3D11DeviceContext> deviceContext) 
	: Super(ComponentType::MeshRenderer), _device(device)
{
	_geometry = make_shared<Geometry<VertexTextureData>>();
	GeometryHelper::CreateRectangle(_geometry);
	// _geometry = make_shared<Geometry<VertexColorData>>();
	//GeometryHelper::CreateRectangle(_geometry, Color{1.0f, 0.f, 0.f, 1.0f});

	_vertexBuffer = make_shared<VertexBuffer>(device);
	_vertexBuffer->Create(_geometry->GetVertices());

	_indexBuffer = make_shared<IndexBuffer>(device);
	_indexBuffer->Create(_geometry->GetIndices());

	_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());
	//_inputLayout->Create(VertexColorData::descs, _vertexShader->GetBlob());

	_pixelShader = make_shared<PixelShader>(device);
	_pixelShader->Create(L"Default.hlsl", "PS", "ps_5_0");

	_rasterizerState = make_shared<RasterizerState>(device);
	_rasterizerState->Create();

	_blendState = make_shared<BlendState>(device);
	_blendState->Create();

	_cameraBuffer = make_shared<ConstantBuffer<CameraData>>(device, deviceContext);
	_cameraBuffer->Create();

	_transformBuffer = make_shared<ConstantBuffer<TransformData>>(device, deviceContext);
	_transformBuffer->Create();

	_texture1 = make_shared<Texture>(device);
	_texture1->Create(L"chiikawa.png");

	_samplerState = make_shared<SamplerState>(device);
	_samplerState->Create();
}

이렇게 하니 빨간줄이 없어졌다.

나중에 파일 이름 같은 거 하드코딩으로 안하게 정리해줘야 한다.

일단 그림이 뜨는 걸 확인하면서 넘어갈 것이기 때문에 이렇게 진행을 해본다.

 

3) GameObject::Update에서 component들을 갱신하는 부분은 냅두고 _cameraData와 _transformData 갱신해 _cameraBuffer, _transformBuffer에 CopyData하는 코드 MeshRenderer::Update로 옮기기

GameObject::Update 에서도빨간줄이 뜬다.

	_cameraData.matView = Camera::S_MatView;
	//_cameraData.matView = Matrix::Identity;
	_cameraData.matProjection = Camera::S_MatProjection;
	//_cameraData.matProjection = Matrix::Identity;
	_cameraBuffer->CopyData(_cameraData);

	_transformData.matWorld = GetOrAddTransform()->GetWorldMatrix();
	_transformBuffer->CopyData(_transformData);

이 코드도 날려서 MeshRenderer 쪽에 복사를 해주면 된다.

그 전에 MeshRenderer.h에

virtual void Update() override;

이 함수를 추가하고 정의부를 만들어서

void MeshRenderer::Update()
{
	_cameraData.matView = Camera::S_MatView;
	//_cameraData.matView = Matrix::Identity;
	_cameraData.matProjection = Camera::S_MatProjection;
	//_cameraData.matProjection = Matrix::Identity;
	_cameraBuffer->CopyData(_cameraData);

	_transformData.matWorld = GetOrAddTransform()->GetWorldMatrix();
	_transformBuffer->CopyData(_transformData);
}

이렇게 GameObject::Update의 코드를 옮겨준다.

그리고MeshRenderer.cpp에

#include "Camera.h"

를 추가해준다.

이러면 MeshRenderer만 대상으로 S_MatView, S_MatProjection 이런 부분들이 호출 될 것이기 때문에 모든 게임 오브젝트가 얘를 덮어 쓰진 않을 것이고, 그래서 편리하게 작업을 할 수 있을 것이고,

_transformData.matWorld = GetTransform()->GetWorldMatrix();

GetOrAddTransform을 GetTransform으로 수정한다.

 

4) GameObject::Render의 코드를 MeshRenderer::Render로 옮기기

void GameObject::Render(shared_ptr<Pipeline> pipeline)도 빨간 줄이 뜨는데,

원래는 pipeline에서 넘겨서 작업을 하게 유도를 했었는데

MeshRenderer가 해주게끔 유도를 해준다.

pipeline을 받아서 똑같이 넘겨줘도 되고, 임시적으로는 MeshRenderer.h 에서 하는 걸 고려할 수 있다.

MeshRenderer.h에

void Render(shared_ptr<Pipeline> pipeline);

MeshRenderer 생성자에 pipeline을 세번째 인자로 받아서 Update함수에서 Render를 하는 것도 고려할 수 있다. MeshRenderer가 말 그대로 Mesh를 렌더링 하는 기능을 하는 애니까 그렇게 고려를 할 수 있을 거다.

일단은 GameObject::Render의 코드를 전부 MeshRenderer::Render로 옮긴다.

void MeshRenderer::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, _cameraBuffer);
	pipeline->SetConstantBuffer(1, SS_VertexShader, _transformBuffer);

	pipeline->SetTexture(0, SS_PixelShader, _texture1);
	pipeline->SetSamplerState(0, SS_PixelShader, _samplerState);

	pipeline->DrawIndexed(_geometry->GetIndexCount(), 0, 0);

}

GameObject::Render 함수는 삭제한다.

빌드를 하면 에러가 발생한다.

GameObject.cpp 에

#include "MeshRenderer.h"

를 안해서 그렇다.

그리고

void Game::Render()
{
	_graphics->RenderBegin();

	// IA - VS - RS - PS - OM
	{
		//_monster->Render(_pipeline); 
	}

	_graphics->RenderEnd();
}

Render를 호추하는 부분을 주석처리 하면 빌드가 된다.

 

5) _monster 오브젝트에 MeshRenderer 컴포넌트를 추가하기

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(); 
		_monster->AddComponent(make_shared<MeshRenderer>()); 
		// _monster->GetTransform()->SetScale(Vec3(100.f, 100.f, 1.f)); 
		// ..
	}

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

이렇게 _monster->AddComponent(make_shared<MeshRenderer>()); 를 추가하면 _monster를 대상으로 뭔가를 그려주는 작업을 해주게 된다.

그리고 Game.cpp에

#include "MeshRenderer.h"

를 추가한다.

그리고 MeshRenderer를 만들 떄는 항상 device와 deviceContext가 필요 했으니까

_monster->AddComponent(make_shared<MeshRenderer>(_graphics->GetDevice(), _graphics->GetDeviceContext()));

이렇게 넘겨주면 된다.

 

6) Game::Render에서 MeshRenderer::Render 호출

Game::Render에서 Rendering하는 부분이 딸려서 가야겠지만, 나중에 MeshRenderer에다가 Rendering 기능도 이전을 해줄 것이다. 그래서 MeshRenerer생성자에 넘겨주지 않고, Game::Render에서 Render만 바로 하드코딩 하도록 한다. 테스트를 하기 위해

void Game::Render()
{
	_graphics->RenderBegin();

	// IA - VS - RS - PS - OM
	{
		//_monster->Render(_pipeline); 
		// TEMP
		_monster->GetMeshRenderer()->Render(_pipeline);
	}

	_graphics->RenderEnd();
}

빌드를 해보고 실행을 하면 똑같이 이미지가 뜨는 것을 확인할 수 있다.

여기까지가 MeshRenderer를 이용한 것이다.

이제 카메라는 정리가 된 것이다.

이게 유니티의 부품을 추가하는 방식의 장점이라고 보면 된다.

카메라 컴포넌트를 추가하면 카메라를 전담하게 될 것이고, MeshRenderer를 추가하면 뭔가를 그려주게 될 것이고,

지금은 하드 코딩한 상태니, Render하는 부분이 Update와 통합이 되어서 MeshRenderer::Update에서 그려지는 거 까지 감당하게끔 만들어 주면 된다.

 

GameObject가 결국

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

이 다섯 단계를 순회 하면서

우리만의 생명주기를 만들어주게 될 것이고, 그 생명주기를 따라서

딸려있는 부품들까지 호출이 되면서, 게임이 진행된다.

이번 시간은 구조를 가지고 생각을 해야 한다.

이미지를 그리는 것 까지 했으면 애니메이션은 단순하다.

여러개의 이미지를 왔다 갔다 틀어주는 것이다.

바로 3D로 넘어가지 않는건 엔진 구조 잡는게 생각보다 복잡하기 때문이다.

3D로 넘어가게 되면 그래픽스 지식에 신경 써야 하기 때문에 엔진 구조를 신경쓰기 어렵다.

그 다음에 생각해야 하는건 MeshRenderer라고 해서 그리는 기능을 모아 놓은 것 까진 좋은데 이 중에서 또 따로 뺄 수 있는 게 있을지 고민해야 한다.

어떤 부분은 공용적이고 어떤 부분은 따로 빼야 한다.

 

3. Mesh는 기하학 데이터, Material은 shader와 넘겨줄 인자들

유니티를 보면 MeshFilter, MeshRenderer가 있는데 합쳐서 만들 것이다.

MeshFilter 컴포넌트를 보면 Mesh에 Cube가 들어있다. 큐브라는 모양을 나타내는 것이고,

MeshRenderer 컴포넌트의 Material이라는 게 있는데 기본적으로 Shader에 넘기는 기능들과 관련이 있다고 보면 된다. 어떠한 shader로 되어 있는지, 온갖 인자들을 넘겨주는데 이걸 묶어서 material이라고 부른다. 어떤 셰이더를 사용할 것인지, 어떤 인자를 사용할 것인지에 불과하다. 기본 shader가 작성한 코드에선 Default.hlsl이라고 볼 수 있다. 지금은 넘겨주는 인자들이

cbuffer CameraData : register(b0) 
{ 
    row_major matrix matView;
    row_major matrix matProjection;
}

cbuffer TransformData : register(b1) 
{
    row_major matrix matWorld;
}
Texture2D texture0 : register(t0); 

SamplerState sampler0 : register(s0);

상수 버퍼, 텍스쳐, 샘플러 스테이에 넘겨주는 이거라고 볼 수 있다.

이런 애들이 Material을 구성하는 요소들이다.

후반부에 가면 Shader와 넘겨줄 수 있는 인자들을 하나로 묶어서 Material 이라는 개념으로 만들어 주면 된다.

그 외에 MeshFilter컴포넌트에 있는 Mesh라는 개념이 있었다. Cube라는 모양을 나타내는 형태를 나타내는 게 있었는데,

  	// Mesh
	shared_ptr<Geometry<VertexTextureData>> _geometry;
	shared_ptr<VertexBuffer> _vertexBuffer;
	shared_ptr<IndexBuffer> _indexBuffer;
	shared_ptr<InputLayout> _inputLayout;

	// Material
	shared_ptr<VertexShader> _vertexShader;
	shared_ptr<RasterizerState> _rasterizerState;
	shared_ptr<PixelShader> _pixelShader;
	shared_ptr<Texture> _texture1;

	shared_ptr<SamplerState> _samplerState;
	shared_ptr<BlendState> _blendState;

여기서 보면 어떤 부분은 Material로 인정을 해야 할 것이고,

어떤 부분은 Mesh로 바꿔서 관리를 하면 될 것이다.

 

geometry라는 기하학적 도형을 표현하는 방식을 만들어 놨고

그 도형을 토대로 vertex와 index 목록으로 되어있는 물체를 표현하는 방식이었다.

그걸 이용해서 원하는 형식대로 만들어 놔서 return해 geometry라는 개념을 만들었다.

그걸 이용해서 vertexBuffer, indexBuffer, inputLayout이 4총사를 묶어서 따로 관리했다 볼 수 있는 거다.

inputLayout은 어떻게 묘사한 것이니 아래로 가도 되긴 하지만, 중요한 건 geometry, vertexBuffer, indexBuffer는 통합해서 하나로 관리해도 된다.

모형을 나타내고, 모형을 GPU에 떠넘길 때도 이 정보를 이용할 수 있다.

 

Material도 이중에서 일부는 Shader와 State같은 건 바뀌지 않는 것들이고,

samplerState, blendState는 그려주는 파이프라인 상태랑 관련이 있는 것이고,

vertexShader, pixelShader는 로드한 셰이더 파일들이랑 연관성이 있는 것이다. 리소스라고 볼 수 있는 것이다.

이런 식으로 세분화해서 구분할 필요가 있다.

MeshRenderer를 몰빵해서 만들었지만 이 안에서도 생각을 해보면 구분할 거리가 많다는 것이다.

상용 엔진을 컨닝하면서 보면 된다. 보면 이래서 이렇게 만들었구나 힌트를 많이 얻을 수 있다.

반응형