DirectX

55. 모델_모델 띄우기

devRiripong 2024. 2. 28.
반응형
void AssimpTool::Init()
{

	{
		shared_ptr<Converter> converter = make_shared<Converter>();

		// FBX -> Memory
		converter->ReadAssetFile(L"Tower/Tower.fbx");

		// Memory -> CustomData (File)
		// 1차 목표
		converter->ExportMaterialData(L"Tower/Tower");
		converter->ExportModelData(L"Tower/Tower");

		// CustomData (File) -> Memory
	}
}

 

지난 시간에 이어서 1차 목표 중 ExportModelData를 구현하는 작업을 해 본다. 

void Converter::ExportModelData(wstring savePath)
{
	wstring finalPath = _modelPath + savePath + L".mesh"; // .data, .model 로 해도 상관 없다. 하나 골라서 사용하면 된다. 
	ReadModelData(_scene->mRootNode, -1, -1); 
	WriteModelFile(finalPath); 
}

에서

WriteModelFile을 만들어 주면 된다.

여기서 뭔가 바이너리 형태로 만들어주고 싶다

서버시간에 했던 것과 크게 다를 것은 없다.

 

1. ExportModelData에서 호출되는 WriteModelFile에서 파일을 Open 하고 Write 하는 용도로 사용할 헬퍼 클래스 FileUtils를 구현하기

 

Engine/98. Utils에 새로운 클래스를 추가한다.

클래스의 이름은 FileUtils라고 한다.

파일을 만들거나 열어서 데이터를 쭉 읽거나 쓰거나 둘 중 하나를 하는 걸 만드는 것이다.

읽는 거 하나, 쓰는 거 하나 따로 만들어서 하거나, 알아서 사용하라고 한번에 만드는 경우도 있는데 이번엔 한 번에 묶어서 만들어 준다.

고전적인 WinApi 방식으로 만들어 본다.

#pragma once

// Write를 할건지 Read를 할건지
enum FileMode : uint8 
{
	Write, 
	Read,
}; 

class FileUtils
{
public: 
	FileUtils(); 
	~FileUtils(); 

	void Open(wstring filePath, FileMode mode); // mode에 읽는 용도인지 쓰는 용도인지
	
	// 일반적인 데이터는 이렇게 사이즈 별로 넣어준다.
	// 예외적인 경우는 string 같은 클래스의 경우는 동적 배열 느낌이라 여기서 막바로 사용하면 안된다.
	template<typename T>
	void Write(const T& data) // 복사 않고 참조
	{
		DWORD numOfBytes = 0;
		assert(::WriteFile(_handle, &data, sizeof(T), (LPDWORD)&numOfBytes, nullptr));
	}

	// 템플릿 특수화 문법: 템플릿을 사용하지만 string을 사용하는 경우에만 예외적으로 얘를 사용하겠다.
	template<>
	void Write<string>(const string& data)
	{
		return Write(data);
	}

	// vector나 string 같이 사이즈를 알 수 없는 경우
	void Write(void* data, uint32 dataSize);
	void Write(const string& data);

	// 전달 받은 data에 넣어주는 버전
	template<typename T>
	void Read(OUT T& data)
	{
		DWORD numOfBytes = 0;
		assert(::ReadFile(_handle, &data, sizeof(T), (LPDWORD)&numOfBytes, nullptr));
	}

	// 함수 안에서 선언한 data에 채워주고 리턴해 복사하는 버전
	template<typename T>
	T Read()
	{
		T data;
		Read(data);
		return data;
	}

	void Read(void** data, uint32 dataSize);
	void Read(OUT string& data);

private: 
	HANDLE _handle = INVALID_HANDLE_VALUE; // 파일 핸들 

};
#include "pch.h"
#include "FileUtils.h"

FileUtils::FileUtils()
{

}

FileUtils::~FileUtils()
{
	if (_handle != INVALID_HANDLE_VALUE)
	{
		::CloseHandle(_handle);
		_handle = INVALID_HANDLE_VALUE;
	}
}

void FileUtils::Open(wstring filePath, FileMode mode)
{
	if (mode == FileMode::Write)
	{
		// 이 handle에서 데이터를 꺼내 쓰면 된다. 
		// 새로 만든다.
		_handle = ::CreateFile
		( // ::의 의미는 만든게 아니라 API상에서 제공하는 것이란 의미
			filePath.c_str(),
			GENERIC_WRITE,
			0,
			nullptr,
			CREATE_ALWAYS,
			FILE_ATTRIBUTE_NORMAL,
			nullptr
		);
	}
	else
	{// 있는 것을 열어 주겠다. 없으면 따로 만들진 않겠다.
		_handle = ::CreateFile
		(
			filePath.c_str(),
			GENERIC_READ,
			FILE_SHARE_READ,
			nullptr,
			OPEN_EXISTING,
			FILE_ATTRIBUTE_NORMAL,
			nullptr
		);
	}

	assert(_handle != INVALID_HANDLE_VALUE); // 핸들에서 데이터를 꺼내 쓰면 된다.
}

void FileUtils::Write(void* data, uint32 dataSize)
{
	uint32 numOfBytes = 0;
	assert(::WriteFile(_handle, data, dataSize, reinterpret_cast<LPDWORD>(&numOfBytes), nullptr));
}

void FileUtils::Write(const string& data)
{
	// 가변 데이터니 사이즈를 먼저 넣어준다.(문자열의 길이)
	uint32 size = (uint32)data.size();
	Write(size);

	if (data.size() == 0)
		return;

	// 사이즈에 해당하는 데이터를 밀어 넣는다.(문자열)
	Write((void*)data.data(), size);
}

void FileUtils::Read(void** data, uint32 dataSize)
{
	uint32 numOfBytes = 0;
	assert(::ReadFile(_handle, *data, dataSize, reinterpret_cast<LPDWORD>(&numOfBytes), nullptr));
}

void FileUtils::Read(OUT string& data)
{
	uint32 size = Read<uint32>();// 데이터 스트림의 저장 포맷이 첫 4바이트에 길이를 저장하기 때문

	if (size == 0)
		return;

	char* temp = new char[size + 1];
	temp[size] = 0;
	Read((void**)&temp, size);
	data = temp;
	delete[] temp;
}

이 FileUtils라는 걸 통해 바이너리 형태로 바일을 Write하거나 Read 할 일 있으면 얘를 이용해서 만들면 된다.

 

2. Converter::ExportMaterialData에서 호출되는 WriteMaterialData에서 SetText의 인자로 호출되는 Converter::WriteTexture에서 fbx에 텍스쳐가 들어가 있고, 바이너리 형태인 경우 FileUtils를 이용해 file->Open, file->Write를 실행하기

이제 AssimpTool로 돌아가서 관련된 부분들을 채우는 게 중요하다.

 

Converter::WriteTexture에서

	if (srcTexture) // fbx에 텍스쳐가 들어가 있는 경우
	{
		string pathStr = saveFolder + fileName;

		if (srcTexture->mHeight == 0) // 데이터가 1차원 배열 형태로 저장되어 있다면 바이너리 모드로 만든다.
		{
			shared_ptr<FileUtils> file = make_shared<FileUtils>();
			file->Open(Utils::ToWString(pathStr), FileMode::Write);
			file->Write(srcTexture->pcData, srcTexture->mWidth);
		}

주석 처리 했던 부분을 주석을 해제한다.

Height가 0이면 바이너리 형태로 긁어 오라는 뜻이어서 FileUtils를 사용했다.

#include "FileUtils.h"

도 추가한다.

Open을 해서 Write버전을 만들어 주고 Write를 해주면 된다.

file 작업이 다 끝났으면 FileUtils라는 스마트 포인터가 해제가 되면서

FileUtils::~FileUtils()
{
	if (_handle != INVALID_HANDLE_VALUE)
	{
		::CloseHandle(_handle);
		_handle = INVALID_HANDLE_VALUE;
	}
}

이 부분으로 들어와서

파일이 닫힐 테니까 다든 건 신경 안쓰고 open 하는 부분만 만들어주면 된다.

 

 

3. Converter::ExportModelData에서 호출되는 WriteModelFile 구현하기

이제 Converter::WriteModelFile을 채워주는 게 목적이다.

일단 폴더가 없으면 만드는 거 부터 시작한다.

void Converter::WriteModelFile(wstring finalPath)
{
	auto path = filesystem::path(finalPath);

	// 폴더가 없으면 만든다.
	filesystem::create_directory(path.parent_path());

	shared_ptr<FileUtils> file = make_shared<FileUtils>();
	file->Open(finalPath, FileMode::Write);

	// Bone Data
	file->Write<uint32>(_bones.size());
	for (shared_ptr<asBone>& bone : _bones)
	{
		file->Write<int32>(bone->index);
		file->Write<string>(bone->name);
		file->Write<int32>(bone->parent);
		file->Write<Matrix>(bone->transform);
	}

	// Mesh Data
	file->Write<uint32>(_meshes.size());
	for (shared_ptr<asMesh>& meshData : _meshes)
	{
		file->Write<string>(meshData->name);
		file->Write<int32>(meshData->boneIndex);
		file->Write<string>(meshData->materialName);

		// Vertex Data
		file->Write<uint32>(meshData->vertices.size());
		file->Write(&meshData->vertices[0], sizeof(VertexType) * meshData->vertices.size());

		// Index Data
		file->Write<uint32>(meshData->indices.size());
		file->Write(&meshData->indices[0], sizeof(uint32) * meshData->indices.size());
	}
}

폴더를 만들어 주고,

FileUtils 스마트 포인터 변수를 만들고, Write 모드로 Open을 하고,

AsTypes.h의 asBone, asMesh, asMaterial struct를 보고 정보를 넣어주면 된다.

file->Write<int32>(bone->index); 에서 <int32>를 넣어준 이유는 명시적으로 헷갈리지 않게 나중에 Read 할 때도 순서를 맞춰주기 위해서다. 혹시라도 실수로 int16으로 하면 난리가 난다.

이런 걸 연습하면 서버 작업할 때 패킷 연동도 쉽게 할 수 있다.

Read 하는 쪽에서도 순서에 맞춰서 똑같이 만들어 주면 된다.

AssimpTool 프로젝트를 빌드한다.

 

4. AssimpTool 프로젝트 실행해서 .mesh 바이너리 파일 생성하기

하고 싶은 얘기는 명확하다.

void AssimpTool::Init()
{
	{
		shared_ptr<Converter> converter = make_shared<Converter>();

		// FBX -> Memory
		converter->ReadAssetFile(L"Tower/Tower.fbx");

		// Memory -> CustomData (File)
		// 1차 목표
		converter->ExportMaterialData(L"Tower/Tower");
		converter->ExportModelData(L"Tower/Tower");

		// CustomData (File) -> Memory
	}
}

FBX를 Memory 상으로 들고 있었다가

Memory를 커스텀한 데이터 파일로 밀어 넣는 부분을 실행한다고 볼 수 있다.

 

실행해 보면

Resources\Models\Tower에

이렇게 생성이 된 것이다.

이건 바이너리 파일이라 열어 보려해도 열리지 않는다.

굳이 보고 싶다면 헥스 에디터라는 걸 써서 열 수 있다.

 

코드가 이상한가 싶으면 뜯어 보면 된다.

 

void AssimpTool::Init()
{

	{
		shared_ptr<Converter> converter = make_shared<Converter>();

		// FBX -> Memory
		converter->ReadAssetFile(L"Tower/Tower.fbx");

		// Memory -> CustomData (File)
		// 1차 목표
		converter->ExportMaterialData(L"Tower/Tower");
		converter->ExportModelData(L"Tower/Tower");

		// CustomData (File) -> Memory
	}
}

 

이렇게 1차 목표의 ExportModelData까지 구현을 하였다. 

 

 

이제 3단계로 커스텀 데이터로 되어 있는 이 파일을 메모리에 로드를 해서 걔를 관리하기 시작해야 한다.

어느 시점에서 해야 하 는걸까? 툴에서 하는 게 아니라, 클라이언트 쪽에서 게임을 진행할 때 우리가 만든 포맷을 그대로 불러 읽어 들여서 다시 메모리에 들고 있어서 사용할 준비를 끝내야 한다.

asType.h의 as 버전을 사용해도 안될 건 없지만, 그게 아니면 Mesh 같은 거에 끼워 넣어서 vertexBuffer, indexBuffer를 채우고 온갖 잡동사니를 넣어주면 된다.

 

Mesh에 다 넣어 놓자니 일반적인 작업하던 메쉬랑은 결이 좀 다르다. Material도 여러 개 포함될 수 있고, 하나의 물체에 Mesh도 Material도 여러개 있을 수 있고, 파일이 복잡해진다.

총합적으로 묶어서 나중에 가면 언리얼 엔진처럼 static mesh, skeletal mesh로 구분해서 깔끔하게 만들고 정리를 하면 되겠지만 오늘 단계에서는 부수고 고치는 걸 하지 않기 위해서 그냥 별도로 리소스 파일을 파줘서 따로 관리를 한다.

 

5. Assimp::Init에서 3단계에 해당하는 "커스텀 데이터로 되어 있는 파일을 메모리에 로드해서 관리하기"를 하기 위해 Model, ModelMesh 클래스 생성하기

 

Engine/00. Engine/Resource에 Model이란 클래스를 추가한다.

작업하고 있는 뼈대가 있는 아이(Bones)랑 mesh란 정보를 들고 있고 Material 정보까지 들고 있는 많은 정보를 들고 있는 아이를 따로 파줄 거고, 나중엔 애니메이션 정보가 추가되면 애니 정보도 들고 있을 것이다.

ModelMesh 클래스도 추가한다.

Engine/Resources필터 안에 Model 필터를 만들어서 그 안에 넣는다.

Model이란 걸 만들어서 채우고 그게 화면에 그려지게 하는 게 최종 목표다.

 

ModelMesh에서는  AsType.h에서 as버전으로 관리했던 아이들을 버리고, Material은 재활용하도록 하고, 나머지 Mesh와 관련된 부분들은 수정을 해서 사용할 것이다.

 

6. ModelMesh.h 에 Model에서 사용할 struct ModelBone, ModelMesh를 정의하기

ModelMesh.h에 가서

struct ModelBone
{
	wstring name;
	int32 index;
	int32 parentIndex;
	shared_ptr<ModelBone> parent; // Cache

	Matrix transform;
	vector<shared_ptr<ModelBone>> children; // Cache
};

Bone의 기본정보는 4가지인데 2개는 Cache 해서

parent, children를 매번 찾아서 매번 구하기 뭐 하긴 하니까 한 번만 다 연결을 시켜 준 다음에 그거를 포인터 식으로 들고 있으려고 만들어 준 것이다.

연결만 해주면 빠르게 사용할 수 있는 거니 cache라고 부른다.

 

mesh도 마찬가지로 AsType.h에 들고 있는 정보를 그대로 옮겨준다. vertex, index와 관련된 부분은 Geometry라는 새로운 타입을 만들어서 관리하고 있었다.

struct ModelMesh
{
	void CreateBuffers();

	wstring name;

	// Mesh
	shared_ptr<Geometry<ModelVertexType>> geometry = make_shared<Geometry<ModelVertexType>>();
	shared_ptr<VertexBuffer> vertexBuffer;
	shared_ptr<IndexBuffer> indexBuffer;

	// Material
	wstring materialName = L"";
	shared_ptr<Material> material; // Cache

	// Bones
	int32 boneIndex;
	shared_ptr<ModelBone> bone; // Cache;
};

Struct는 그냥 public으로 만들어서 get, set을 할 필요 없게 하였다.

 

ModelVertexType을 아직 모르는데 VertexData.h에 가서

using ModelVertexType = VertexTextureNormalTangentBlendData;

이렇게 추가한다.

 

struct VertexTextureNormalTangentBlendData
{
	Vec3 position = { 0, 0, 0 };
	Vec2 uv = { 0, 0 };
	Vec3 normal = { 0, 0, 0 };
	Vec3 tangent = { 0, 0, 0 };
	Vec4 blendIndices = { 0, 0, 0, 0 }; 
	Vec4 blendWeights = { 0, 0, 0, 0 }; 
};

blendIndices, blendWeights가 있는데 애니메이션에 들어가면 필요한 것이기에 미리 VertexTextureNormalTangentBlendData로 geometry를 세팅한 것이다.

 

Vertex정보랑 Index정보를 묶어서 관리할 수 있는 아이를 Geometry라고 했다. 기하학적인 도형이다라고 표현을 한 것이다. index 버퍼랑 vertex버퍼는 따로 채워줘야 한다.

 

 

struct ModelMesh를 분석해 보면

	// Mesh
	shared_ptr<Geometry<ModelVertexType>> geometry = make_shared<Geometry<ModelVertexType>>();
	shared_ptr<VertexBuffer> vertexBuffer;
	shared_ptr<IndexBuffer> indexBuffer;

이 3 총사가 기존에 작업하던 Mesh를 묘사하던 아이였고,

// Material 같은 경우는

	// Material
	wstring materialName = L"";
	shared_ptr<Material> material; // Cache

이름으로 Material을 찾아서 캐시 해서 사용할 예정이다.

ModelMesh에서 Material을 들고 있는 이유는 단순한데,

이전 작업에서는 Mesh랑 Material이 하나씩만 있었기 때문에 MeshRenderer에서 따로따로 들고 처리했을 것이다. 

Material에 Shader가 들어가 있고, shader에 넘겨주는 인자들을 Material이 들고 있어서 세트로 들고 있게 했었다. 

하지만 지금은 어떤 Mesh가 어떤 Material로 그려야 될지가 굉장히 많이 달라질 수 있고, 그게 fbx파일에 들어가 있었다 했다. 물체가 복잡해지면 mesh가 10개 material이 5개고 어떤 애는 뭘로 그리고가 달라질 수 있기 때문에 그 부분들을 들고 있게끔 캐싱을 해주는 것이다.

 

	// Bones
	int32 boneIndex;
	shared_ptr<ModelBone> bone; // Cache;

Bone도 어떤 Bone에 연결이 되어 있는지 들고 있는데 

당장 뼈들을 이용해서 애니메이션을 막 적용시키지 않는다 하더라도 계층적인 구조로 되어 있었다. 그러기 때문에 물체마다 조금 다르긴 하지만 계층구조가 없으면 물체가 정확히 표현이 안 되는 경우가 생길 수 있다. 물론 애니메이션을 할 때도 필요하다.

 

Mesh, Material, Bone은 삼총사는 세트라고 보면 된다.

 

Mesh 같은 걸 다 채웠다고 하면, CreateBuffer라는 함수를 하나 파준다.

#include "pch.h"
#include "ModelMesh.h"

void ModelMesh::CreateBuffers()
{
	vertexBuffer = make_shared<VertexBuffer>();
	vertexBuffer->Create(geometry->GetVertices());
	indexBuffer = make_shared<IndexBuffer>();
	indexBuffer->Create(geometry->GetIndices());
}

_geometry를 채운 다음에 CreateBuffers로 vertexBuffer, indexBuffer까지 채워주고,

나머지는 알아서 연결해서 사용하세요가 된다.

 

#pragma once

struct ModelBone
{
	wstring name;
	int32 index;
	int32 parentIndex;
	shared_ptr<ModelBone> parent; // Cache

	Matrix transform;
	vector<shared_ptr<ModelBone>> children; // Cache
};

struct ModelMesh
{
	void CreateBuffers();

	wstring name;

	// Mesh
	shared_ptr<Geometry<ModelVertexType>> geometry = make_shared<Geometry<ModelVertexType>>();
	shared_ptr<VertexBuffer> vertexBuffer;
	shared_ptr<IndexBuffer> indexBuffer;

	// Material
	wstring materialName = L"";
	shared_ptr<Material> material; // Cache

	// Bones
	int32 boneIndex;
	shared_ptr<ModelBone> bone; // Cache;
};

 

AsTypes랑 조금 다르다. AsTypes는 AiMesh 같은 애들이 들고 있었던, Assimp가 들고 있었던 부분은 걸러서 필요한 부분만 들고 있었던 부분이다.

인게임에서 사용할 애들은 ModelMesh에서 보듯이 캐싱도 적용하고, 구조에 맞게끔 사용하게끔 더 정제를 해서 사용하는 거라고 할 수 있다.

 

7. xml을 읽고, ModelMesh를 사용하는 Model 클래스 구현하기 위해 ResourceManager에  헬퍼함수 GetOrAddTexture를 만들기

Model 클래스로 넘어가서 Model은 무엇을 하는 걸까?

얘기했던 모든 아이들을 여기서 들고 있어서 사용을 할 최종 결과물이라고 볼 수 있다.

 

Towel.xml을 다시 읽는 작업이다.

<?xml version="1.0" encoding="UTF-8"?>
<Materials>
    <Material>
        <Name>watchtower</Name>
        <DiffuseFile>Wood_Tower_Col.jpg</DiffuseFile>
        <SpecularFile>Wood_Tower_Col.jpg</SpecularFile>
        <NormalFile>Wood_Tower_Nor.jpg</NormalFile>
        <Ambient R="0" G="0" B="0" A="1"/>
        <Diffuse R="0.39738566" G="0.39738566" B="0.39738566" A="1"/>
        <Specular R="1" G="1" B="1" A="0"/>
        <Emissive R="0" G="0" B="0" A="1"/>
    </Material>
</Materials>

Diffuse, Specular, Normal이 들어가 있다. 이름이 있으면 이 텍스쳐를 찾아서

auto texture = RESOURCES->GetOrAddTexture(textureStr, (parentPath / textureStr).wstring());
material->SetDiffuseMap(texture);

material에 DiffuseMap을 세팅하는 부분을 여기다 넣어주고 싶다.

 

ResourceManager에 텍스쳐를 긁어오는 기능을 만들어 준다. 만약 텍스쳐가 없으면 크래시가 나면 곤란하니까 여기선 파일이 없으면 무시하고 가게 한다.

인게임에서는 텍스쳐가 없으니까 안 뜨겠지만 크래시 내기보다는 스스로 찾아서 그 부분만 텍스처를 갖고 오는 걸로 작업을 하려고 한다. 많은 메시들을 갖고 왔을 때 하나라도 누락이 되면 게임이 터지기 때문이다.

 

ResourceManager에 GetOrAddTexture를 추가한다.

	shared_ptr<Texture> GetOrAddTexture(const wstring& key, const wstring& path);

정리되면 로드랑 마찬가지로 템플릿으로 만들게 해 주면 된다. 지금은 그냥 한다.

std::shared_ptr<Texture> ResourceManager::GetOrAddTexture(const wstring& key, const wstring& path)
{
	shared_ptr<Texture> texture = Get<Texture>(key);

	if (filesystem::exists(filesystem::path(path)) == false)
		return nullptr;

	texture = Load<Texture>(key, path);

	if (texture == nullptr)
	{
		texture = make_shared<Texture>();
		texture->Load(path);
		Add(key, texture);
	}

	return texture;
}

key, path를 넣어주게 되면

파일이 있는지를 체크해서 없으면 리턴하고

Load를 해서 기존에 있으면 있는 것을 쓰고, 

없으면 texture→Load 하고 Add 해서 키값을 넣어주는 부분까지 이렇게 완료가 되었다.

 

이제 이 함수를 이용해서 Model에서 ReadMaterial을 사용할 준비가 끝났다.

 

8. Model 클래스 구현하기

1) xml과 texture 파일을 읽는 ReadMaterial  함수 구현

Model::ReadMaterial에서

auto texture = RESOURCES->GetOrAddTexture(textureStr, (parentPath / textureStr).wstring());
material->SetDiffuseMap(texture);

parentPath / textureStr는

자기 자신을 기준으로 했을 때의 경로다.

Resources\Textures\Tower에서

Tower.xml을 기준으로 Wood_Tower_Col.jpg, Wood_Tower_Nor.jpg를 나타내고 있는 것이다.

xml에 파일 이름만 있는 것이기 때문에 위치를 찾아주기 위해서 규칙상 텍스쳐 폴더에다가 parentPath에 뭔가를 넣어주기로 했으니까 경로를 찾아주기 위해 노력을 해줬다고 보면 된다.

 

ReadMaterial을 구현하면,

// xml을 다시 읽는 거 
void Model::ReadMaterial(wstring filename)
{
	wstring fullPath = _texturePath + filename + L".xml";
	auto parentPath = filesystem::path(fullPath).parent_path();

	tinyxml2::XMLDocument* document = new tinyxml2::XMLDocument();
	tinyxml2::XMLError error = document->LoadFile(Utils::ToString(fullPath).c_str());
	assert(error == tinyxml2::XML_SUCCESS);

	tinyxml2::XMLElement* root = document->FirstChildElement();
	tinyxml2::XMLElement* materialNode = root->FirstChildElement();

	while (materialNode)
	{
		shared_ptr<Material> material = make_shared<Material>();

		tinyxml2::XMLElement* node = nullptr;

		node = materialNode->FirstChildElement();
		material->SetName(Utils::ToWString(node->GetText()));

		// Diffuse Texture
		node = node->NextSiblingElement();
		if (node->GetText())
		{
			wstring textureStr = Utils::ToWString(node->GetText());
			if (textureStr.length() > 0)
			{
				auto texture = RESOURCES->GetOrAddTexture(textureStr, (parentPath / textureStr).wstring());
				material->SetDiffuseMap(texture);
			}
		}

		// Specular Texture
		node = node->NextSiblingElement();
		if (node->GetText())
		{
			wstring texture = Utils::ToWString(node->GetText());
			if (texture.length() > 0)
			{
				wstring textureStr = Utils::ToWString(node->GetText());
				if (textureStr.length() > 0)
				{
					auto texture = RESOURCES->GetOrAddTexture(textureStr, (parentPath / textureStr).wstring());
					material->SetSpecularMap(texture);
				}
			}
		}

		// Normal Texture
		node = node->NextSiblingElement();
		if (node->GetText())
		{
			wstring textureStr = Utils::ToWString(node->GetText());
			if (textureStr.length() > 0)
			{
				auto texture = RESOURCES->GetOrAddTexture(textureStr, (parentPath / textureStr).wstring());
				material->SetNormalMap(texture);
			}
		}

		// Ambient
		{
			node = node->NextSiblingElement();

			Color color;
			color.x = node->FloatAttribute("R");
			color.y = node->FloatAttribute("G");
			color.z = node->FloatAttribute("B");
			color.w = node->FloatAttribute("A");
			material->GetMaterialDesc().ambient = color;
		}

		// Diffuse
		{
			node = node->NextSiblingElement();

			Color color;
			color.x = node->FloatAttribute("R");
			color.y = node->FloatAttribute("G");
			color.z = node->FloatAttribute("B");
			color.w = node->FloatAttribute("A");
			material->GetMaterialDesc().diffuse = color;
		}

		// Specular
		{
			node = node->NextSiblingElement();

			Color color;
			color.x = node->FloatAttribute("R");
			color.y = node->FloatAttribute("G");
			color.z = node->FloatAttribute("B");
			color.w = node->FloatAttribute("A");
			material->GetMaterialDesc().specular = color;
		}

		// Emissive
		{
			node = node->NextSiblingElement();

			Color color;
			color.x = node->FloatAttribute("R");
			color.y = node->FloatAttribute("G");
			color.z = node->FloatAttribute("B");
			color.w = node->FloatAttribute("A");
			material->GetMaterialDesc().emissive = color;
		}

		_materials.push_back(material);

		// Next Material
		materialNode = materialNode->NextSiblingElement();
	}

	BindCacheInfo();
}

다른 구조를 만들겠다면 코드를 수정하면 된다.

 

2) 바이너리를 읽는 ReadModel 함수 구현

ReadModel 같은 경우는

바이너리로 만들어 줬던 .mesh 파일을 다시 거꾸로 읽는 것이다.

void Model::ReadModel(wstring filename)
{
	wstring fullPath = _modelPath + filename + L".mesh";

	shared_ptr<FileUtils> file = make_shared<FileUtils>();
	file->Open(fullPath, FileMode::Read);

	// Bones
// 순서대로 읽어야 한다.
	{
		const uint32 count = file->Read<uint32>();

		for (uint32 i = 0; i < count; i++)
		{
			shared_ptr<ModelBone> bone = make_shared<ModelBone>();
			bone->index = file->Read<int32>();
			bone->name = Utils::ToWString(file->Read<string>());
			bone->parentIndex = file->Read<int32>();
			bone->transform = file->Read<Matrix>();

			_bones.push_back(bone);
		}
	}

	// Mesh
	{
		const uint32 count = file->Read<uint32>();

		for (uint32 i = 0; i < count; i++)
		{
			shared_ptr<ModelMesh> mesh = make_shared<ModelMesh>();

			mesh->name = Utils::ToWString(file->Read<string>());
			mesh->boneIndex = file->Read<int32>();

			// Material
			mesh->materialName = Utils::ToWString(file->Read<string>());

			//VertexData
			{
				const uint32 count = file->Read<uint32>();
				vector<ModelVertexType> vertices;
				vertices.resize(count);

				void* data = vertices.data();
				file->Read(&data, sizeof(ModelVertexType) * count);
				mesh->geometry->AddVertices(vertices);
			}

			//IndexData
			{
				const uint32 count = file->Read<uint32>();

				vector<uint32> indices;
				indices.resize(count);

				void* data = indices.data();
				file->Read(&data, sizeof(uint32) * count);
				mesh->geometry->AddIndices(indices);
			}

			mesh->CreateBuffers();

			_meshes.push_back(mesh);
		}
	}

	BindCacheInfo();
}

순서대로 읽어야 한다.

 

다 만들어 본 다음에 Write 한 다음에 Read를 해서 메모리 안 깨지고 다 들어가 있는지 테스트하면 얼추 맞다고 볼 수 있다.

순서대로 Bone정보, Mesh 정보를 순회해서 읽고 VertexData, IndexData를 채워주고, 마지막에 CreateBuffers만 해서 실질적으로

void ModelMesh::CreateBuffers()
{
	vertexBuffer = make_shared<VertexBuffer>();
	vertexBuffer->Create(geometry->GetVertices());
	indexBuffer = make_shared<IndexBuffer>();
	indexBuffer->Create(geometry->GetIndices());
}

이렇게 만들어 주고,

 

ReadModel 이 부분 까지는 필수였다.

 

3) 캐시와 관련된 부분 채워주기 

 

마지막으로 해야 할 것은 ModelMesh.h에서 캐시 해 준다고 했던 Cache와 관련된 부분들을 채워주는 것이다.

 

Model.h에

	void BindCacheInfo();

이 등장하는데

얘의 역할은 지금까지 만든 모든 material이랑 model의 캐시와 관련된 부분들을 채워주려고 하는 것이다.

void Model::BindCacheInfo()
{
	// Mesh에 Material 캐싱
	for (const auto& mesh : _meshes)
	{
		// 이미 찾았으면 스킵
		if (mesh->material != nullptr)
			continue;

		mesh->material = GetMaterialByName(mesh->materialName);
	}

	// Mesh에 Bone 캐싱
	for (const auto& mesh : _meshes)
	{
		// 이미 찾았으면 스킵
		if (mesh->bone != nullptr)
			continue;

		mesh->bone = GetBoneByIndex(mesh->boneIndex);
	}

	// Bone 계층 정보 채우기
	if (_root == nullptr && _bones.size() > 0)
	{
		_root = _bones[0];

		for (const auto& bone : _bones)
		{
			if (bone->parentIndex >= 0)
			{
				bone->parent = _bones[bone->parentIndex];
				bone->parent->children.push_back(bone);
			}
			else
			{
				bone->parent = nullptr;
			}
		}
	}
}

 

material, bone도 캐시를 해야 굳이 또 찾지 않고 빠르게 접근할 수 있기 때문에 그 부분을 찾아 놓는 부분을 작업한 건데 함수를 호출하는 쪽에서 material을 먼저 할지, 모델을 먼저 할지 예측을 못하니까 Material을 읽어도, Model을 읽어도 한 번씩 바인딩 캐싱 작업을 해주도록 ReadMaterial과 ReadModel의 마지막에 각각 BindCacheInfo를 호출해 주었다. 

 

4) BindCacheInfo에서 material, mesh, bone을 찾을 때 사용하는 헬퍼 함수 구현하기

material, mesh, bone을 찾을 때 사용하는 헬퍼 함수를 정의해 준다.

uint32 GetMaterialCount() { return static_cast<uint32>(_materials.size()); }
vector<shared_ptr<Material>>& GetMaterials() { return _materials; }
shared_ptr<Material> GetMaterialByIndex(uint32 index) { return _materials[index]; }
shared_ptr<Material> GetMaterialByName(const wstring& name);

uint32 GetMeshCount() { return static_cast<uint32>(_meshes.size()); }
vector<shared_ptr<ModelMesh>>& GetMeshes() { return _meshes; }
shared_ptr<ModelMesh> GetMeshByIndex(uint32 index) { return _meshes[index]; }
shared_ptr<ModelMesh> GetMeshByName(const wstring& name);

uint32 GetBoneCount() { return static_cast<uint32>(_bones.size()); }
vector<shared_ptr<ModelBone>>& GetBones() { return _bones; }
shared_ptr<ModelBone> GetBoneByIndex(uint32 index) { return (index < 0 || index >= _bones.size() ? nullptr : _bones[index]); }
shared_ptr<ModelBone> GetBoneByName(const wstring& name);
std::shared_ptr<Material> Model::GetMaterialByName(const wstring& name)
{
	for (auto& material : _materials)
	{
		if (material->GetName() == name)
			return material;
	}

	return nullptr;
}

std::shared_ptr<ModelMesh> Model::GetMeshByName(const wstring& name)
{
	for (auto& mesh : _meshes)
	{
		if (mesh->name == name)
			return mesh;
	}

	return nullptr;
}

std::shared_ptr<ModelBone> Model::GetBoneByName(const wstring& name)
{
	for (auto& bone : _bones)
	{
		if (bone->name == name)
			return bone;
	}

	return nullptr;
}

이렇게 추가한다.

 

결과적으로 Model 클래스가

#pragma once

struct ModelBone;
struct ModelMesh;

class Model : public enable_shared_from_this<Model> // 이걸 안붙이면 this 포인터로 사용할 수 없다. 
{
public:
	Model();
	~Model();

public:
	// 핵심적인 함수이다. 이제 거꾸로 하는 거 ExportModelData에서 우리만의 포멧으로 만들어진 메쉬랑 xml 포멧으로 들고 있는 
	// 그거를 다시 여기서 로드해서 메모리에 채워주는 작업을 해줘야 한다. fbx에 있던 거를 로드하고 정지해서 저장하고 다시 그걸 로드하는 단계
	// xml로 material은 되어 있으니까 그 xml의 그 형식을 그대로 맞춰가지고 다시 거꾸로 순서 맞춰가지고 하나씩 긁어서 읽어오는 작업을 해줘야 한다.
	void ReadMaterial(wstring filename);
	void ReadModel(wstring filename);

	uint32 GetMaterialCount() { return static_cast<uint32>(_materials.size()); }
	vector<shared_ptr<Material>>& GetMaterials() { return _materials; }
	shared_ptr<Material> GetMaterialByIndex(uint32 index) { return _materials[index]; }
	shared_ptr<Material> GetMaterialByName(const wstring& name);

	uint32 GetMeshCount() { return static_cast<uint32>(_meshes.size()); }
	vector<shared_ptr<ModelMesh>>& GetMeshes() { return _meshes; }
	shared_ptr<ModelMesh> GetMeshByIndex(uint32 index) { return _meshes[index]; }
	shared_ptr<ModelMesh> GetMeshByName(const wstring& name);

	uint32 GetBoneCount() { return static_cast<uint32>(_bones.size()); }
	vector<shared_ptr<ModelBone>>& GetBones() { return _bones; }
	shared_ptr<ModelBone> GetBoneByIndex(uint32 index) { return (index < 0 || index >= _bones.size() ? nullptr : _bones[index]); }
	shared_ptr<ModelBone> GetBoneByName(const wstring& name);

private:
	void BindCacheInfo();

private:
	wstring _modelPath = L"../Resources/Models/";
	wstring _texturePath = L"../Resources/Textures/";
	// 애니메이션까지 하고 나중에 리소스매니져를 통해 하도록 만들어 줘야 한다.

private:
	shared_ptr<ModelBone> _root;
	vector<shared_ptr<Material>> _materials; // mesh를 어떻게 그려줘야 할지
	vector<shared_ptr<ModelBone>> _bones; // // mesh끼리 서로 상하 관계나 대칭구조
	vector<shared_ptr<ModelMesh>> _meshes; 

}; 
#include "pch.h"
#include "Model.h"
#include "Utils.h"
#include "FileUtils.h"
#include "tinyxml2.h"
#include <filesystem>
#include "Material.h"
#include "ModelMesh.h"

Model::Model()
{

}

Model::~Model()
{

}

// xml을 다시 읽는 거 
void Model::ReadMaterial(wstring filename)
{
	wstring fullPath = _texturePath + filename + L".xml";
	auto parentPath = filesystem::path(fullPath).parent_path();

	tinyxml2::XMLDocument* document = new tinyxml2::XMLDocument();
	tinyxml2::XMLError error = document->LoadFile(Utils::ToString(fullPath).c_str());
	assert(error == tinyxml2::XML_SUCCESS);

	tinyxml2::XMLElement* root = document->FirstChildElement();
	tinyxml2::XMLElement* materialNode = root->FirstChildElement();

	while (materialNode)
	{
		shared_ptr<Material> material = make_shared<Material>();

		tinyxml2::XMLElement* node = nullptr;

		node = materialNode->FirstChildElement();
		material->SetName(Utils::ToWString(node->GetText()));

		// Diffuse Texture
		node = node->NextSiblingElement();
		if (node->GetText())
		{
			wstring textureStr = Utils::ToWString(node->GetText());
			if (textureStr.length() > 0)
			{
				auto texture = RESOURCES->GetOrAddTexture(textureStr, (parentPath / textureStr).wstring());
				material->SetDiffuseMap(texture);
			}
		}

		// Specular Texture
		node = node->NextSiblingElement();
		if (node->GetText())
		{
			wstring texture = Utils::ToWString(node->GetText());
			if (texture.length() > 0)
			{
				wstring textureStr = Utils::ToWString(node->GetText());
				if (textureStr.length() > 0)
				{
					auto texture = RESOURCES->GetOrAddTexture(textureStr, (parentPath / textureStr).wstring());
					material->SetSpecularMap(texture);
				}
			}
		}

		// Normal Texture
		node = node->NextSiblingElement();
		if (node->GetText())
		{
			wstring textureStr = Utils::ToWString(node->GetText());
			if (textureStr.length() > 0)
			{
				auto texture = RESOURCES->GetOrAddTexture(textureStr, (parentPath / textureStr).wstring());
				material->SetNormalMap(texture);
			}
		}

		// Ambient
		{
			node = node->NextSiblingElement();

			Color color;
			color.x = node->FloatAttribute("R");
			color.y = node->FloatAttribute("G");
			color.z = node->FloatAttribute("B");
			color.w = node->FloatAttribute("A");
			material->GetMaterialDesc().ambient = color;
		}

		// Diffuse
		{
			node = node->NextSiblingElement();

			Color color;
			color.x = node->FloatAttribute("R");
			color.y = node->FloatAttribute("G");
			color.z = node->FloatAttribute("B");
			color.w = node->FloatAttribute("A");
			material->GetMaterialDesc().diffuse = color;
		}

		// Specular
		{
			node = node->NextSiblingElement();

			Color color;
			color.x = node->FloatAttribute("R");
			color.y = node->FloatAttribute("G");
			color.z = node->FloatAttribute("B");
			color.w = node->FloatAttribute("A");
			material->GetMaterialDesc().specular = color;
		}

		// Emissive
		{
			node = node->NextSiblingElement();

			Color color;
			color.x = node->FloatAttribute("R");
			color.y = node->FloatAttribute("G");
			color.z = node->FloatAttribute("B");
			color.w = node->FloatAttribute("A");
			material->GetMaterialDesc().emissive = color;
		}

		_materials.push_back(material);

		// Next Material
		materialNode = materialNode->NextSiblingElement();
	}

	BindCacheInfo();
}

void Model::ReadModel(wstring filename)
{
	wstring fullPath = _modelPath + filename + L".mesh";

	shared_ptr<FileUtils> file = make_shared<FileUtils>();
	file->Open(fullPath, FileMode::Read);

	// Bones
	{
		const uint32 count = file->Read<uint32>();

		for (uint32 i = 0; i < count; i++)
		{
			shared_ptr<ModelBone> bone = make_shared<ModelBone>();
			bone->index = file->Read<int32>();
			bone->name = Utils::ToWString(file->Read<string>());
			bone->parentIndex = file->Read<int32>();
			bone->transform = file->Read<Matrix>();

			_bones.push_back(bone);
		}
	}

	// Mesh
	{
		const uint32 count = file->Read<uint32>();

		for (uint32 i = 0; i < count; i++)
		{
			shared_ptr<ModelMesh> mesh = make_shared<ModelMesh>();

			mesh->name = Utils::ToWString(file->Read<string>());
			mesh->boneIndex = file->Read<int32>();

			// Material
			mesh->materialName = Utils::ToWString(file->Read<string>());

			//VertexData
			{
				const uint32 count = file->Read<uint32>();
				vector<ModelVertexType> vertices;
				vertices.resize(count);

				void* data = vertices.data();
				file->Read(&data, sizeof(ModelVertexType) * count);
				mesh->geometry->AddVertices(vertices);
			}

			//IndexData
			{
				const uint32 count = file->Read<uint32>();

				vector<uint32> indices;
				indices.resize(count);

				void* data = indices.data();
				file->Read(&data, sizeof(uint32) * count);
				mesh->geometry->AddIndices(indices);
			}

			mesh->CreateBuffers();

			_meshes.push_back(mesh);
		}
	}

	BindCacheInfo();
}

std::shared_ptr<Material> Model::GetMaterialByName(const wstring& name)
{
	for (auto& material : _materials)
	{
		if (material->GetName() == name)
			return material;
	}

	return nullptr;
}

std::shared_ptr<ModelMesh> Model::GetMeshByName(const wstring& name)
{
	for (auto& mesh : _meshes)
	{
		if (mesh->name == name)
			return mesh;
	}

	return nullptr;
}

std::shared_ptr<ModelBone> Model::GetBoneByName(const wstring& name)
{
	for (auto& bone : _bones)
	{
		if (bone->name == name)
			return bone;
	}

	return nullptr;
}

void Model::BindCacheInfo()
{
	// Mesh에 Material 캐싱
	for (const auto& mesh : _meshes)
	{
		// 이미 찾았으면 스킵
		if (mesh->material != nullptr)
			continue;

		mesh->material = GetMaterialByName(mesh->materialName);
	}

	// Mesh에 Bone 캐싱
	for (const auto& mesh : _meshes)
	{
		// 이미 찾았으면 스킵
		if (mesh->bone != nullptr)
			continue;

		mesh->bone = GetBoneByIndex(mesh->boneIndex);
	}

	// Bone 계층 정보 채우기
	if (_root == nullptr && _bones.size() > 0)
	{
		_root = _bones[0];

		for (const auto& bone : _bones)
		{
			if (bone->parentIndex >= 0)
			{
				bone->parent = _bones[bone->parentIndex];
				bone->parent->children.push_back(bone);
			}
			else
			{
				bone->parent = nullptr;
			}
		}
	}
}

이렇게 만들어지고

 

이 Model의 함수들이 호출되고 완료가 되었으면

private:
	shared_ptr<ModelBone> _root;
	vector<shared_ptr<Material>> _materials; // mesh를 어떻게 그려줘야 할지
	vector<shared_ptr<ModelBone>> _bones; // // mesh끼리 서로 상하 관계나 대칭구조
	vector<shared_ptr<ModelMesh>> _meshes; 

이 변수들이 필요한 내용들을 들고 있게 되는 것이다.

 

 

원래 Mesh라는 애가 이런 정보를 들고 있었다.

리소스긴 한데 필요한 잡동사니들을 들고 있는 그런 기능들이라고 보면 된다.

게임 오브젝트 별로 알아서 계층 구조에 따라 게임 오브젝트가 여러 개 만들어지게끔 만드는 게 사실 더 유니티 방식이긴 하다.

지금은 묶어서 Model이란 클래스로 만들어 놨다.

 

7. 모델을 렌더링을 하는 역할을 할 Component를 상속받은 ModelRenderer클래스를 생성하기

1) ModelRenderer 클래스를 생성하고, 생성자를 만들고, Component.h에서 ModelRenderer ComponentType 추가하기

그럼 이제 Model을 렌더링 하고 싶다고 하면,

Model 쪽에다가 바로 그 기능을 넣어 줘도 되지만

MeshRenderer라는 그림을 그려주는 컴포넌트를 만들어 줬으니까

Engine/04.Component필터에 ModelRenderer라는 클래스를 생성한다. 기본 클래스는 Compnent를 상속받게끔 한다.

 

물체 하나 띄우는 게 쉽지 않다.

 

생성자에서

ModelRenderer::ModelRenderer(shared_ptr<Shader> shader)
	: Super(ComponentType::ModelRenderer), _shader(shader)
{

}

ComponentType이 ModelRenderer인 경우는 처음이기 때문에

 

Comoponent.h에 가서

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

	End, 
};

ModelRenderer를 추가한다.

 

2) GameObject에서 GetModelRenderer 함수 만들기

그리고 GameObject.h에 가서

class ModelRenderer; 

이걸 전방 선언을 하고,

	shared_ptr<ModelRenderer> GetModelRenderer(); 

함수를 선언한다. 

 

GameObject.cpp에 가서

std::shared_ptr<ModelRenderer> GameObject::GetModelRenderer()
{
	shared_ptr<Component> component = GetFixedComponent(ComponentType::ModelRenderer);
	return static_pointer_cast<ModelRenderer>(component);
}

GetModelRenderer함수를 정의하고

#include "ModelRenderer.h"

도 추가한다.

 

3) MeshRenderer의 Update, SetModel 함수와 나머지 구현하기

#pragma once
#include "Component.h"

class Model;
class Shader;
class Material;

class ModelRenderer : public Component
{
	using Super = Component;

public:
	ModelRenderer(shared_ptr<Shader> shader);
	virtual ~ModelRenderer();

	virtual void Update() override;

	void SetModel(shared_ptr<Model> model);
	void SetPass(uint8 pass) { _pass = pass; }

private:
	shared_ptr<Shader>	_shader;
	uint8				_pass = 0; // 렌더 패스를 0번으로 할지 1번으로 할지
	shared_ptr<Model>	_model;
};

#include "pch.h"
#include "ModelRenderer.h"
#include "Material.h"
#include "ModelMesh.h"
#include "Model.h"

ModelRenderer::ModelRenderer(shared_ptr<Shader> shader)
	: Super(ComponentType::ModelRenderer), _shader(shader)
{

}

ModelRenderer::~ModelRenderer()
{

}

void ModelRenderer::Update()
{
	if (_model == nullptr)
		return;

	auto world = GetTransform()->GetWorldMatrix();
	RENDER->PushTransformData(TransformDesc{ world });

	const auto& meshes = _model->GetMeshes();
	for (auto& mesh : meshes)
	{
		if (mesh->material)
			mesh->material->Update();

		uint32 stride = mesh->vertexBuffer->GetStride();
		uint32 offset = mesh->vertexBuffer->GetOffset();

		DC->IASetVertexBuffers(0, 1, mesh->vertexBuffer->GetComPtr().GetAddressOf(), &stride, &offset);
		DC->IASetIndexBuffer(mesh->indexBuffer->GetComPtr().Get(), DXGI_FORMAT_R32_UINT, 0);

		_shader->DrawIndexed(0, _pass, mesh->indexBuffer->GetCount(), 0, 0);
	}
}

void ModelRenderer::SetModel(shared_ptr<Model> model)
{
	_model = model;

	const auto& materials = _model->GetMaterials();
	for (auto& material : materials)
	{
		material->SetShader(_shader);
	}
}

이 ModelRenderer가 하는 일은 모든 잡동사니들을 다 채워줬으면 이 모델을 렌더링을 하는 역할을 맡게 될 것이다가 핵심이니까 그 부분을 최종적으로 적어주면 된다.

 

Update함수는 그려주는 역할을 하는 것이다. ModelRenderer도 Update를 하면 그려주는 역할을 해주면 막타가 된다고 볼 수 있다. MeshRenderer와 대칭적이다. MeshRenderer에서는 Mesh가 하나가 있었는데 이제는 여러 개가 있을 수 있기 때문에 mesh를 하나씩 순회를 하면서 그 mesh에 따라 자기가 들고 있는 material에 따라 Update함수를 호출해 줄 것이다.

Material의 Update라면 관련된 쉐이더와 연관성 있는 라이팅 정보들이나 디퓨즈, 스페큘러 관련된 온갖 정보들을 넣어준다고 볼 수 있다.

 

Engine을 빌드한다.

 

8. 테스트를 위해 StaticMeshDemo클래스 만들기

새로 테스트하는 AssimpTool/Game필터에 StaticMeshDemo 클래스를 만들어 준다.

애니메이션이 안 들어간 일종의 staticMesh라고 볼 수 있기 때문에 이름을 이렇게 했다.

 

테스트할 때마다 하던 작업들을 그대로 한다.

#pragma once
#include "IExecute.h"

class StaticMeshDemo : public IExecute
{
public:
	void Init() override;
	void Update() override;
	void Render() override;

	void CreateTower();

private:
	shared_ptr<Shader> _shader;
	shared_ptr<GameObject> _obj;
	shared_ptr<GameObject> _camera;
};

 

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

void StaticMeshDemo::Init()
{
	RESOURCES->Init();
	_shader = make_shared<Shader>(L"15. ModelDemo.fx");

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

	CreateTower();

	RENDER->Init(_shader);
}

void StaticMeshDemo::Update()
{
	_camera->Update();
	RENDER->Update();

	{
		LightDesc lightDesc;
		lightDesc.ambient = Vec4(0.4f);
		lightDesc.diffuse = Vec4(1.f);
		lightDesc.specular = Vec4(0.f);
		lightDesc.direction = Vec3(1.f, 0.f, 1.f);
		RENDER->PushLightData(lightDesc);
	}

	{
		_obj->Update();
	}
}

void StaticMeshDemo::Render()
{

}

void StaticMeshDemo::CreateTower()
{
	// CustomData -> Memory
	shared_ptr<class Model> m1 = make_shared<Model>();
	m1->ReadModel(L"Tower/Tower");
	m1->ReadMaterial(L"Tower/Tower");

	_obj = make_shared<GameObject>();
	_obj->GetOrAddTransform()->SetPosition(Vec3(0, 0, 50));
	_obj->GetOrAddTransform()->SetScale(Vec3(1.0f));

	_obj->AddComponent(make_shared<ModelRenderer>(_shader));
	{
		_obj->GetModelRenderer()->SetModel(m1);
		//_obj->GetModelRenderer()->SetPass(1); // shader에서 0번으로 할지 1번으로 할지. 와이어프레임으로 할지 
	}
}

 

Client/Game/Week1 필터에 있는 코드들은 이제 필요가 없으니 삭제를 한다.

Week3 필터를 추가한다.

 

9. 15. ModelDemo.fx 쉐이더 만들고 Main에서 세팅하기

Client/Shader에도 Week3 필터를 추가한다.

탐색기에서 13. NormalMapping.fx을 복제해서 이름을 15. ModelDemo.fx로 한다. 그리고 Client/Shader/Week3 필터에 넣는다. Client의 필터에 있어도 경로에만 파일이 있으면 쓸 수 있다.

 

15. ModelDemo.fx에서

float4 PS_RED(MeshOutput input) : SV_TARGET
{
    return float4(1, 0, 0, 1); 
}

이걸 추가해서 빨갛게 나오게 한다.

 

그다음에 wireFrame 버전으로 출력하는 버전을 만들어 줄 것이다.

technique11 T0
{
    PASS_VP(P0, VS, PS)
};

현재는 PASS_VP만 기본적으로 되어 있다.

 

Shaders/Common 필터의 00. Global.fx에 가서

#define PASS_RS_VP(name, rs, vs, ps)			\
pass name						\
{							\
    SetRasterizerState(rs);				\
    SetVertexShader(CompileShader(vs_5_0, vs()));	\
    SetPixelShader(CompileShader(ps_5_0, ps()));	\
}

Rasterizer까지 설정하는 버전을 만든다.

 

15. ModelDemo.fx에서 사용할 때는

technique11 T0
{
    //PASS_VP(P0, VS, PS)
    PASS_RS_VP(P1, FillModeWireFrame, VS, PS_RED)
};

이렇게 wireFrame으로 출력되게 한다.

 

라이팅도 끄고 컬러를 diffuseMap만 쓰도록 한다.

float4 PS(MeshOutput input) : SV_TARGET
{
    // ComputeNormalMapping(input.normal, input.tangent, input.uv);
    // float4 color = ComputeLight(input.normal, input.uv, input.worldPosition);
    float4 color = DiffuseMap.Sample(LinearSampler, input.uv);

    return color;
}

 

 

전체를 보면 다음과 같이 만들어 주었다.

#include "00. Global.fx"
#include "00. Light.fx"

MeshOutput VS(VertexTextureNormalTangent input)
{
    MeshOutput output;
    output.position = mul(input.position, W);
    output.worldPosition = output.position.xyz;
    output.position = mul(output.position, VP);
    output.uv = input.uv; 
    output.normal = mul(input.normal, (float3x3)W);
    output.tangent = mul(input.tangent, (float3x3)W); 
    
    return output;     
}

float4 PS(MeshOutput input) : SV_TARGET
{
    //ComputeNormalMapping(input.normal, input.tangent, input.uv);
    //float4 color = ComputeLight(input.normal, input.uv, input.worldPosition);
    float4 color = DiffuseMap.Sample(LinearSampler, input.uv);

    return color;
}

float4 PS_RED(MeshOutput input) : SV_TARGET
{
    return float4(1, 0, 0, 1); 
}


technique11 T0
{
    //PASS_VP(P0, VS, PS)
    PASS_RS_VP(P1, FillModeWireFrame, VS, PS_RED)
};

 

 

Main.cpp에서

#include "StaticMeshDemo.h"
	desc.app = make_shared<StaticMeshDemo>(); // 실행 단위

이렇게 세팅해준다.

 

 

 

실행을 하면

이렇게 쉐이더에서 설정한 대로 빨갛고 와이어 프레임으로 나오는 걸 볼 수 있다.

 

이렇게 모델을 화면에 띄어 보았다. 

반응형

'DirectX' 카테고리의 다른 글

57. 모델_ImGUI  (0) 2024.03.01
56. 모델_계층 구조  (0) 2024.02.29
54. 모델_Bone, Mesh 로딩  (0) 2024.02.23
53. 모델_Material 로딩  (0) 2024.02.21
52. 모델_Assimp  (0) 2024.02.18

댓글