DirectX

29. 엔진구조_Material

devRiripong 2024. 1. 26.
반응형

Material과 Mesh라는 개념으로 MeshRenderer.h에 있는 아래의 애들을 빼줄 것이다.

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

	// Material
	shared_ptr<InputLayout> _inputLayout;
	shared_ptr<VertexShader> _vertexShader;
	shared_ptr<PixelShader> _pixelShader;
	shared_ptr<Texture> _texture1;

00.Engine / Resource 필터에 파일들을 파 두었다.

 

1. Mesh

Mesh의 개념은 물체가 어떻게 생겼는지

유니티에서 보면 Cube, Sphere 등 고를 수 있다.

 

1) MeshRenderer.h의 // Mesh 부분을 Mesh.h로 옮긴다.

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

 

2) void Mesh::CreateDefaultRectangle()은 MeshRenderer 생성자에서 만들어 준 부분을 가져온다.

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

Geometry가 Helper로 만든 간단한 모양이 아니라 3D 그런 거면 fbx나 다른 파일로 이루어진 그런 지오메트리를 만들어 주고 그걸 다시 vertexBuffer, indexBuffer를 연결해 주는 식으로 가야 한다. 지금은 간단한 사각형 만들거라 이렇게 만든다.

 

3) MeshRenderer.h의 // Mesh 가 있던 자리에 shared_ptr<Mesh> _mesh;선언하기

빌드를 하면 RenderManager::RenderObjects()에서 에러가 발생하는데

_pipeline->SetVertexBuffer(meshRenderer->_vertexBuffer);
_pipeline->SetIndexBuffer(meshRenderer->_indexBuffer);

meshRenderer가 들고 있던 Mesh 부분을 이동시켜서 그렇다.

근데 날려준게 아니라 Mesh에서 뭉쳐서 관리할 수 있게 만들어 준 것이다.

MeshRenderer.h에 //Mesh가 있던 자리에

// Mesh
	shared_ptr<Mesh> _mesh;

이렇게 추가하고

class Mesh;

전방 선언을 해준다.

MeshRenderer.cpp에

#include "Mesh.h"

를 해주고,

 

4) MeshRenderer.h SetMesh, GetMesh를 만들고 활용해 meshRenderer에 있었던 _vertexBuffer, _indexBuffer의 빈자리 채우기

MeshRenderer.h에

세팅하는 함수 SetMesh와 Get하는 함수 GetMesh를 선언한다.

void SetMesh(shared_ptr<Mesh> mesh) { _mesh = mesh; } 
shared_ptr<Mesh> GetMesh() { return _mesh; }

유니티에서 Mesh를 설정하는 것과 유사하다.

유니티에서 할 수 있는 건 툴에서 제작이 가능하다.

 

RenderManager::RenderObjects()에서 에러가 나는 부분을 위에서 만들어준 GetMesh를 이용하여

_pipeline->SetVertexBuffer(meshRenderer->_vertexBuffer);
_pipeline->SetIndexBuffer(meshRenderer->_indexBuffer);

에서

_pipeline->SetVertexBuffer(meshRenderer->GetMesh()->GetVertexBuffer());
_pipeline->SetIndexBuffer(meshRenderer->GetMesh()->GetIndexBuffer());

이렇게 수정하면 된다.

_pipeline->DrawIndexed(meshRenderer->_geometry->GetIndexCount(), 0, 0);

여기서도 에러가 발생하는데

_pipeline->DrawIndexed(meshRenderer->GetMesh()->GetIndexBuffer()->GetCount(), 0, 0);

이렇게 수정한다.

이제 빌드가 된다.

하지만 실행하면 크래시가 난다.

5) ResourceManager::CreateDefaultMesh()에서 mesh를 만들고, _resources에 넣기

MeshRenderer에서 SetMesh에서 shared_ptr <Mesh> mesh를 받아주고 있는데 mesh를 딱히 세팅하지 않았다.

mesh도 하나의 리소스다.

ResourceManager에서

CreateDefaultMesh가 있었다. 여기서 Mesh를 만들어 줄 것이다.

void ResourceManager::CreateDefaultMesh()
{
	// Mesh
	shared_ptr<Mesh> mesh = make_shared<Mesh>(_device); 
	mesh->SetName(L"Rectangle"); 
	mesh->CreateDefaultRectangle(); 
	Add(mesh->GetName(), mesh); 
}

이렇게 등록을 해 놓으면 “Rectangle”로 꺼내올 수 있다.

ResourceManager.cpp에 

#include "Mesh.h"

을 넣어준다.

 

6) SceneManager::LoadTestScene()에서 mesh를 RESOURCES로 부터 Get 하여 SetMesh 해주기

이제 “Rectangle”이란 메쉬를 사용할 수 있게 된 거니까,

추가적으로 다른 오브젝트를 사용하면SceneManager::LoadTestScene()에서

Camera, Monster를 만들고 있었는데,

// Monster
{
    shared_ptr<GameObject> monster = make_shared<GameObject>(_graphics->GetDevice(), _graphics->GetDeviceContext());
    {
        monster->GetOrAddTransform();
        monster->AddComponent(make_shared<MeshRenderer>(_graphics->GetDevice(), _graphics->GetDeviceContext()));
        // _monster->GetTransform()->SetScale(Vec3(100.f, 100.f, 1.f)); 
        // ..
        scene->AddGameObject(monster); 
    }
}

이 부분을

// Monster
{
    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); 

// TOOD: Material

        auto mesh = RESOURCES->Get<Mesh>(L"Rectangle"); 
        meshRenderer->SetMesh(mesh); 
        scene->AddGameObject(monster); 
    }
}

이렇게 meshRenderer를 만들고, 넣는 부분을 분리해 줬고, mesh를 세팅해 주는 코드를 추가한다.

#include "ResourceManager.h"
#include "Game.h"
#include "Mesh.h"

이것도 추가한다.

 

나중에 애니메이션도 추가되고, Material도 추가해야 한다.

이런 식으로 조립해서 만드는 게 핵심이다.

실행을 하면 이미지가 뜬다.

 

유니티로 치면 Mesh와 MeshFilter를 합쳐서 만든 것이다.

 

2. Material 

이제 Material을 만들어야 한다.

material은 어떤 재질을 얘기한다.

나무, 철, 천 같은 걸 재질이라 한다.

Shader와 Shader와 관련된 인자들로 묶인 파일에 불과하다는 걸 알 수 있다.

MeshRenderer.h의 //Material 이 부분을 묶어서 하나의 Material로 만들어 주면 된다.

아직 Shader에 대한 리소스를 만들지 않았다. 이거부터 먼저 해야 한다.

 

1) MeshRenderer의 //Material 부분을 Shader.h로 옮기고, Shader를 ResourceManager의 friend로 하기

shared_ptr<InputLayout> _inputLayout;
shared_ptr<VertexShader> _vertexShader;
shared_ptr<PixelShader> _pixelShader;

MeshRenderer.h의 //Material 부분 중 이걸 Shader.h로 옮긴다.

#pragma once
#include "ResourceBase.h"
class Shader : public ResourceBase
{
	using Super = ResourceBase; 
public:
	Shader(); 
	virtual ~Shader(); 

	shared_ptr<InputLayout> GetInputLayout() { return _inputLayout; }
	shared_ptr<VertexShader> GetVertexShader() { return _vertexShader; }
	shared_ptr<PixelShader> GetPixelShader() { return _pixelShader; }

private: 
	friend class ResourceManager; 

	shared_ptr<InputLayout> _inputLayout;
	shared_ptr<VertexShader> _vertexShader;
	shared_ptr<PixelShader> _pixelShader;
};

Set함수를 만들어도 되지만 friend로 했다.

 

2) Shader와 Texture들고 있게 Material.h 채우기

Material.h는 무엇인가

Shader에다가 추가적으로 여러 가지의 텍스처들 혹은 인자들이 들어가 있는 걸 말한다.

지금 단계에선 Material에서 넘길 부분들이 별로 없다.

넘기는 인들이 Dfault.hlsl에서 보면

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

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

상수버퍼 2개랑

Texture2D texture0 : register(t0);

텍스쳐 1개밖에 없기 때문에

나중에 라이팅 같은 게 추가된다면

Material에 추가되는 기능들이 많아질 수 있다.

지금은 얼마 없지만 쉐이더에 넘기는 온갖 인자들을 만들어 준다고 보면 된다.

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

이렇게 만들어 줄 수 있다.

나중에 가면 복잡해질 수도 있다.

 

3) ResourceManager::CreateDefaultShader로 MeshRenderer의 생성자에서 _vertexShader, _inputLayout, _pixelShader 옮기고 shader에 세팅해서 _resources에 넣기

ResourceManager::CreateDefaultShader는 MeshRenderer의 생성자에서 _vertexShader, _inputLayout, _pixelShader 부분을 이동시킨다.

void ResourceManager::CreateDefaultShader()
{
	auto vertexShader = make_shared<VertexShader>(_device);
	vertexShader->Create(L"Default.hlsl", "VS", "vs_5_0");
    
	auto inputLayout = make_shared<InputLayout>(_device);
	inputLayout->Create(VertexTextureData::descs, vertexShader->GetBlob());
    
	auto pixelShader = make_shared<PixelShader>(_device);
	pixelShader->Create(L"Default.hlsl", "PS", "ps_5_0");

	// Shader
	shared_ptr<Shader> shader = make_shared<Shader>(); 
	shader->SetName(L"Default");
	shader->_vertexShader = vertexShader; 
	shader->_inputLayout = inputLayout; 
	shader->_pixelShader = pixelShader; 
	Add(shader->GetName(), shader); 
}

이렇게 하면 DefaultShader를 사용할 준비가 된 것이다.

 

4) ResourceManager::CreateDefaultMaterial에서 Shader와 Texture를 세팅한 material을 _resources에 넣기

그다음에 ResourceManager::CreateDefaultMaterial로 넘어가면 된다.

void ResourceManager::CreateDefaultMaterial()
{
	shared_ptr<Material> material = make_shared<Material>(); 
	material->SetName(L"Default"); 
	material->SetShader(Get<Shader>(L"Default"));
	material->SetTexture(Get<Texture>(L"chiikawa"));
	Add(material->GetName(), material); 
}

 

5) MeshRenderer에서 필요 없는 부분을 정리하고, shared_ptr <Material> _material 변수를 선언하고, material을 활용할 헬퍼 함수들을 만들기

빌드하면, RenderManager::RenderObjects에서

info.inputLayout = meshRenderer->_inputLayout;
info.vertexShader = meshRenderer->_vertexShader;
info.pixelShader = meshRenderer->_pixelShader;

meshRenderer에서 Shader로 세 변수들을 옮겨줬기 때문에 에러가 발생한다.

 

MeshRenderer.h에서

class Material;

을 전방선언하고,

shared_ptr<Material> _material;

를 추가한다.

shared_ptr<Texture> _texture1;

옮기 않고 남겨뒀던 이건, material에 들어가는 인자 값 같은 거라 _material에 소속이 되어가지고 들어가는 개념이니 삭제한다. 생성자에도 texture1을 만드는 부분을 삭제한다.

 

Update함수도 아무것도 안 하니 삭제한다.

 

그리고 나머지 헬퍼 함수들을 만든다.

 	void SetMaterial(shared_ptr<Material> material) { _material = material; }
	void SetShader(shared_ptr<Shader> shader) { _material->SetShader(shader); }
	void SetMesh(shared_ptr<Mesh> mesh) { _mesh = mesh; }
	void SetTexture(shared_ptr<Texture> texture) { _material->SetTexture(texture); };

	auto GetMaterial() { return _material; }
	auto GetVertexShader() { return GetMaterial()->GetShader()->GetVertexShader(); }
	auto GetInputLayout() { return GetMaterial()->GetShader()->GetInputLayout(); }
	auto GetPixelShader() { return GetMaterial()->GetShader()->GetPixelShader(); }

	shared_ptr<Mesh> GetMesh() { return _mesh; }
	shared_ptr<Texture> GetTexture() { return GetMaterial()->GetTexture(); }

그리고

#include "Material.h"
#include "Shader.h"

를 클래스 전방 선언 아래에 추가한다.

_mesh와 _material을 MeshRenderer로 꺼내 쓰라고 이 함수를 제공하는 것이다.

MeshRenderer에 원하는 mesh와 material을 설정하면 꺼내 쓸 수 있는 개념이다.

 

6) SceneManager::LoadTestScene()에서 _resources에서 Material을 Get 해서 meshRenderer에 set 하기

SceneManager::LoadTestScene()로 돌아가서

/ /TODO:Material로 남겨준 부분을 채워준다.

// Monster
	{
		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 material = RESOURCES->Get<Material>(L"Default"); 
			meshRenderer->SetMaterial(material); 

			auto mesh = RESOURCES->Get<Mesh>(L"Rectangle"); 
			meshRenderer->SetMesh(mesh); 
		}
			scene->AddGameObject(monster); 
	}

여기까지 했으면,

monster 오브젝트가 만들어지고,

meshRenderer가 monster에 추가되고

meshRenderer에 material와 mesh가 등록된 것까지 된 것이다.

 

7) RenderManager::RenderObjects()에서 MeshRenderer의 헬퍼 함수들을 통해 _mesh, _material에 세팅된 요소들 활용하게 하기 

RenderManager::RenderObjects()에서 에러 나던 부분도

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

		// IA - VS - RS - PS - OM
		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);
	}

이렇게 meshRenderer→Get~으로 세팅을 해주면 된다.

빌드가 된다.

 

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

많이 정리가 되었다.

유니티 엔진도 이렇게 동작한다.

material은 공유해서 사용하는 거라 하나를 고치면 그것을 사용하는 애들에게 다 영향을 준다.

 

ResourceBase를 보면 Save, Load를 만들어 놨다.

툴 상에서는 하나의 파일로 Material이 보인다. 이런 파일 형태로 관리를 할 수 있어야 한다.

 

Material파일을 워드패드로 열어보면 코드로 되어 있는 것을 볼 수 있다.

파일 입출력을 통해 이해할 수 있는 형태로 RsourceBase에 있는 내용들을 적어야 한다는 게 결론이다.

 

남은 건 애니메이션, 파일 입출력이다.

반응형

'DirectX' 카테고리의 다른 글

31. 엔진구조_Data  (0) 2024.01.29
30. 엔진구조_Animation  (0) 2024.01.28
28. 엔진구조_ RenderManager  (0) 2024.01.23
27. 엔진구조_ResourceManager  (0) 2024.01.22
26. 엔진구조_SceneManager  (0) 2024.01.21

댓글