https://www.acmicpc.net/problem/2869

 

 

원래는 반복문을 통해 얼마나 걸리는지 계산하려고 했으나 시간제한을 보면 0.25초이고 최대가 10억이라 무조건 시간초과

가 뜬다 그렇기 때문에 일반화된 식을 통해 기간을 계산해야한다. 

이때 먼저 낮에 A미터를 올라가고 밤에 B미터를 미끄러지기 때문에 일단 낮 밤을 합쳤을 때는 V-A 만큼을 가야하고 그 다음에는 A만큼만 가면 되기때문에 결국에는 V-A / A-B 를 계산한다음 낮에 한번 더 가야하니 1을 더해주면 답이 된다.

 

정답코드

#include "iostream"
#include "cmath"

using namespace std;

int main()
{
	int a, b, v;

	cin >> a >> b >> v;
	
	int days = ceil((double)(v - a) / (a - b)) + 1;

	cout << days;
	return 0;
}

https://www.acmicpc.net/problem/2941

단어가 몇개의 크로아티아 알파벳으로 이루어져있는 지 확인해야하는 문제이다. 

이때 주어진 입력은 변경된 알파벳이기 때문에 받아준 단어를 순회하면서 변경된 문자라면 하나의 크로아티아 알파벳

으로 인식하여 횟수를 추가해주고 아니라면 그냥 한글자 단위만큼만 횟수를 추가해줘서 전체 횟수를 반환해주면 된다.

 

정답코드

#include "iostream"
#include "string"
#include "vector"

using namespace std;

int countCro(string s)
{
	// 크로아티아 알파벳 목록 정의
	vector<string> croatianAlphabets = { "c=", "c-", "dz=", "d-", "lj", "nj", "s=", "z=" };

	int cnt = 0;

	for (int i = 0; i < s.length();)
	{
		bool matched = false;

		for (string alpha : croatianAlphabets)
		{
			if (s.substr(i, alpha.length()) == alpha)
			{
				cnt++;
				i+=alpha.length();
				matched=true;
				break;
			}
		}

		if (!matched)
		{
			i++;
			cnt++;
		}
	}

	return cnt;
}

int main()
{
	string s;

	cin >> s;

	int result = countCro(s);
	cout << result << endl;
}

https://www.acmicpc.net/problem/25206

 

주어진 학점과 등급을 통해 전공 평점을 계산하면 되는 것이다. 

나는 이때 vector와 pair 자료형을 통해 모든 학점과 등급을 저장하고 이를 순회하면서 전공 평점을 계산해줬다 이때 

고정소수점방식으로 6자리까지만 출력되도록 했다.

 

정답코드

#include "iostream"
#include "string"
#include "vector"
#include "utility"

using namespace std;

vector<pair<double, string>> scores;

// 학점 계산 함수
double ch(string s)
{
    if (s == "A+")
        return 4.5;
    else if (s == "A0")
        return 4.0;
    else if (s == "B+")
        return 3.5;
    else if (s == "B0")
        return 3.0;
    else if (s == "C+")
        return 2.5;
    else if (s == "C0")
        return 2.0;
    else if (s == "D+")
        return 1.5;
    else if (s == "D0")
        return 1.0;
    else if (s == "F")
        return 0.0;
    else
        return 0.0;  // Default case
}

int main()
{
    string course_name, grade;
    double credit;
    for (int i = 0; i < 20; i++)
    {
        cin >> course_name >> credit >> grade;
        scores.push_back(make_pair(credit, grade));  // 학점과 등급을 벡터에 저장
    }

    double total_score = 0.0;
    double total_credits = 0.0;

    for (int i = 0; i < scores.size(); i++) {
        if (scores[i].second == "P") continue;  // P 등급인 과목은 계산에서 제외
        total_score += (scores[i].first * ch(scores[i].second));  // 학점 * 과목평점
        total_credits += scores[i].first;  // 총 학점
    }

    // 전공평점 계산
    double gpa = total_score / total_credits;
    //cout << fixed; // 고정 소수점 형식
    //cout.precision(6); // 소수점 아래 6자리까지 출력
    cout << gpa << endl;

    return 0;
}



1. 텍스처

오늘은 사각형뿐만 아니라 큐브,구,그리드를 텍스처있는 형태로 만들어 볼 것이다.

이때 uv 좌표를 사용하게 될 것인데 uv 좌표는 전에 말했던 것 처럼 결국엔 텍스처의 비율이라는 것이다. 좌표에 uv 시작점 그리고 비율을 넘겨주면 텍스처가 따라서 맵핑되는 것이다.

 

먼저 uv좌표를 받고 텍스처가 샘플링된 uv좌표로 들어갈 수 있도록 fx 코드를 만들어주자

World.fx

matrix World;
matrix View;
matrix Projection;
Texture2D Texture0;

struct VertexInput
{
	float4 position : POSITION;
    float2 uv : TEXCOORD;
};

struct VertexOutput
{
	float4 position : SV_POSITION;
    float2 uv : TEXCOORD;
};

VertexOutput VS(VertexInput input)
{
	VertexOutput output;
    output.position = mul(input.position,World);
    output.position = mul(output.position, View);
    output.position = mul(output.position, Projection);
	

    output.uv = input.uv;

	return output;
}

SamplerState Sampler0;

float4 PS(VertexOutput input) : SV_TARGET
{
	//텍스처 -> 샘플링된 좌표 
    return Texture0.Sample(Sampler0, input.uv);
}

//와이어프레임모드
RasterizerState FillModeWireFrame
{
    FillMode = Wireframe;
};

technique11 T0
{
	//하나의 통로
	pass P0
	{
		SetVertexShader(CompileShader(vs_5_0, VS()));
		SetPixelShader(CompileShader(ps_5_0, PS()));
	}

	pass P1
	{
        SetRasterizerState(FillModeWireFrame); 
		SetVertexShader(CompileShader(vs_5_0, VS()));
		SetPixelShader(CompileShader(ps_5_0, PS()));
	}
};

 

그리고 GeometryHelper에서 사각형이지만 Color를 받는 버전이 아닌 VertexTextureData를 사용하는 버전을 새로 만들어주자

GeometryHelper.cpp

void GeometryHelper::CreateQuad(shared_ptr<Geometry<VertexTextureData>> geometry)
{
	vector<VertexTextureData> vtx;

	vtx.resize(4);


	vtx[0].position = Vec3(-0.5f, -0.5f, 0.f);
	vtx[0].uv = Vec2(0.f, 1.f);
	vtx[1].position = Vec3(-0.5f, 0.5f, 0.f);
	vtx[1].uv = Vec2(0.f, 0.f);
	vtx[2].position = Vec3(0.5f, -0.5f, 0.f);
	vtx[2].uv = Vec2(1.f, 1.f);
	vtx[3].position = Vec3(0.5f, 0.5f, 0.f);
	vtx[3].uv = Vec2(1.f, 0.f);
	geometry->SetVertices(vtx);

	//1 3	보통 시계방향
	//0 2
	vector<uint32> idx = { 0,1,2,2,1,3 };
	geometry->SetIndices(idx);
}

 

그리고 새로운 Demo를 만들어 줄 것인데 이전과 코드가 같지만 Texture 변수를 저장할 스마트 포인터변수와 리소스매니저를 통해 텍스처를 경로상에서 로드하고이를 shader 클래스의 GetSRV를 통해 ShaderResourceView로 우리가 지정해준 shader fx파일의 Texture0과 연결시켜주면 된다. 

TextureDemo.h

#pragma once

#include "IExecute.h"
#include "Geometry.h"

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


	shared_ptr<Shader> _shader;
	
	// Object
	shared_ptr<Geometry<VertexTextureData>> _geometry;
	shared_ptr<VertexBuffer> _vertexBuffer;
	shared_ptr<IndexBuffer> _indexBuffer;
	Matrix _world=Matrix::Identity;

	//Camera
	shared_ptr<GameObject> _camera;

	shared_ptr<Texture> _texture;
};

 

TextureDemo.cpp

#include "pch.h"
#include "05. TextureDemo.h"
#include "GeometryHelper.h"
#include "Camera.h"
#include "CameraScript.h"

void TextureDemo::Init()
{
	_shader = make_shared<Shader>(L"04. World.fx");

	//Object
	_geometry = make_shared<Geometry<VertexTextureData>>();
	GeometryHelper::CreateQuad(_geometry);
	//고속복사
	_vertexBuffer = make_shared<VertexBuffer>();
	_vertexBuffer->Create(_geometry->GetVertices());
	_indexBuffer = make_shared<IndexBuffer>();
	_indexBuffer->Create(_geometry->GetIndices());

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

	//Texture
	_texture = RESOURCES->Load<Texture>(L"Veigar", L"..\\Resources\\Textures\\veigar.jpg");
}

void TextureDemo::Update()
{
	_camera->Update();
}

void TextureDemo::Render()
{
	_shader->GetMatrix("World")->SetMatrix((float*)&_world);
	_shader->GetMatrix("View")->SetMatrix((float*)&Camera::S_MatView);
	_shader->GetMatrix("Projection")->SetMatrix((float*)&Camera::S_MatProjection);
	_shader->GetSRV("Texture0")->SetResource(_texture->GetComPtr().Get());

	uint32 stride = _vertexBuffer->GetStride();
	uint32 offset = _vertexBuffer->GetOffset();

	DC->IASetVertexBuffers(0, 1, _vertexBuffer->GetComPtr().GetAddressOf(), &stride, &offset);
	DC->IASetIndexBuffer(_indexBuffer->GetComPtr().Get(), DXGI_FORMAT_R32_UINT, 0);
	
	//_buffer->GetCount()
	_shader->DrawIndexed(0, 0, _indexBuffer->GetCount(), 0, 0);
}

 

이렇게 해주면 베이가가 나타난다

 

2.Geometry

이제 큐브, 구, 그리드 도형도 만들어 줄 것이다.

1. 큐브

큐브를 만들때는 정점만으로는 최소 8개가 필요하지만 uv매핑을 하려면 각면 마다 4개씩 24개가 있어야 한다. 

그에 따른 인덱스도 지정해주어야한다. 인덱스는 한면마다 6개씩 총 36개가 있어야한다.

GeometryHelper.cpp

void GeometryHelper::CreateCube(shared_ptr<Geometry<VertexTextureData>> geometry)
{
	//x y z
	float w2 = 0.5f;
	float h2 = 0.5f;
	float d2 = 0.5f;

	vector<VertexTextureData> vtx(24);

	// 앞면
	vtx[0] = VertexTextureData{ Vec3(-w2, -h2, -d2), Vec2(0.0f, 1.0f) };
	vtx[1] = VertexTextureData{ Vec3(-w2, +h2, -d2), Vec2(0.0f, 0.0f) };
	vtx[2] = VertexTextureData{ Vec3(+w2, +h2, -d2), Vec2(1.0f, 0.0f) };
	vtx[3] = VertexTextureData{ Vec3(+w2, -h2, -d2), Vec2(1.0f, 1.0f) };
	// 뒷면
	vtx[4] = VertexTextureData{ Vec3(-w2, -h2, +d2), Vec2(1.0f, 1.0f) };
	vtx[5] = VertexTextureData{ Vec3(+w2, -h2, +d2), Vec2(0.0f, 1.0f) };
	vtx[6] = VertexTextureData{ Vec3(+w2, +h2, +d2), Vec2(0.0f, 0.0f) };
	vtx[7] = VertexTextureData{ Vec3(-w2, +h2, +d2), Vec2(1.0f, 0.0f) };
	// 윗면
	vtx[8] = VertexTextureData{ Vec3(-w2, +h2, -d2), Vec2(0.0f, 1.0f) };
	vtx[9] = VertexTextureData{ Vec3(-w2, +h2, +d2), Vec2(0.0f, 0.0f) };
	vtx[10] = VertexTextureData{ Vec3(+w2, +h2, +d2), Vec2(1.0f, 0.0f) };
	vtx[11] = VertexTextureData{ Vec3(+w2, +h2, -d2), Vec2(1.0f, 1.0f) };
	// 아랫면
	vtx[12] = VertexTextureData{ Vec3(-w2, -h2, -d2), Vec2(1.0f, 1.0f) };
	vtx[13] = VertexTextureData{ Vec3(+w2, -h2, -d2), Vec2(0.0f, 1.0f) };
	vtx[14] = VertexTextureData{ Vec3(+w2, -h2, +d2), Vec2(0.0f, 0.0f) };
	vtx[15] = VertexTextureData{ Vec3(-w2, -h2, +d2), Vec2(1.0f, 0.0f) };
	// 왼쪽면
	vtx[16] = VertexTextureData{ Vec3(-w2, -h2, +d2), Vec2(0.0f, 1.0f) };
	vtx[17] = VertexTextureData{ Vec3(-w2, +h2, +d2), Vec2(0.0f, 0.0f) };
	vtx[18] = VertexTextureData{ Vec3(-w2, +h2, -d2), Vec2(1.0f, 0.0f) };
	vtx[19] = VertexTextureData{ Vec3(-w2, -h2, -d2), Vec2(1.0f, 1.0f) };
	// 오른쪽면
	vtx[20] = VertexTextureData{ Vec3(+w2, -h2, -d2), Vec2(0.0f, 1.0f) };
	vtx[21] = VertexTextureData{ Vec3(+w2, +h2, -d2), Vec2(0.0f, 0.0f) };
	vtx[22] = VertexTextureData{ Vec3(+w2, +h2, +d2), Vec2(1.0f, 0.0f) };
	vtx[23] = VertexTextureData{ Vec3(+w2, -h2, +d2), Vec2(1.0f, 1.0f) };

	geometry->SetVertices(vtx);

	vector<uint32> idx(36);

	// 앞면
	idx[0] = 0; idx[1] = 1; idx[2] = 2;
	idx[3] = 0; idx[4] = 2; idx[5] = 3;
	// 뒷면
	idx[6] = 4; idx[7] = 5; idx[8] = 6;
	idx[9] = 4; idx[10] = 6; idx[11] = 7;
	// 윗면
	idx[12] = 8; idx[13] = 9; idx[14] = 10;
	idx[15] = 8; idx[16] = 10; idx[17] = 11;
	// 아랫면
	idx[18] = 12; idx[19] = 13; idx[20] = 14;
	idx[21] = 12; idx[22] = 14; idx[23] = 15;
	// 왼쪽면
	idx[24] = 16; idx[25] = 17; idx[26] = 18;
	idx[27] = 16; idx[28] = 18; idx[29] = 19;
	// 오른쪽면
	idx[30] = 20; idx[31] = 21; idx[32] = 22;
	idx[33] = 20; idx[34] = 22; idx[35] = 23;

	geometry->SetIndices(idx);
}

 

이렇게 해주고 TextureDemo에서 GeometryHelper 내장함수를 부르는 것을 CreateCube로 바꿔주면 텍스처가 적용된 

큐브가 나오게 된다.

 

 

2.구

구를 만들 때는 전체 구를 만든 다음 위 아래 끝점에서 도넛모양으로 여러개로 자르고 그 도넛을 여러개로 잘라서 만들고 위 아래를 삼각형으로 연속적으로 이어주면 된다.

설계 그림

GeometryHelper.cpp

void GeometryHelper::CreateSphere(shared_ptr<Geometry<VertexTextureData>> geometry)
{
	//옵션, 인자로 받아도되는 것
	float radius = 0.5f; // 구의 반지름
	uint32 stackCount = 20; // 가로 분할
	uint32 sliceCount = 20; // 세로 분할

	vector<VertexTextureData> vtx;

	VertexTextureData v;

	// 북극
	//제일 위가 0,0
	v.position = Vec3(0.0f, radius, 0.0f);
	v.uv = Vec2(0.5f, 0.0f);
	vtx.push_back(v);

	//가로 분할
	float stackAngle = XM_PI / stackCount;
	//도넛 분할
	float sliceAngle = XM_2PI / sliceCount;

	float deltaU = 1.f / static_cast<float>(sliceCount);
	float deltaV = 1.f / static_cast<float>(stackCount);

	// 고리마다 돌면서 정점을 계산한다 (북극/남극 단일점은 고리가 X)
	for (uint32 y = 1; y <= stackCount - 1; ++y)
	{
		float phi = y * stackAngle;

		// 고리에 위치한 정점
		for (uint32 x = 0; x <= sliceCount; ++x)
		{
			float theta = x * sliceAngle;

			v.position.x = radius * sinf(phi) * cosf(theta);
			v.position.y = radius * cosf(phi);
			v.position.z = radius * sinf(phi) * sinf(theta);

			v.uv = Vec2(deltaU * x, deltaV * y);

			vtx.push_back(v);
		}
	}

	// 남극
	v.position = Vec3(0.0f, -radius, 0.0f);
	v.uv = Vec2(0.5f, 1.0f);
	vtx.push_back(v);

	geometry->SetVertices(vtx);

	vector<uint32> idx(36);

	// 북극 인덱스
	for (uint32 i = 0; i <= sliceCount; ++i)
	{
		//  [0]
		//   |  \
		//  [i+1]-[i+2]
		idx.push_back(0);
		idx.push_back(i + 2);
		idx.push_back(i + 1);
	}

	// 몸통 인덱스
	//도넛 - 도넛 사이 삼각형
	uint32 ringVertexCount = sliceCount + 1;
	for (uint32 y = 0; y < stackCount - 2; ++y)
	{
		for (uint32 x = 0; x < sliceCount; ++x)
		{
			//  [y, x]-[y, x+1]
			//  |		/
			//  [y+1, x]
			idx.push_back(1 + (y)*ringVertexCount + (x));
			idx.push_back(1 + (y)*ringVertexCount + (x + 1));
			idx.push_back(1 + (y + 1) * ringVertexCount + (x));
			//		 [y, x+1]
			//		 /	  |
			//  [y+1, x]-[y+1, x+1]
			idx.push_back(1 + (y + 1) * ringVertexCount + (x));
			idx.push_back(1 + (y)*ringVertexCount + (x + 1));
			idx.push_back(1 + (y + 1) * ringVertexCount + (x + 1));
		}
	}

	// 남극 인덱스
	uint32 bottomIndex = static_cast<uint32>(vtx.size()) - 1;
	uint32 lastRingStartIndex = bottomIndex - ringVertexCount;
	for (uint32 i = 0; i < sliceCount; ++i)
	{
		//  [last+i]-[last+i+1]
		//  |      /
		//  [bottom]
		idx.push_back(bottomIndex);
		idx.push_back(lastRingStartIndex + i);
		idx.push_back(lastRingStartIndex + i + 1);
	}

	geometry->SetIndices(idx);
}

 

이렇게 해주고 호출 부분을 CreateSphere로 바꿔주면 구가 생성된다. 2번째 그림은 와이어프레임 모드가 적용된 버전

으로 아까 말한 원리가 더 잘 이해되게 생성되어있다.

 

3.그리드

 

그리드는 격자모양의 판으로 나중에 지형을 생성할 때 사용된다. 이 그리드를 만들려면 각 선 사이를 정점으로 나눠주면 되는데 이때 정점의 수는 지정해줄 격의 수보다 하나 많으면 된다. 그럼 이 그리드를 만들기 위해서는 x y의 사이즈를 지정해주어야한다. 

GeometryHelper.cpp

void GeometryHelper::CreateGrid(shared_ptr<Geometry<VertexTextureData>> geometry, int32 sizeX, int32 sizeY)
{
	vector<VertexTextureData> vtx;

	for (int32 z = 0; z < sizeZ + 1; z++)
	{
		for (int32 x = 0; x < sizeX + 1; x++)
		{
			VertexTextureData v;
			//각 정점
			v.position = Vec3(static_cast<float>(x), 0, static_cast<float>(z));
			v.uv = Vec2(static_cast<float>(x), static_cast<float>(z));

			vtx.push_back(v);
		}
	}

	geometry->SetVertices(vtx);

	vector<uint32> idx;

	for (int32 z = 0; z < sizeZ; z++)
	{
		for (int32 x = 0; x < sizeX; x++)
		{
			//  [0]
			//   |	\
			//  [2] - [1]
			idx.push_back((sizeX + 1) * (z + 1) + (x));
			idx.push_back((sizeX + 1) * (z)+(x + 1));
			idx.push_back((sizeX + 1) * (z)+(x));
			//  [1] - [2]
			//   	\  |
			//		  [0]
			idx.push_back((sizeX + 1) * (z)+(x + 1));
			idx.push_back((sizeX + 1) * (z + 1) + (x));
			idx.push_back((sizeX + 1) * (z + 1) + (x + 1));
		}
	}

	geometry->SetIndices(idx);
}

 

TextureDemo에서 Geometry->CreateGrid를 해주게 되면 그리드가 생성된다. 이때 텍스처가 맵핑 제대로 되지 않는 이유는 우리가 uv좌표가 1이 넘어가는 경우 어떻게 해줄지를 아직 구현하지 않았기 때문이다.

 

 

3.Sampling

이번에는 텍스처를 어떻게 샘플링해줄 것인지 정해줄 것이다. 이때 사용되는 것은 필터와 Address인데 

필터는 확대 / 축소 시 중간값을 어떻게 처리해주는지에 관한 정보이고 

Address는 uv값이 1보다 컸을 때, 나머지 부분을 어떻게 처리해주는지에 관한 정보이다.

 

일단 fx 파일에서 Wrap, Mirror, Clamp, Border 4가지 모드를 만들어주고 테스트 해보자 이때 모드의 적용을 Texture의 샘플

을 설정하는 PS 함수 단계에서 실행하게 해주자

Sampler.fx

matrix World;
matrix View;
matrix Projection;
Texture2D Texture0;
uint Address;


struct VertexInput
{
	float4 position : POSITION;
    float2 uv : TEXCOORD;
};

struct VertexOutput
{
	float4 position : SV_POSITION;
    float2 uv : TEXCOORD;
};

VertexOutput VS(VertexInput input)
{
	VertexOutput output;
    output.position = mul(input.position,World);
    output.position = mul(output.position, View);
    output.position = mul(output.position, Projection);
	

    output.uv = input.uv;

	return output;
}

//Filter - 확대/ 축소 시 중간값 처리방식
// Address - uv가 1보다 컸을 때, 나머지 부분 처리방식
SamplerState Sampler0;


SamplerState SamplerAddressWrap
{
    AddressU = Wrap;
    AddressV = Wrap;
};

SamplerState SamplerAddressMirror
{
    AddressU = Mirror;
    AddressV = Mirror;
};

SamplerState SamplerAddressClamp
{
    AddressU = Clamp;
    AddressV = Clamp;
};

SamplerState SamplerAddressBorder
{
    AddressU = Border;
    AddressV = Border;
    BorderColor = float4(1, 0, 0, 1);
};

float4 PS(VertexOutput input) : SV_TARGET
{
	if(Address==0)
        return Texture0.Sample(SamplerAddressWrap, input.uv);
    
    if (Address == 1)
        return Texture0.Sample(SamplerAddressMirror, input.uv);
    
    if (Address == 2)
        return Texture0.Sample(SamplerAddressClamp, input.uv);
    
    if (Address == 3)
        return Texture0.Sample(SamplerAddressBorder, input.uv);
	//텍스처 -> 샘플링된 좌표 
    return Texture0.Sample(Sampler0, input.uv);
}

technique11 T0
{
	//하나의 통로
	pass P0
	{
		SetVertexShader(CompileShader(vs_5_0, VS()));
		SetPixelShader(CompileShader(ps_5_0, PS()));
	}
};

 

이렇게 해준다음 Address의 값을 구조체를 선언하고 이 값에 따라 바뀔 수 있도록 해주자

SamplerDemo.cpp

void SamplerDemo::Render()
{
	_shader->GetMatrix("World")->SetMatrix((float*)&_world);
	_shader->GetMatrix("View")->SetMatrix((float*)&Camera::S_MatView);
	_shader->GetMatrix("Projection")->SetMatrix((float*)&Camera::S_MatProjection);
	_shader->GetSRV("Texture0")->SetResource(_texture->GetComPtr().Get());

	enum ADDRESS_VALUE
	{
		ADDRESS_WRAP=0,
		ADDRESS_MIRROR=1,
		ADDRESS_CLAMP=2,
		ADDRESS_BORDER=3,
	};

	_shader->GetScalar("Address")->SetInt(ADDRESS_WRAP);

	uint32 stride = _vertexBuffer->GetStride();
	uint32 offset = _vertexBuffer->GetOffset();

	DC->IASetVertexBuffers(0, 1, _vertexBuffer->GetComPtr().GetAddressOf(), &stride, &offset);
	DC->IASetIndexBuffer(_indexBuffer->GetComPtr().Get(), DXGI_FORMAT_R32_UINT, 0);
	
	//_buffer->GetCount()
	_shader->DrawIndexed(0, 0, _indexBuffer->GetCount(), 0, 0);
}

 

1. Wrap - 모든 텍스처가 동일

 

2.Mirror - 각 이미지들이 거울을 보는 것처럼 대칭 형태

 

3.Clamp - 기본

4.Border - 원래 텍스처를 제외하고는 지정한 색상으로 유지

 

https://www.acmicpc.net/problem/1316

 

 

그룹단어가 몇개인지 찾는 문제이다. 같은 문자가 연속해서 나오다가 끊기고 다시 나오면 그룹문자가 아니기 때문에 map을 통해 갯수를 세고 이게 전에 나왔던 개수이면 false를 반환 만약 순회가 끝나면 그룹문자이기 때문에 true를 반환해주자.

 

정답코드

#include "iostream"
#include "string"
#include "map"
using namespace std;

bool isGroup(string s)
{
	map<char, int> smap;
	char prechar = s[0];
	smap[prechar]++;
	for (int i = 1; i <s.length(); i++) \
	{
		if (s[i-1] != s[i] && smap[s[i]] > 0) return false;
		else smap[s[i]]++;
	}
	return true;
}
int main()
{
	int n, cnt = 0;
	string s;
	
	cin >> n;

	for (int i = 0; i < n; i++)
	{
		cin >> s;
		if (isGroup(s)) cnt++;
	}

	cout << cnt;
	return 0;
}

https://www.acmicpc.net/problem/1157

 

가장 많이 사용된 알파벳을 알아내면 되는 문제이다. 

이 문제는 간단하게 map자료형을 사용하여 각 알파벳의 사용횟수를 세고 그 map을 순회하며 만약 최대값이 같은 알파벳이 있다면 ? 를 반환하게 해주면 된다.

 

정답코드

#include "iostream"
#include "string"
#include "map"

using namespace std;

int main()
{
	string s;
	map<char, int> sm;
	cin >> s;

	for (int i = 0; i < s.length(); i++)
	{
		if (islower(s[i])) sm[toupper(s[i])]++;
		else sm[s[i]]++;
	}

	int maxcnt = 0;
	char mosts = '?';

	for (auto& pair : sm)
	{
		if (pair.second > maxcnt) 
		{
			maxcnt = pair.second;
			mosts = pair.first;
		}
		else if (pair.second == maxcnt) {
			mosts = '?';
		}
	}

	cout << mosts << endl;
}

 

 



오늘은 3D에 들어가기에 앞서 기존에 했던 것을 복습해보자.

 

1.사각형그리기

먼저 사각형을 그려볼 것이다. 사각형은 삼각형을 그렸던 코드와 쉐이더 fx 파일을 바탕으로 구현해보자.

일단 사각형을 만들 때 삼각형 2개를 결합해서 사용할 수도 있고 인덱스버퍼를 활용하여 순서를 맞춰줘서 사각형을

완성하는 방식도 있다. 

지금은 이전에 했던 방식처럼 4개의 점과 인덱스를 통해 사각형을 만들어보자 

GeometryHelper 클래스를 만들어서 사각형의 정점정보와 인덱스 정보를 생성해주는 함수를 만들어주자 

어떤방식으로 사각형을 만들어줄지 VertexData에서 구조체를 선택해줘야하는데

지금은 정점과 Color를 가지고 있는 VertexColor 구조체를 통해 사각형을 만들어주자

GeometryHelper

#pragma once
#include "Geometry.h"
#include "VertexData.h"

class GeometryHelper
{
public:
	static void CreateQuad(shared_ptr<Geometry<VertexColorData>> geometry, Color color);
};

 

GeometryHelper

#include "pch.h"
#include "GeometryHelper.h"

void GeometryHelper::CreateQuad(shared_ptr<Geometry<VertexColorData>> geometry, Color color)
{
	vector<VertexColorData> vtx;
	//인덱스버퍼포함
	vtx.resize(4);

	
	vtx[0].position = Vec3(-0.5f, -0.5f, 0.f);
	vtx[0].color = color;
	vtx[1].position = Vec3(-0.5f, 0.5f, 0.f);
	vtx[1].color = color;
	vtx[2].position = Vec3(0.5f, -0.5f, 0.f);
	vtx[2].color = color;
	vtx[3].position = Vec3(0.5f, 0.5f, 0.f);
	vtx[3].color = color;
	geometry->SetVertices(vtx);

	//1 3	보통 시계방향
	//0 2
	vector<uint32> idx = { 0,1,2,2,1,3 };
	geometry->SetIndices(idx);
}

 

만들어준 GeometryHelper를 QuadDemo 클래스에서 호출해서 사각형을 그려줄 수 있도록하자

이때 geometry가 정점의 위치와 색깔을 가진 VertexColorData 구조체를 통해 선언해주고

정점버퍼와 인덱스버퍼를 만들어주고 복사 및 설정을 해주어야한다.

그리고 VertexColorData에 맞게 쉐이더도 새로 만들어주자 

Quad.fx

struct VertexInput
{
	float4 position : POSITION;
    float4 color : COLOR;
};

struct VertexOutput
{
	float4 position : SV_POSITION;
    float4 color : COLOR;
};

VertexOutput VS(VertexInput input)
{
	VertexOutput output;
	output.position = input.position;
    output.color = input.color;

	return output;
}

float4 PS(VertexOutput input) : SV_TARGET
{
    return input.color;
}

//와이어프레임모드
RasterizerState FillModeWireFrame
{
    FillMode = Wireframe;
};

technique11 T0
{
	//하나의 통로
	pass P0
	{
		SetVertexShader(CompileShader(vs_5_0, VS()));
		SetPixelShader(CompileShader(ps_5_0, PS()));
	}

	pass P1
	{
        SetRasterizerState(FillModeWireFrame); 
		SetVertexShader(CompileShader(vs_5_0, VS()));
		SetPixelShader(CompileShader(ps_5_0, PS()));
	}
};

QuadDemo.h

#pragma once
#include "IExecute.h"
#include "Geometry.h"

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


	shared_ptr<Shader> _shader;
	
	shared_ptr<Geometry<VertexColorData>> _geometry;

	shared_ptr<VertexBuffer> _vertexBuffer;
	shared_ptr<IndexBuffer> _indexBuffer;
};

QuadDemo.cpp

#include "pch.h"
#include "02. QuadDemo.h"
#include "GeometryHelper.h"

void QuadDemo::Init()
{
	_shader = make_shared<Shader>(L"02. Quad.fx");

	_geometry = make_shared<Geometry<VertexColorData>>();
	GeometryHelper::CreateQuad(_geometry, Color(1.f, 0.f, 0.f, 1.f));

	//고속복사
	_vertexBuffer = make_shared<VertexBuffer>();
	_vertexBuffer->Create(_geometry->GetVertices());

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

void QuadDemo::Update()
{

}

void QuadDemo::Render()
{
	uint32 stride = _vertexBuffer->GetStride();
	uint32 offset = _vertexBuffer->GetOffset();

	DC->IASetVertexBuffers(0, 1, _vertexBuffer->GetComPtr().GetAddressOf(), &stride, &offset);
	DC->IASetIndexBuffer(_indexBuffer->GetComPtr().Get(), DXGI_FORMAT_R32_UINT, 0);
	
	//_buffer->GetCount()
	_shader->DrawIndexed(0, 0, _indexBuffer->GetCount(), 0, 0);
}

 

마지막으로 Main 클래스에서 QuadDemo의 단계가 실행되도록 코드를 수정해주자

Main.cpp

#include "pch.h"
#include "Main.h"
#include "Engine/Game.h"
#include "01. TriangleDemo.h"
#include "02. QuadDemo.h"

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
	GameDesc desc;
	desc.appName = L"GameCoding";
	desc.hInstance = hInstance;
	desc.vsync = false;
	desc.hWnd = NULL;
	desc.width = 800;
	desc.height = 600;
	desc.clearColor = Color(0.5f, 0.5f, 0.5f, 0.5f);
	//실행단위
	desc.app = make_shared<QuadDemo>();

	GAME->Run(desc);

	return 0;
}

 

이렇게 해주면 사각형이 나오게 된다.

 

이때 Draw 함수에서 두번째 매개변수를 1으로 바꿔주면 1번 pass가 실행되며 사각형의 와이어프레임 즉 선만 보이게 된다.

 

2.Constant Buffer

이제 Constant Buffer을 통해 움직이는 사각형을 만들어보자 

이때 shader를 받아주는 fx에서 Matrix를 선언해주고 이를 곱해주는 방식으로 해주면 된다.

그리고 Update를 통해 받는 키에 따라 delta time과 스피드를 통해 움직이도록 해주고 이를 translation matrix를 통해

World 행렬에 적용해주고 이를 shader 변수의 GetMatrix로 shader쪽의 Matrix를 가져오고 이 Matrix를 Set해주면 된다.

ConstBuffer.fx

matrix World;
matrix View;
matrix Projection;

struct VertexInput
{
	float4 position : POSITION;
    float4 color : COLOR;
};

struct VertexOutput
{
	float4 position : SV_POSITION;
    float4 color : COLOR;
};

VertexOutput VS(VertexInput input)
{
	VertexOutput output;
    output.position = mul(input.position,World);
    output.position = mul(output.position, View);
    output.position = mul(output.position, Projection);
	

    output.color = input.color;

	return output;
}

float4 PS(VertexOutput input) : SV_TARGET
{
    return input.color;
}

//와이어프레임모드
RasterizerState FillModeWireFrame
{
    FillMode = Wireframe;
};

technique11 T0
{
	//하나의 통로
	pass P0
	{
		SetVertexShader(CompileShader(vs_5_0, VS()));
		SetPixelShader(CompileShader(ps_5_0, PS()));
	}

	pass P1
	{
        SetRasterizerState(FillModeWireFrame); 
		SetVertexShader(CompileShader(vs_5_0, VS()));
		SetPixelShader(CompileShader(ps_5_0, PS()));
	}
};

ConstBufferDemo.h

#pragma once
#include "IExecute.h"
#include "Geometry.h"

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


	shared_ptr<Shader> _shader;
	
	shared_ptr<Geometry<VertexColorData>> _geometry;

	shared_ptr<VertexBuffer> _vertexBuffer;
	shared_ptr<IndexBuffer> _indexBuffer;

	Vec3 _translation = Vec3(0.f, 0.f, 0.f);

	Matrix _world=Matrix::Identity;
	Matrix _view = Matrix::Identity;
	Matrix _projection = Matrix::Identity;
};

 

ConstBufferDemo.cpp

#include "pch.h"
#include "03. ConstBufferDemo.h"
#include "GeometryHelper.h"

void ConstBufferDemo::Init()
{
	_shader = make_shared<Shader>(L"03. ConstBuffer.fx");

	_geometry = make_shared<Geometry<VertexColorData>>();
	GeometryHelper::CreateQuad(_geometry, Color(1.f, 0.f, 0.f, 1.f));

	//고속복사
	_vertexBuffer = make_shared<VertexBuffer>();
	_vertexBuffer->Create(_geometry->GetVertices());

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

void ConstBufferDemo::Update()
{
	float dt = TIME->GetDeltaTime();

	if (INPUT->GetButton(KEY_TYPE::A)) 
	{
		_translation.x -= 3.f * dt;
	}
	else if (INPUT->GetButton(KEY_TYPE::D))
	{
		_translation.x += 3.f * dt;
	}
	else if (INPUT->GetButton(KEY_TYPE::W))
	{
		_translation.y += 3.f * dt;
	}
	else if (INPUT->GetButton(KEY_TYPE::S))
	{
		_translation.y -= 3.f * dt;
	}

	//SRT
	_world = Matrix::CreateTranslation(_translation);
}

void ConstBufferDemo::Render()
{

	_shader->GetMatrix("World")->SetMatrix((float*)&_world);
	_shader->GetMatrix("View")->SetMatrix((float*)&_view);
	_shader->GetMatrix("Projection")->SetMatrix((float*)&_projection);

	uint32 stride = _vertexBuffer->GetStride();
	uint32 offset = _vertexBuffer->GetOffset();

	DC->IASetVertexBuffers(0, 1, _vertexBuffer->GetComPtr().GetAddressOf(), &stride, &offset);
	DC->IASetIndexBuffer(_indexBuffer->GetComPtr().Get(), DXGI_FORMAT_R32_UINT, 0);
	
	//_buffer->GetCount()
	_shader->DrawIndexed(0, 0, _indexBuffer->GetCount(), 0, 0);
}

 

Main.cpp

#include "pch.h"
#include "Main.h"
#include "Engine/Game.h"
#include "01. TriangleDemo.h"
#include "02. QuadDemo.h"
#include "03. ConstBufferDemo.h"

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
	GameDesc desc;
	desc.appName = L"GameCoding";
	desc.hInstance = hInstance;
	desc.vsync = false;
	desc.hWnd = NULL;
	desc.width = 800;
	desc.height = 600;
	desc.clearColor = Color(0.5f, 0.5f, 0.5f, 0.5f);
	//실행단위
	desc.app = make_shared<ConstBufferDemo>();

	GAME->Run(desc);

	return 0;
}

 

이렇게 해주면 사각형이 움직인다.

 

3.Camera

카메라를 구현하기 위한 기본 코드는 이전 2D에서 했던 코드를 가져오지만 Camera부분에서 3D에기 때문에원근투영만 되는 카메라로 바꿔주도록하자 

Camera.h

#pragma once
#include "Component.h"

enum class ProjectType
{
	Persperctive,		//원근투영
	Orthographic,		//직교투영
};

class Camera : public Component
{
	using Super = Component;
public:
	Camera();
	virtual ~Camera();

	virtual void Update() override;

	void UpdateMatrix();

	void SetNear(float value) { _near = value; }
	void SetFar(float value) { _far = value; }
	void SetFOV(float value) { _fov = value; }
	void SetWidth(float value) { _width = value; }
	void SetHeight(float value) { _height = value; }

	Matrix& GetViewMatrix() { return _matview; }
	Matrix& GetProjectionMatrix() { return _matProjection; }
	

private:
	Matrix _matview = Matrix::Identity;
	Matrix _matProjection = Matrix::Identity;

	float _near = 1.f;
	float _far = 1000.f;
	float _fov = XM_PI / 4.f;
	float _width = 0.f;
	float _height = 0.f;
public:
	//일단 카메라가 한개라고 가정
	static Matrix S_MatView;
	static Matrix S_MatProjection;
};

 

Camera.cpp

#include "pch.h"
#include "Camera.h"

Matrix Camera::S_MatView = Matrix::Identity;
Matrix Camera::S_MatProjection = Matrix::Identity;

Camera::Camera() : Super(ComponentType::Camera)
{
	_width = static_cast<float>(GAME->GetGameDesc().width);
	_height = static_cast<float>(GAME->GetGameDesc().height);
}

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();
	
	//필드오브 뷰,비율,니어,파
	S_MatProjection = ::XMMatrixPerspectiveFovLH(_fov, _width / _height, _near, _far);
}

 

그리고 카메라를 움직일때 바로 CameraDemo의 Update에서 작동시키는 것이아닌 Camera라는 오브젝트 하나를 만든다음 거기에 스크립트를 붙여서 작동하게 만들어주자 

CameraScript.h

#pragma once
#include "MonoBehaviour.h"

class CameraScript : public MonoBehaviour
{
public:
	virtual void Start() override;
	virtual void Update() override;

	float _speed = 10.f;
};

CameraScript.cpp

#include "pch.h"
#include "CameraScript.h"
#include "Transform.h"

void CameraScript::Start()
{

}

void CameraScript::Update()
{
	float dt = TIME->GetDeltaTime();

	Vec3 pos = GetTransform()->GetPosition();

	if (INPUT->GetButton(KEY_TYPE::W))
		pos += GetTransform()->GetLook() * _speed * dt;
	
	if (INPUT->GetButton(KEY_TYPE::S))
		pos -= GetTransform()->GetLook() * _speed * dt;

	if (INPUT->GetButton(KEY_TYPE::A))
		pos -= GetTransform()->GetRight() * _speed * dt;

	if (INPUT->GetButton(KEY_TYPE::D))
		pos += GetTransform()->GetLook() * _speed * dt;

	GetTransform()->SetPosition(pos);

	if (INPUT->GetButton(KEY_TYPE::Q))
	{
		Vec3 rotation = GetTransform()->GetLocalRotation();
		rotation.x -= dt * 0.5f;
		GetTransform()->SetLocalRotation(rotation);
	}

	if (INPUT->GetButton(KEY_TYPE::E))
	{
		Vec3 rotation = GetTransform()->GetLocalRotation();
		rotation.x += dt * 0.5f;
		GetTransform()->SetLocalRotation(rotation);
	}

	if (INPUT->GetButton(KEY_TYPE::Z))
	{
		Vec3 rotation = GetTransform()->GetLocalRotation();
		rotation.y -= dt * 0.5f;
		GetTransform()->SetLocalRotation(rotation);
	}

	if (INPUT->GetButton(KEY_TYPE::C))
	{
		Vec3 rotation = GetTransform()->GetLocalRotation();
		rotation.y += dt * 0.5f;
		GetTransform()->SetLocalRotation(rotation);
	}


}

 

이제 메인코드와 CameraDemo 코드를 수정해주자

카메라라는 이름의 게임오브젝트에 만들어준 CameraScript를 붙여주고 Update에서는 카메라 오브젝트의 Update가 작동

하도록 하면 된다.

CameraDemo.h

#pragma once

#include "IExecute.h"
#include "Geometry.h"

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


	shared_ptr<Shader> _shader;
	
	// Object
	shared_ptr<Geometry<VertexColorData>> _geometry;
	shared_ptr<VertexBuffer> _vertexBuffer;
	shared_ptr<IndexBuffer> _indexBuffer;


	Matrix _world=Matrix::Identity;
	Matrix _view = Matrix::Identity;
	Matrix _projection = Matrix::Identity;

	//Camera
	shared_ptr<GameObject> _camera;
};

CameraDemo.cpp

#include "pch.h"
#include "04. CameraDemo.h"
#include "GeometryHelper.h"
#include "Camera.h"
#include "CameraScript.h"

void CameraDemo::Init()
{
	_shader = make_shared<Shader>(L"03. ConstBuffer.fx");

	_geometry = make_shared<Geometry<VertexColorData>>();
	GeometryHelper::CreateQuad(_geometry, Color(1.f, 0.f, 0.f, 1.f));

	//고속복사
	_vertexBuffer = make_shared<VertexBuffer>();
	_vertexBuffer->Create(_geometry->GetVertices());

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

	// Camera
	_camera = make_shared<GameObject>();
	_camera->GetOrAddTransform();
	_camera->AddComponent(make_shared<Camera>());
	_camera->AddComponent(make_shared<CameraScript>());
}

void CameraDemo::Update()
{
	_camera->Update();
}

void CameraDemo::Render()
{
	_shader->GetMatrix("World")->SetMatrix((float*)&_world);
	_shader->GetMatrix("View")->SetMatrix((float*)&Camera::S_MatView);
	_shader->GetMatrix("Projection")->SetMatrix((float*)&Camera::S_MatProjection);

	uint32 stride = _vertexBuffer->GetStride();
	uint32 offset = _vertexBuffer->GetOffset();

	DC->IASetVertexBuffers(0, 1, _vertexBuffer->GetComPtr().GetAddressOf(), &stride, &offset);
	DC->IASetIndexBuffer(_indexBuffer->GetComPtr().Get(), DXGI_FORMAT_R32_UINT, 0);
	
	//_buffer->GetCount()
	_shader->DrawIndexed(0, 0, _indexBuffer->GetCount(), 0, 0);
}

 

Main.cpp

#include "pch.h"
#include "Main.h"
#include "Engine/Game.h"
#include "01. TriangleDemo.h"
#include "02. QuadDemo.h"
#include "03. ConstBufferDemo.h"
#include "04. CameraDemo.h"

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
	GameDesc desc;
	desc.appName = L"GameCoding";
	desc.hInstance = hInstance;
	desc.vsync = false;
	desc.hWnd = NULL;
	desc.width = 800;
	desc.height = 600;
	desc.clearColor = Color(0.5f, 0.5f, 0.5f, 0.5f);
	//실행단위
	desc.app = make_shared<CameraDemo>();

	GAME->Run(desc);

	return 0;
}

 

이렇게 해주면 이제 카메라가 움직일 수 있게 된다.

 

1.Mesh & Material

이제 MeshRenderer에 있는 렌더링관련 변수들을 Mesh에 사용하는 변수인지 Material에서 사용하는 변수인지 구분하여 클래스를 구현해주자

이때 Mesh는 물체의 모양이고 Material은 물체의 재질이라고  보면 된다.

이제 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;
	//인덱스버퍼  이거도 Geometry에 포함
	shared_ptr<IndexBuffer> _indexBuffer;
};

 

Mesh.cpp

#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
	_indexBuffer = make_shared<IndexBuffer>(_device);
	_indexBuffer->Create(_geometry->GetIndices());
}

 

이제 MeshRederer 클래스를 수정해주자

수정할 때 MeshRenderer에서 Mesh를 지정하고 가져올 수 도 있게 함수를 만들어주자

MeshRenderer.h

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

 

 

그리고 ResourceManager의 CreateDefaultMesh 함수를 통해 Mesh가 초기화될 수 있도록하자

ResourceManager.cpp

#include "pch.h"
#include "ResourceManager.h"
#include "Texture.h"
#include "Mesh.h"
#include "Shader.h"
#include "Material.h"
#include "Animation.h"

ResourceManager::ResourceManager(ComPtr<ID3D11Device> device)
	:_device(device)
{

}

void ResourceManager::Init()
{
	CreateDefaultTexture();
	CreateDefaultMesh();
	CreateDefaultShader();
	CreateDefaultMaterial();
	CreateDefaultAnimation();
}

void ResourceManager::CreateDefaultTexture()
{
	{
		auto texture = make_shared<Texture>(_device);
		texture->SetName(L"Cat");
		texture->Create(L"cat.png");
		Add(texture->GetName(), texture);
	}
}

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

void ResourceManager::CreateDefaultShader()
{

}

void ResourceManager::CreateDefaultMaterial()
{

}

void ResourceManager::CreateDefaultAnimation()
{

}

이제 직접 Mesh를 지정해보자 Mesh의 지정은 우리가 고양이그림을 지정해주는 부분인 SceneManager에서 지정해주면 된다.

SceneManager.cpp

	//cat
	shared_ptr<GameObject> cat = make_shared<GameObject>(_graphics->GetDevice(), _graphics->GetDeviceContext());
	{
		cat->GetOrAddTransform();
		auto meshRenderer = make_shared<MeshRenderer>(_graphics->GetDevice(), _graphics->GetDeviceContext());
		cat->AddComponent(meshRenderer);
		//_cat->GetTransform()->GetScale(Vec3(100.f,100.f,100.f));
		//...
		auto mesh = RESOURCES->Get<Mesh>(L"Rectangle");
		meshRenderer->SetMesh(mesh);

		scene->AddGameObject(cat);
	}

 

 

다음은 Material 클래스와 Shader 클래스를 만들어 줄 것이다. Material 클래스에서 쉐이더에 넘겨주는 여러 인자와 shader 변수를 가지고 있도록하고 shader는 렌더링 과정에 있는 inputLayout - vertexShader - PixelShader를 가지오있도록 하자

Material.h

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

 

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;
	//Material
	//쉐이더 넘겨줄 때 어떤모양으로 되어있는지
	shared_ptr<InputLayout> _inputLayout;
	//VS
	shared_ptr<VertexShader> _vertexShader;
	//PS
	shared_ptr<PixelShader> _pixelShader;
	//SRV - 이미지를 어떻게 쓸것인가 - 텍스처
};

 

그리고 이에 맞게 기본 쉐이더와 Material을 초기화해줄 함수를 ResourceManager에서 구현해주자

ResourceManager.cpp

void ResourceManager::CreateDefaultShader()
{
	auto vertexShader = make_shared<VertexShader>(_device);
	vertexShader->Create(L"Default.hlsl", "VS", "vs_5_0");

	//인풋레이아웃
	/// <summary>
	/// 입력이 어떻게 이뤄져있는지
	/// </summary>
	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);
}

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"Cat"));
	Add(material->GetName(), material);
}

MeshRenderer 클래스를 수정하고 변수들을 관리해줄 헬퍼함수를 추가해주자 

MeshRenderer.h

#pragma once
#include "Component.h"
#include "Material.h"
#include "Shader.h"

class Mesh;
class Material;
class Shader;
class Texture;

class MeshRenderer : public Component
{
	using Super = Component;
public:
	MeshRenderer(ComPtr<ID3D11Device> device, ComPtr<ID3D11DeviceContext> deviceContext);
	virtual ~MeshRenderer();

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

private:
	ComPtr<ID3D11Device> _device;

	//이렇게하면 랜더매니저에서 meshRenderer의 변수 protected처럼 쓸수있음
	friend class RenderManager;
	
	//Mesh
	shared_ptr<Mesh> _mesh;
	//Material
	shared_ptr<Material> _material;
};

 

그리고 고양이를 생성하는 SceneManager부분에서 오브젝트에 Material을 붙여주자

//cat
shared_ptr<GameObject> cat = make_shared<GameObject>(_graphics->GetDevice(), _graphics->GetDeviceContext());
{
	cat->GetOrAddTransform();
	auto meshRenderer = make_shared<MeshRenderer>(_graphics->GetDevice(), _graphics->GetDeviceContext());
	cat->AddComponent(meshRenderer);
	//_cat->GetTransform()->GetScale(Vec3(100.f,100.f,100.f));
	//...
	//Material
	auto material = RESOURCES->Get<Material>(L"Default");
	meshRenderer->SetMaterial(material);
	//Mesh 부분 
	auto mesh = RESOURCES->Get<Mesh>(L"Rectangle");
	meshRenderer->SetMesh(mesh);

	scene->AddGameObject(cat);
}

 

그리고 마지막으로 RenderManager에서 파이프라인부분을 수정해주자 

RenderManager.cpp

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

		PipelineInfo info;
		info.inputLayout = meshRenderer->GetInputLayout();
		info.vertexShader = meshRenderer->GetVertextShader();
		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);
	}
}

 

이렇게 Mesh와 Shader, Material을 리소스로 관리하여 우리가 중앙에서 관리해서 하나를 공유해서 쓸 수 있다.

 

2.Animation

이제 애니메이션을 구현해보자

애니메이션은 전체 이미지에서 개별의 이미지를 자른 다음 여러 이미지를 일정 간격으로 연속으로 보여주는 것으로 

구현할 수 있다. 

지금 우리는 전체에서 부분을 나눌 때 uv 매핑을 통해 영역을 구할 것이다. 

일단 애니메이션의 기반이될 Animation 클래스를 만들어주자 이 클래스에서 구조체를 통해 어디서부터 어떻게 얼마나 그릴지를 정해줄 수 있도록 하고 실제 애니메이션을 하게될 텍스처, 애니메이션의 정보인 키프레임과 같은 변수들과 헬퍼 함수들을 만들어주자

Animation.h

#pragma once
#include "ResourceBase.h"

struct Keyframe
{
	//어디서 부터 그릴 것인지
	Vec2 offset = Vec2{ 0.f,0.f };
	//영역의 크기
	Vec2 size = Vec2{ 0.f,0.f };
	//몇초동안 그릴 것인지
	float time = 0.f;
};

class Animation : public ResourceBase
{
	using Super = ResourceBase;
public:
	Animation();
	virtual ~Animation();

	virtual void Load(const wstring& path) override;
	virtual void Save(const wstring& path) override;

	void SetLoop(bool loop) { _loop = loop; }
	bool IsLoop() { return _loop; }

	void SetTexture(shared_ptr<Texture> texture) { _texture = texture; }
	shared_ptr<Texture> GetTexture() { return _texture; }
	Vec2 GetTextureSize();

	const Keyframe& GetKeyframe(int32 index);
	int32 GetKeyframeCount();
	//원하는 애니메이션 정보 추가
	void AddKeyframe(const Keyframe& keyframe);

private:
	bool _loop = false;
	shared_ptr<Texture> _texture;
	vector<Keyframe> _keyframes;
};

 

Animation.cpp

#include "pch.h"
#include "Animation.h"
#include "Texture.h"

Animation::Animation() : Super(ResourceType::Animation)
{
}

Animation::~Animation()
{
}

void Animation::Load(const wstring& path)
{
}

void Animation::Save(const wstring& path)
{
}

Vec2 Animation::GetTextureSize()
{
	return _texture->GetSize();
}

const Keyframe& Animation::GetKeyframe(int32 index)
{
	return _keyframes[index];
}

int32 Animation::GetKeyframeCount()
{
	return static_cast<int32>(_keyframes.size());
}

void Animation::AddKeyframe(const Keyframe& keyframe)
{
	_keyframes.push_back(keyframe);
}

 

그리고 이 애니메이션을 실제로 재생하게될 컴포넌트 Animator 클래스를 만들어주자.

이 클래스에서는

하나의 애니메이션 프레임이 재생되는 시간, 재생되고 있는 키프레임번호등의 변수를 가지고 있으며 애니메이션을 설정하는 함수를 가지고 있다. 

Update 되는 동안 만약 반복이 활성화되어 있다면 하나의 애니메이션을 반복하고 아니라면 하나의 이미지의 지속시간이 끝났다면 다음 이미지로 넘어갈 수 있게 하자

 

Animator.h

#pragma once
#include "Component.h"
#include "Animation.h"

class Animation;

class Animator : public Component
{
	using Super = Component;

public:
	Animator();
	virtual ~Animator();

	void Init();
	void Update();

	shared_ptr<Animation> GetCurrentAnimation();
	const Keyframe& GetCurrentKeyframe();

	void SetAnimation(shared_ptr<Animation> animation) { _currentAnimation = animation; }

private:
	float _sumTime = 0.f;

	//현재 실행 중인 키프레임 번호
	int32 _currentKeyFrameIndex = 0;
	shared_ptr<Animation> _currentAnimation;

};

 

Animator.cpp

#include "pch.h"
#include "Animator.h"
#include "Game.h"
#include "TimeManager.h"

Animator::Animator()
	: Super(ComponentType::Animator)
{
}

Animator::~Animator()
{
}

void Animator::Init()
{
}

void Animator::Update()
{
	shared_ptr<Animation> animation = GetCurrentAnimation();
	if (animation == nullptr)
		return;

	const Keyframe& keyframe = animation->GetKeyframe(_currentKeyFrameIndex);

	float deltaTime = TIME->GetDeltaTime();
	_sumTime += deltaTime;

	//하나의 이미지 지속시간이 끝났다면
	if (_sumTime >= keyframe.time)
	{
		//다음 프레임으로
		_currentKeyFrameIndex++;
		int32 totalCount = animation->GetKeyframeCount();

		//다 재생했다면
		if (_currentKeyFrameIndex >= totalCount)
		{
			if (animation->IsLoop())
				_currentKeyFrameIndex = 0;
			else
				_currentKeyFrameIndex = totalCount - 1;
		}

		_sumTime = 0.f;
	}
}

shared_ptr<Animation> Animator::GetCurrentAnimation()
{
	return _currentAnimation;
}

const Keyframe& Animator::GetCurrentKeyframe()
{
	return _currentAnimation->GetKeyframe(_currentKeyFrameIndex);
}

 

이제 ResourceManager에 CreateDefaultAnimation 함수에서 기본 애니메이션을 컴포넌트에 추가하는 부분을 구현해보자

먼저 snake라는 전체이미지를 텍스처로 가져온다음 이걸 잘라서 쓸 수 있도록 하자.

ResourceManager.h

void ResourceManager::CreateDefaultTexture()
{
	{
		auto texture = make_shared<Texture>(_device);
		texture->SetName(L"Cat");
		texture->Create(L"cat.png");
		Add(texture->GetName(), texture);
	}

	{
		auto texture = make_shared<Texture>(_device);
		texture->SetName(L"Snake");
		texture->Create(L"Snake.bmp");
		Add(texture->GetName(), texture);
	}
}

void ResourceManager::CreateDefaultAnimation()
{
	shared_ptr<Animation> animation = make_shared<Animation>();
	animation->SetName(L"SnakeAnim"); 
	animation->SetTexture(Get<Texture>(L"Snake"));
	animation->SetLoop(true);

	//각 프레임당 이미지위치정보
	animation->AddKeyframe(Keyframe{ Vec2{0.f,0.f},Vec2{100.f,100.f},0.1f });
	animation->AddKeyframe(Keyframe{ Vec2{100.f,0.f},Vec2{100.f,100.f},0.1f });
	animation->AddKeyframe(Keyframe{ Vec2{200.f,0.f},Vec2{100.f,100.f},0.1f });
	animation->AddKeyframe(Keyframe{ Vec2{300.f,0.f},Vec2{100.f,100.f},0.1f });

	Add(animation->GetName(), animation);
}

 

그리고 SceneManager에 오브젝트에 Animator 컴포넌트를 붙여주자 

SceneManager.cpp

//cat
shared_ptr<GameObject> cat = make_shared<GameObject>(_graphics->GetDevice(), _graphics->GetDeviceContext());
{
	cat->GetOrAddTransform();
	auto meshRenderer = make_shared<MeshRenderer>(_graphics->GetDevice(), _graphics->GetDeviceContext());
	cat->AddComponent(meshRenderer);
	//_cat->GetTransform()->GetScale(Vec3(100.f,100.f,100.f));
	//...
	//Material
	auto material = RESOURCES->Get<Material>(L"Default");
	meshRenderer->SetMaterial(material);
	//Mesh 부분 
	auto mesh = RESOURCES->Get<Mesh>(L"Rectangle");
	meshRenderer->SetMesh(mesh);

	scene->AddGameObject(cat);
}
{
	auto animator = make_shared<Animator>();
	cat->AddComponent(animator);
	auto anim = RESOURCES->Get<Animation>(L"SnakeAnim");
	animator->SetAnimation(anim);
}

 

하지만 이렇게만 해주면 아직 애니메이션이 실행되지 않는데 이것은 우리가 영역을 지정은 해주었지만 이 영역이 적용되는 코드를 아직 만들어주지 않았기 때문이다. 

이를 구현하기 위해 RenderHelper 헤더파일에 Animation에 관한 Data를 선언하고 이를 RenderManager에서 GPU로 넘겨줄 수 있게 PushAnimationData 라는 함수를 만들어주자 그리고 오브젝트를 렌더할 때 Animator 컴포넌트가 있는지 검사

하고 있다면 가지고 있는 애니메이션 정보를 GPU로 넘겨주고 아니라면 0값으로 초기화해주도록 하자

RenderHelper.h

//GPU로 넘겨줄때 16바이트로 정렬
struct AnimationData
{
	Vec2 spriteOffset;
	Vec2 spriteSize;
	//전체 사이즈
	Vec2 textureSize;
	float useAnimation;
	//16바이트 채우기위한 더미
	float padding;
};

 

RenderManager.h

public:
		void PushAnimationData();
private:
		//Animation
		AnimationData _animationData;
		shared_ptr<ConstantBuffer<AnimationData>> _animationBuffer;

RenderManager.cpp

void RenderManager::Init()
{
	_pipeline = make_shared<Pipeline>(_deviceContext);

	_cameraBuffer = make_shared<ConstantBuffer<CameraData>>(_device, _deviceContext);
	_cameraBuffer->Create();
	_transformBuffer = make_shared<ConstantBuffer<TransformData>>(_device, _deviceContext);
	_transformBuffer->Create();
	_animationBuffer = make_shared<ConstantBuffer<AnimationData>>(_device, _deviceContext);
	_animationBuffer->Create();

	_rasterizerState = make_shared<RasterizerState>(_device);
	_rasterizerState->Create();
	_blendState = make_shared<BlendState>(_device);
	_blendState->Create();
	_samplerState = make_shared<SamplerState>(_device);
	_samplerState->Create();
}

void RenderManager::PushAnimationData()
{
	_animationBuffer->CopyData(_animationData);
}

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

		//Animation 여부 
		shared_ptr<Animator> animator = gameObject->GetAnimator();
		if (animator)
		{
			const Keyframe& keyframe = animator->GetCurrentKeyframe();
			_animationData.spriteOffset = keyframe.offset;
			_animationData.spriteSize = keyframe.size;
			_animationData.textureSize = animator->GetCurrentAnimation()->GetTextureSize();
			_animationData.useAnimation = 1.f;
			PushAnimationData();

			_pipeline->SetConstantBuffer(2, SS_VertexShader, _animationBuffer);
			_pipeline->SetTexture(0, SS_PixelShader, animator->GetCurrentAnimation()->GetTexture());

		}
		else
		{
			_animationData.spriteOffset = Vec2(0.f, 0.f);
			_animationData.spriteSize = Vec2(0.f, 0.f);
			_animationData.textureSize = Vec2(0.f, 0.f);
			_animationData.useAnimation = 0.f;
			PushAnimationData();

			_pipeline->SetConstantBuffer(2, SS_VertexShader, _animationBuffer);
			_pipeline->SetTexture(0, SS_PixelShader, meshRenderer->GetTexture());
		}

		PipelineInfo info;
		info.inputLayout = meshRenderer->GetInputLayout();
		info.vertexShader = meshRenderer->GetVertextShader();
		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);
	}
}

 

여기서 이제 입력에 따라 쉐이더에서 uv좌표도 달라져야하는데 이때 선택한 스프라이트 부분만 텍스처에서 사용할 수 있도록 하고 이에 따른 시작점도 계산해줄 계산식을 만들어주면 된다. 

//정점 쉐이더- 위치관련 -> 레스터라이저-정점바탕으로 도형만들고 내/외부 판단 및 보간
VS_OUTPUT VS(VS_INPUT input)
{
    VS_OUTPUT output;
    
    //World view projection
    float4 position = mul(input.position, matWorld);
    position = mul(position, matView);
    position = mul(position, matProjection);
    
    output.position = position;
    output.uv = input.uv;
    
    if(useAnimation==1.f)
    {
        // 500 -부분 / 1000 전체
        output.uv *= spriteSize / textureSize;
        output.uv += spriteOffset / textureSize;
    }
    
    return output;
}

 

이렇게 해주면 애니메이션이 출력되게 된다.

 

만약 여기서 SceneManager에서 오브젝트를 하나추가하고 위치를 조정해주면 아래와 같이 되는 것이다.

SceneManager.cpp

//cat2
{
	shared_ptr<GameObject> cat = make_shared<GameObject>(_graphics->GetDevice(), _graphics->GetDeviceContext());
	cat->GetOrAddTransform()->SetPosition(Vec3(-1.f, -1.f, 0.f));
	{
		cat->GetOrAddTransform();
		auto meshRenderer = make_shared<MeshRenderer>(_graphics->GetDevice(), _graphics->GetDeviceContext());
		cat->AddComponent(meshRenderer);
		//_cat->GetTransform()->GetScale(Vec3(100.f,100.f,100.f));
		//...
		//Material
		auto material = RESOURCES->Get<Material>(L"Default");
		meshRenderer->SetMaterial(material);
		//Mesh 부분 
		auto mesh = RESOURCES->Get<Mesh>(L"Rectangle");
		meshRenderer->SetMesh(mesh);

		scene->AddGameObject(cat);
	}
	{
		auto animator = make_shared<Animator>();
		cat->AddComponent(animator);
		auto anim = RESOURCES->Get<Animation>(L"SnakeAnim");
		animator->SetAnimation(anim);
	}
}

 

 

여기서 만약 카메라를 움직이고 싶다면 카메라를 움직여주는 클래스를 하나 만들고 이를 Component로 추가해주면 된다.

CameraMove.h

#pragma once
#include "MonoBehaviour.h"
class CameraMove : public MonoBehaviour
{
public:
	virtual void Update() override;
};

CameraMove.cpp

#include "pch.h"
#include "CameraMove.h"
#include "GameObject.h"

void CameraMove::Update()
{
	auto pos = GetTransform()->GetPosition();
	pos.x += 0.001f;
	GetTransform()->SetPosition(pos);
}

SceneManager.cpp

#include "CameraMove.h"
shared_ptr<Scene> SceneManager::LoadTestScene()
{
	//툴이 없기때문에 임의로 씬할당 후 제어
	shared_ptr<Scene> scene = make_shared<Scene>();

	//제어

	//Camera
	{
		shared_ptr<GameObject> camera = make_shared<GameObject>(_graphics->GetDevice(), _graphics->GetDeviceContext());
		{
			camera->GetOrAddTransform();
			camera->AddComponent(make_shared<Camera>());
			scene->AddGameObject(camera);
		}
		{
			camera->AddComponent(make_shared<CameraMove>());
		}
	}
}

 

이렇게 해주면 카메라가 오른쪽으로 움직이는 것을 볼 수 있다.

 

3.Data

 

이제 하드코딩을 해주는 것 말고 실제 xml 파일을 가져와서 이를 통해 애니메이션이 실행되도록 해보자

이때 xml 가져오는 것은 tinyxml이라는 외부라이브러리를 가져와서 사용할 것이다.

이 xml을 가져오고 저장하는 것은 Animation 클래스의 Save, Load 함수를 통해 구현하자 일단 애니메이션 정보가 담긴

새로운 xml 파일을 저장하고 이를 통해 Load하는 방식으로 하자. 

일단 미리 완성되어있는 xml 파일을 살펴보자 

TestAnim.xml

<Animation Name="SnakeAnim" Loop="true" TexturePath="TODO">
    <Keyframe OffsetX="0" OffsetY="0" SizeX="100" SizeY="100" Time="0.1"/>
    <Keyframe OffsetX="100" OffsetY="0" SizeX="100" SizeY="100" Time="0.1"/>
    <Keyframe OffsetX="200" OffsetY="0" SizeX="100" SizeY="100" Time="0.1"/>
    <Keyframe OffsetX="300" OffsetY="0" SizeX="100" SizeY="100" Time="0.1"/>
</Animation>

 

xml 파일을 보면 트리구조로 되어있는 것을 볼 수 있다. 우리가 저장할 때도 이런 방식으로 저장해주어야 한다.

 

Animation.cpp

void Animation::Load(const wstring& path)
{
	tinyxml2::XMLDocument doc;

	string pathStr(path.begin(), path.end());
	XMLError error = doc.LoadFile(pathStr.c_str());
	assert(error == XMLError::XML_SUCCESS);

	XMLElement* root = doc.FirstChildElement();
	string nameStr = root->Attribute("Name");
	_name = wstring(nameStr.begin(), nameStr.end());
	_loop = root->BoolAttribute("Loop");
	_path = path;

	//Load Texture

	//Keyframe
	XMLElement* node = root->FirstChildElement();
	for (; node != nullptr; node = node->NextSiblingElement())
	{
		
		Keyframe keyframe;

		keyframe.offset.x = node->FloatAttribute("OffsetX");
		keyframe.offset.y = node->FloatAttribute("OffsetY");
		keyframe.size.x = node->FloatAttribute("SizeX");
		keyframe.size.y = node->FloatAttribute("SizeY");
		keyframe.time = node->FloatAttribute("Time");

		AddKeyframe(keyframe);
	}
}

void Animation::Save(const wstring& path)
{
	tinyxml2::XMLDocument doc;

	//제일 상위노드 생성
	XMLElement* root = doc.NewElement("Animation");
	doc.LinkEndChild(root);

	//wstring -> string
	string nameStr(GetName().begin(), GetName().end());
	//Value값 넣어주기 Key, Value
	root->SetAttribute("Name", nameStr.c_str());
	root->SetAttribute("Loop", _loop);
	root->SetAttribute("TexturePath", "TODO");

	for (const auto& keyframe : _keyframes)
	{
		XMLElement* node = doc.NewElement("Keyframe");
		root->LinkEndChild(node);

		node->SetAttribute("OffsetX", keyframe.offset.x);
		node->SetAttribute("OffsetY", keyframe.offset.y);
		node->SetAttribute("SizeX", keyframe.size.x);
		node->SetAttribute("SizeY", keyframe.size.y);
		node->SetAttribute("Time", keyframe.time);
	}
	string pathStr(path.begin(), path.end());
	auto result = doc.SaveFile(pathStr.c_str());
	assert(result == XMLError::XML_SUCCESS);
}


사용할 때는 기본 애니메이션을 만들어주는 함수가 있는 ResourceManager에서 사용하면 된다.

void ResourceManager::CreateDefaultAnimation()
{
	shared_ptr<Animation> animation = make_shared<Animation>();
	animation->SetName(L"SnakeAnim"); 
	animation->SetTexture(Get<Texture>(L"Snake"));
	animation->SetLoop(true);

	//각 프레임당 이미지위치정보
	animation->AddKeyframe(Keyframe{ Vec2{0.f,100.f},Vec2{100.f,100.f},0.1f });
	animation->AddKeyframe(Keyframe{ Vec2{100.f,100.f},Vec2{100.f,100.f},0.1f });
	animation->AddKeyframe(Keyframe{ Vec2{200.f,100.f},Vec2{100.f,100.f},0.1f });
	animation->AddKeyframe(Keyframe{ Vec2{300.f,100.f},Vec2{100.f,100.f},0.1f });

	Add(animation->GetName(), animation);

	// XML + JSON
	//외부라이브러리 활용
	animation->Save(L"TestAnim.xml");

	shared_ptr<Animation> anim2 = make_shared<Animation>();
	anim2->Load(L"TestAnim.xml");
}

 

이렇게 해주면 코드가 있는 경로에 xml 파일이 생성된다.

<Animation Name="SnakeAnim" Loop="true" TexturePath="TODO">
    <Keyframe OffsetX="0" OffsetY="100" SizeX="100" SizeY="100" Time="0.1"/>
    <Keyframe OffsetX="100" OffsetY="100" SizeX="100" SizeY="100" Time="0.1"/>
    <Keyframe OffsetX="200" OffsetY="100" SizeX="100" SizeY="100" Time="0.1"/>
    <Keyframe OffsetX="300" OffsetY="100" SizeX="100" SizeY="100" Time="0.1"/>
</Animation>

 

오늘부터는 전체 게임코드에 오브젝트를 넣고 렌더하는 것이 아닌 SceneManager를 가지고 유니티와 같이 하나의 Scene안에서 게임오브젝트, 카메라와 같은 여러 오브젝트를 묶어서 다룰 수 있게 만들어보자. 

 

1.SceneManager

일단 Scene 클래스를 만들어서 게임오브젝트를 추가하고 제거할 수 있으면 기본적인 Awake부터 Update까지의 함수를 추가해서 GameObject의 Awake 등의 기본함수와 맞춰서 실행될 수 있도록하자.

Scene.h

#pragma once

class GameObject;

class Scene
{
public:
	void Awake();
	void Start();
	void Update();
	void LateUpdate();
	void FixedUpdate();

public:
	void AddGameObject(shared_ptr<GameObject> gameObject);
	void RemoveGameObject(shared_ptr<GameObject> gameObject);

	const vector<shared_ptr<GameObject>>& GetGameObjects() { return _gameObjects; }
	
private:
	vector<shared_ptr<GameObject>> _gameObjects;
};

 

Scene.cpp

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

void Scene::Awake()
{
	for (const shared_ptr<GameObject>& gameObject : _gameObjects)
	{
		gameObject->Awake();
	}
}

void Scene::Start()
{
	for (const shared_ptr<GameObject>& gameObject : _gameObjects)
	{
		gameObject->Start();
	}
}

void Scene::Update()
{
	for (const shared_ptr<GameObject>& gameObject : _gameObjects)
	{
		gameObject->Update();
	}
}

void Scene::LateUpdate()
{
	for (const shared_ptr<GameObject>& gameObject : _gameObjects)
	{
		gameObject->LateUpdate();
	}
}

void Scene::FixedUpdate()
{
	for (const shared_ptr<GameObject>& gameObject : _gameObjects)
	{
		gameObject->FixedUpdate();
	}
}

void Scene::AddGameObject(shared_ptr<GameObject> gameObject)
{
	_gameObjects.push_back(gameObject);
}

void Scene::RemoveGameObject(shared_ptr<GameObject> gameObject)
{
	auto findit = std::find(_gameObjects.begin(), _gameObjects.end(),gameObject);
	if (findit != _gameObjects.end())
		_gameObjects.erase(findit);
}

 

그리고 이 Scene을 관리해줄 SceneManager를 만들어주자

원래 엔진은 툴을 활용하여 Scene을 추가하고 로드할 수 도 있지만 지금은 코드상으로 만들고 있기 때문에 우리가 TestScene을 만들어서 그것을 제어하여 활용하도록 하자.

SceneManager.h

#pragma once

class Scene;

class SceneManager
{
public:
	SceneManager();
	
	void Init();
	void Update();
	
	void LoadScene(wstring sceneName);

public:
	shared_ptr<Scene> GetActiveScene() { return _activeScene; }

private:
	shared_ptr<Scene> LoadTestScene();

private:
	//임시적으로 필요한 것 - 그려주는거
	shared_ptr<Graphics> _graphics;
private:
	shared_ptr<Scene> _activeScene;

};

 

SceneManager.cpp

#include "pch.h"
#include "SceneManager.h"
#include "Scene.h"
#include "GameObject.h"
#include "Camera.h"
#include "Transform.h"
#include "MeshRenderer.h"

SceneManager::SceneManager()
{
}

void SceneManager::Init()
{
	if (_activeScene == nullptr)
		return;

	_activeScene->Awake();
	_activeScene->Start();
}

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

	_activeScene->Update();
	_activeScene->LateUpdate();

	_activeScene->FixedUpdate();
}

void SceneManager::LoadScene(wstring sceneName)
{
	//리소스를 로드해서 해야하지만 임시로 하드코딩

	_activeScene = LoadTestScene();
	Init();
}

shared_ptr<Scene> SceneManager::LoadTestScene()
{
	//툴이 없기때문에 임의로 씬할당 후 제어
	shared_ptr<Scene> scene = make_shared<Scene>();

	//제어

	//Camera
	shared_ptr<GameObject> camera = make_shared<GameObject>(_graphics->GetDevice(), _graphics->GetDeviceContext());
	{
		camera->GetOrAddTransform();
		camera->AddComponent(make_shared<Camera>());
		scene->AddGameObject(camera);
	}

	//cat
	shared_ptr<GameObject> cat = make_shared<GameObject>(_graphics->GetDevice(), _graphics->GetDeviceContext());
	{
		cat->GetOrAddTransform();
		cat->AddComponent(make_shared<MeshRenderer>(_graphics->GetDevice(), _graphics->GetDeviceContext()));
		//_cat->GetTransform()->GetScale(Vec3(100.f,100.f,100.f));
		//...
		scene->AddGameObject(cat);
	}
	return scene;
}

 

이제 여러 Manager가 생길텐데 이때 어떻게 여러 Manager를 관리할지가 관건이다. 싱글톤 방식을 활용할 수 도 있겠지만 지금은 제일 상위 클래스인 Game이 모든 Manager를 가지고 있게하여 이를 관리할 수 있도록 하자. 그리고 Game을 어디서든 쉽게 호출 가능하도록 전역변수로 extern 해주자.

Game.h

#pragma once
#include "Graphics.h"
#include "GameObject.h"

class SceneManager;

class Game
{
public:
	Game();
	~Game();
public:
	void Init(HWND hwnd);		//윈도우 핸들받아줌
	void Update();
	void Render();

	shared_ptr<SceneManager> GetSceneManager() { return _scene; }

private:
	HWND _hwnd;

	shared_ptr<Graphics> _graphics;
	shared_ptr<Pipeline> _pipeline;

private:
	shared_ptr<SceneManager> _scene;
}; 

extern unique_ptr<Game> GGame;

 

Game.cpp

#include "pch.h"
#include "Game.h"
#include "Camera.h"
#include "MeshRenderer.h"
#include "SceneManager.h"

unique_ptr<Game> GGame = make_unique<Game>();
Game::Game()
{
	
}

Game::~Game()
{
}

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

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

	_scene = make_shared<SceneManager>(_graphics);

	SCENE->LoadScene(L"Test");
}

void Game::Update()
{
	_graphics->RenderBegin();			//초기화
	// IA - VS - RS - PS -OM

	SCENE->Update();
	_graphics->RenderEnd();			//제출
}

void Game::Render()
{
	
}

 

이 전역변수로 지정된 Game 포인터를 매크로 함수로 선언해서 편리하게 사용할 수 있도록 하자.

pch.h

#define		GAME	GGame
#define		SCENE	GAME->GetSceneManager()

 

이렇게 실행해보면 아무것도 안보인다. 왜냐하면 아직 MeshRenderer의 Render부분을 붙여주지않았기때문이다.

일단 Pipeline이 Game에서 가지고 있기때문에 임시로 이것을 가져올 수 있도록 Get함수를 만들어주어 이를 사용하자

Game.h

	shared_ptr<Pipeline> GetPipeline() { return _pipeline; }

 

MeshRenderer.cpp

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 = GetTransform()->GetWorldMatrix();
	_transformBuffer->CopyData(_transformData);


	//Render
	Render(GGame->GetPipeline());
}

 

이렇게 해주면 다시 고양이가 등장하게 된다.

 

이제 입력과 Time, fps를 측정해줄 InputManager와 TimeManager를 추가해주자

InputManager.h

#pragma once

enum class KEY_TYPE
{
	UP = VK_UP,
	DOWN = VK_DOWN,
	LEFT = VK_LEFT,
	RIGHT = VK_RIGHT,

	W = 'W',
	A = 'A',
	S = 'S',
	D = 'D',

	Q = 'Q',
	E = 'E',
	Z = 'Z',
	C = 'C',

	KEY_1 = '1',
	KEY_2 = '2',
	KEY_3 = '3',
	KEY_4 = '4',

	LBUTTON = VK_LBUTTON,
	RBUTTON = VK_RBUTTON,
};

enum class KEY_STATE
{
	NONE,
	PRESS,
	DOWN,
	UP,
	END
};

enum
{
	KEY_TYPE_COUNT = static_cast<int32>(UINT8_MAX + 1),
	KEY_STATE_COUNT = static_cast<int32>(KEY_STATE::END),
};

class InputManager
{
public:
	void Init(HWND hwnd);
	void Update();

	// 누르고 있을 때
	bool GetButton(KEY_TYPE key) { return GetState(key) == KEY_STATE::PRESS; }
	// 맨 처음 눌렀을 때
	bool GetButtonDown(KEY_TYPE key) { return GetState(key) == KEY_STATE::DOWN; }
	// 맨 처음 눌렀다 뗐을 때
	bool GetButtonUp(KEY_TYPE key) { return GetState(key) == KEY_STATE::UP; }
	
	const POINT& GetMousePos() { return _mousePos; }

private:
	inline KEY_STATE GetState(KEY_TYPE key) { return _states[static_cast<uint8>(key)]; }

private:
	HWND _hwnd;
	vector<KEY_STATE> _states;
	POINT _mousePos = {};
};

 

InputManager.cpp

#include "pch.h"
#include "InputManager.h"

void InputManager::Init(HWND hwnd)
{
	_hwnd = hwnd;
	_states.resize(KEY_TYPE_COUNT, KEY_STATE::NONE);
}

void InputManager::Update()
{
	HWND hwnd = ::GetActiveWindow();
	if (_hwnd != hwnd)
	{
		for (uint32 key = 0; key < KEY_TYPE_COUNT; key++)
			_states[key] = KEY_STATE::NONE;

		return;
	}

	BYTE asciiKeys[KEY_TYPE_COUNT] = {};
	if (::GetKeyboardState(asciiKeys) == false)
		return;

	for (uint32 key = 0; key < KEY_TYPE_COUNT; key++)
	{
		// 키가 눌려 있으면 true
		if (asciiKeys[key] & 0x80)
		{
			KEY_STATE& state = _states[key];

			// 이전 프레임에 키를 누른 상태라면 PRESS
			if (state == KEY_STATE::PRESS || state == KEY_STATE::DOWN)
				state = KEY_STATE::PRESS;
			else
				state = KEY_STATE::DOWN;
		}
		else
		{
			KEY_STATE& state = _states[key];

			// 이전 프레임에 키를 누른 상태라면 UP
			if (state == KEY_STATE::PRESS || state == KEY_STATE::DOWN)
				state = KEY_STATE::UP;
			else
				state = KEY_STATE::NONE;
		}
	}

	::GetCursorPos(&_mousePos);
	::ScreenToClient(_hwnd, &_mousePos);
}

 

TimeManager.h

#pragma once


class TimeManager
{
public:
	void Init();
	void Update();

	uint32 GetFps() { return _fps; }
	float GetDeltaTime() { return _deltaTime; }

private:
	uint64	_frequency = 0;
	uint64	_prevCount = 0;
	float	_deltaTime = 0.f;

private:
	uint32	_frameCount = 0;
	float	_frameTime = 0.f;
	uint32	_fps = 0;
};

TimeManager.cpp

#include "pch.h"
#include "TimeManager.h"

void TimeManager::Init()
{
	//WinAPI기능 - 시간재주는 기능
	::QueryPerformanceFrequency(reinterpret_cast<LARGE_INTEGER*>(&_frequency));
	::QueryPerformanceCounter(reinterpret_cast<LARGE_INTEGER*>(&_prevCount)); // CPU 클럭
}

void TimeManager::Update()
{
	uint64 currentCount;
	::QueryPerformanceCounter(reinterpret_cast<LARGE_INTEGER*>(&currentCount));

	_deltaTime = (currentCount - _prevCount) / static_cast<float>(_frequency);
	_prevCount = currentCount;

	_frameCount++;
	_frameTime += _deltaTime;

	if (_frameTime > 1.f)
	{
		_fps = static_cast<uint32>(_frameCount / _frameTime);

		_frameTime = 0.f;
		_frameCount = 0;
	}
}

 

이렇게 추가해준 매니저를 사용하기 위해 매크로 함수를 만들어주고 메인코드를 수정해주자

pch.h

#define		INPUT	GAME->GetInputManager()
#define		TIME	GAME->GetTimeManager()
#define		SCENE	GAME->GetSceneManager()

 

Game.h

	shared_ptr<InputManager> GetInputManager() { return _input; }
	shared_ptr<TimeManager> GetTimeManager() { return _time; }
	shared_ptr<SceneManager> GetSceneManager() { return _scene; }

 

Game.cpp

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

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

	_input = make_shared<InputManager>();
	_input->Init(hwnd);
	_time = make_shared<TimeManager>();
	_time->Init();
	_scene = make_shared<SceneManager>(_graphics);
	_scene->Init();

	SCENE->LoadScene(L"Test");
}

void Game::Update()
{
	_graphics->RenderBegin();			//초기화
	// IA - VS - RS - PS -OM

	TIME->Update();
	INPUT->Update();
	SCENE->Update();

	_graphics->RenderEnd();			//제출
}

 

 

2.ResourceManager

지금의 코드는 물체의 수가 많아지게 되면 MeshRenderer부분이 천번 수행되어야하는데 이렇게 하게되면 너무 효율적이지 못하다. 그렇기 때문에 이러한 물체들을 관리해줄 클래스와 이를 관리해줄 Manager 클래스를 만들어주자 

일단 물체 그자체에 대한 ResouceBase 클래스를 만들어주자 

이 클래스에서는 리소스의 타입, 이름등과 같은 변수와 입출력과 같은 메소드를 가지고 있도록 하자.

ResouceBase.h

#pragma once

enum class ResourceType : uint8
{
	None = -1,
	Mesh,
	Shader,
	Texture,
	Material,
	Animation,

	End		//카운팅하기 위해
};

enum
{
	RESOURCE_TYPE_COUNT = static_cast<uint8>(ResourceType::End)
};

class ResourceBase : public enable_shared_from_this<ResourceBase>
{
public:
	ResourceBase(ResourceType type);
	virtual ~ResourceBase();

	ResourceType GetType() { return _type; }

	void SetName(const wstring& name) { _name = name; }
	const wstring& GetName() { return _name; }
	uint32 GetID() { return _id; }
 
protected:
	//파일 입출력
	virtual void Load(const wstring& path) { }
	virtual void Save(const wstring& path) { }

protected:
	ResourceType _type = ResourceType::None;
	wstring _name;
	wstring _path;
	uint32 _id = 0;
};

ResourceBase.cpp

#include "pch.h"
#include "ResourceBase.h"

ResourceBase::ResourceBase(ResourceType type)
	:_type(type)
{

}

ResourceBase::~ResourceBase()
{

}

 

그리고 첫번째로 전에 사용하던 Texture 클래스를 ResourceBase를 상속받게하여 사용하자 

Texture.h

#pragma once
#include "ResourceBase.h"

//SRV - 이미지를 어떻게 쓸것인가 - 텍스처
class Texture : public ResourceBase
{
	using Super = ResourceBase;
public:
	Texture(ComPtr<ID3D11Device> device);
	~Texture();

	ComPtr<ID3D11ShaderResourceView> GetComPtr() { return _shaderResourceView; }

	void Create(const wstring& path);

private:
	ComPtr<ID3D11Device> _device;
	ComPtr<ID3D11ShaderResourceView> _shaderResourceView;
};

 

그리고 이제 이러한 Resouce 를 관리해줄 ResourceManager를 만들어보자 

이 ResourceManager에는 각 Resource를 로드해줄 수 있는 부분과 각 리소스를 저장하는 map과 이를 리소스 타입마다

저장해줄 배열로 추가해주는 메서드, 리소스타입을 검사하고 리소스를 가져오는 부분까지 메서드로 만들어주자

 

ResourceManager.h

#pragma once
#include "ResourceBase.h"

class Mesh;
class Material;
class Texture;
class Shader;
class Animation;

class ResourceManager
{
public:
	ResourceManager(ComPtr<ID3D11Device> device);

	void Init();

	template<typename T>
	shared_ptr<T> Load(const wstring& key, const wstring& path);

	template<typename T>
	//리소스추가
	bool Add(const wstring& key, shared_ptr<T> object);

	template<typename T>
	shared_ptr<T> Get(const wstring& key);

	template<typename T>
	ResourceType GetResourceType();

private:
	void CreateDefaultTexture();
	void CreateDefaultMesh();
	void CreateDefaultShader();
	void CreateDefaultMaterial();
	void CreateDefaultAnimation();
private:
	ComPtr<ID3D11Device> _device;

	using KeyObjMap = map<wstring/*key*/, shared_ptr<ResourceBase>>;
	// [map,map~]
	array<KeyObjMap, RESOURCE_TYPE_COUNT> _resources;
};

template<typename T>
inline shared_ptr<T> ResourceManager::Load(const wstring& key, const wstring& path)
{
	auto objectType = GetResourceType<T>();
	KeyObjMap& keyObjMap = _resources[static_cast<uint8>(ResourceType)];

	auto findit = keyObjMap.find(key);
	if (findit != keyObjMap.end())
		return static_pointer_cast<T>(findit->second);

	shared_ptr<T> object = make_shared<T>();
	object->Load(path);
	keyObjMap[key] = object;

	return object;
}

template<typename T>
inline bool ResourceManager::Add(const wstring& key, shared_ptr<T> object)
{
	ResourceType resourceType = GetResourceType<T>();
	KeyObjMap& keyObjMap = _resources[static_cast<uint8>(resourceType)];

	auto findit = keyObjMap.find(key);
	if (findit != keyObjMap.end())
		return false;

	keyObjMap[key] = object;

	return true;
}

template<typename T>
inline shared_ptr<T> ResourceManager::Get(const wstring& key)
{
	ResourceType resourceType = GetResourceType<T>();
	KeyObjMap& keyObjMap = _resources[static_cast<uint8>(resourceType)];

	auto findit = keyObjMap.find(key);
	if (findit != keyObjMap.end())
		return static_pointer_cast<T>(findit->second);
}

template<typename T>
inline ResourceType ResourceManager::GetResourceType()
{
	if (std::is_same_v<T, Mesh>)
		return ResourceType::Mesh;
	if (std::is_same_v<T, Shader>)
		return ResourceType::Shader;
	if (std::is_same_v<T, Texture>)
		return ResourceType::Texture;
	if (std::is_same_v<T, Material>)
		return ResourceType::Material;
	if (std::is_same_v<T, Animation>)
		return ResourceType::Animation;


	assert(false);
	return ResourceType::None;
}

ResourceManager.cpp

#include "pch.h"
#include "ResourceManager.h"
#include "Texture.h"

ResourceManager::ResourceManager(ComPtr<ID3D11Device> device)
	:_device(device)
{

}

void ResourceManager::Init()
{

}

void ResourceManager::CreateDefaultTexture()
{
	{
		auto texture = make_shared<Texture>(_device);
		texture->SetName(L"Cat");
		texture->Create(L"cat.png");
		Add(texture->GetName(), texture);
	}
}

void ResourceManager::CreateDefaultMesh()
{

}

void ResourceManager::CreateDefaultShader()
{

}

void ResourceManager::CreateDefaultMaterial()
{

}

void ResourceManager::CreateDefaultAnimation()
{

}

 

이제 메인이되는 게임코드를 수정해주자 

pch.h

#define		RESOURCES	GAME->GetResourceManager()

Game.h

	shared_ptr<ResourceManager> GetResourceManager() { return _resource; }
    
    shared_ptr<ResourceManager> _resource;

Game.cpp

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

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

	_input = make_shared<InputManager>();
	_input->Init(hwnd);
	_time = make_shared<TimeManager>();
	_time->Init();
	_scene = make_shared<SceneManager>(_graphics);
	_scene->Init();
	_resource = make_shared<ResourceManager>(_graphics->GetDevice());
	_resource->Init();

	SCENE->LoadScene(L"Test");
}

 

3.RenderManager

이제 렌더링부분을 관리해줄 RenderManager 클래스와 구조체와 헬퍼함수를 관리해줄 RenderHelper 클래스를 만들어주자 

이때 CameraData와 TransformData는 버퍼를 통해 GPU와 고속복사를 통해 통신을 하기 위한 것이다.

그럼 이 버퍼 물체가 있을 때마다 계속 생성해야하는지 공용으로 써도 되는지를 생각해보자. DX12로 가게된다면 여러 

버퍼를 추가해서 사용할 수 있지만 지금은 공용으로 두고 사용해보도록 하자 

RenderManager의 주요기능에는 카메라와 Transform 버퍼로 고속복사가 이루어지도록 해주는 함수와 렌더링할

오브젝트를 파악하는 함수, 파악한 오브젝트를 렌더해주는 함수를 만들어주자

렌더링하는 부분과 파이프라인부분은 기존의 매쉬렌더러에 있던 부분을 가져오자

 

그리고 shared_from_this 나 make_shared 같은 경우 생성자에서 weak_ptr이 아직 채워지지 않은 경우가 있을 수 도 있기 때문에 따로 Init같은 함수를 만들어 주는것도 방법이 될 수 있다.

RenderManager.h

#pragma once
#include "RenderHelper.h"

class RenderManager
{
public:
	RenderManager(ComPtr<ID3D11Device> device, ComPtr<ID3D11DeviceContext> deviceContext);

	void Init();
	//렌더링하는 주체여서 그래픽객체 가지고있기
	void Update(shared_ptr<Graphics> graphics);

private:
	//고속복사
	void PushCameraData();
	void pushTransformData();

	void GatherRenderableObjects();
	void RenderObjects();

private:
	ComPtr<ID3D11Device> _device;
	ComPtr<ID3D11DeviceContext> _deviceContext;
	shared_ptr<Pipeline> _pipeline;
private:
	//Camera
	//쉐이더단계에서 더해줄 수 있는 인자같은 존재
	CameraData _cameraData;
	shared_ptr<ConstantBuffer<CameraData>> _cameraBuffer;

	//SRT scale, rotate translate
	TransformData _transformData;
	shared_ptr<ConstantBuffer<TransformData>> _transformBuffer;

	//Animation

private:
	//그려주는 파이프라인 상태
	//RS
	shared_ptr<RasterizerState> _rasterizerState;
	shared_ptr<SamplerState> _samplerState;
	shared_ptr<BlendState> _blendState;

	//그려야할 물체들
	vector<shared_ptr<GameObject>> _renderObjects;
};

RenderManager.cpp

#include "pch.h"
#include "RenderManager.h"
#include "Pipeline.h"
#include "MeshRenderer.h"
#include "Camera.h"
#include "Game.h"
#include "SceneManager.h"
#include "Scene.h"

RenderManager::RenderManager(ComPtr<ID3D11Device> device, ComPtr<ID3D11DeviceContext> deviceContext)
	:_device(device), _deviceContext(deviceContext)
{
}

void RenderManager::Init()
{
	_pipeline = make_shared<Pipeline>(_deviceContext);

	_cameraBuffer = make_shared<ConstantBuffer<CameraData>>(_device, _deviceContext);
	_cameraBuffer->Create();
	_transformBuffer = make_shared<ConstantBuffer<TransformData>>(_device, _deviceContext);
	_transformBuffer->Create();

	_rasterizerState = make_shared<RasterizerState>(_device);
	_rasterizerState->Create();
	_blendState = make_shared<BlendState>(_device);
	_blendState->Create();
	_samplerState = make_shared<SamplerState>(_device);
	_samplerState->Create();
}

void RenderManager::Update(shared_ptr<Graphics> graphics)
{
	graphics->RenderBegin();

	//카메라-가능한물체파악-물체렌더
	PushCameraData();
	GatherRenderableObjects();
	RenderObjects();

	graphics->RenderEnd();
}

void RenderManager::PushCameraData()
{
	_cameraData.matView = Camera::S_MatView;
	_cameraData.matProjection = Camera::S_MatProjection;
	_cameraBuffer->CopyData(_cameraData);
}

void RenderManager::pushTransformData()
{
	_transformBuffer->CopyData(_transformData);
}

void RenderManager::GatherRenderableObjects()
{
	_renderObjects.clear();

	auto& gameObjects = SCENE->GetActiveScene()->GetGameObjects();
	for (const shared_ptr<GameObject>& gameObject : gameObjects)
	{
		shared_ptr<MeshRenderer> meshRenderer = gameObject->GetMeshRenderer();
		if (meshRenderer)
			_renderObjects.push_back(gameObject);
	}
}

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

		PipelineInfo info;
		info.inputLayout = meshRenderer->_inputLayout;
		info.vertexShader = meshRenderer->_vertexShader;
		info.pixelShader = meshRenderer->_pixelShader;
		info.rasterizerState = _rasterizerState;
		info.blendState = _blendState;
		_pipeline->UpdatePipeline(info);

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

		_pipeline->SetConstantBuffer(0, SS_VertexShader, _cameraBuffer);
		_pipeline->SetConstantBuffer(1, SS_VertexShader, _transformBuffer);

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

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

 

이렇게하고 MeshRenderer 부분을 정리해주자

MeshRenderer.h

#pragma once
#include "Component.h"
class MeshRenderer : public Component
{
	using Super = Component;
public:
	MeshRenderer(ComPtr<ID3D11Device> device, ComPtr<ID3D11DeviceContext> deviceContext);
	virtual ~MeshRenderer();

	virtual void Update() override;
private:
	ComPtr<ID3D11Device> _device;

	//이렇게하면 랜더매니저에서 meshRenderer의 변수 protected처럼 쓸수있음
	friend class RenderManager;
	//Mesh
	shared_ptr<Geometry<VertexTextureData>> _geometry;
	shared_ptr<VertexBuffer> _vertexBuffer;
	//인덱스버퍼  이거도 Geometry에 포함
	shared_ptr<IndexBuffer> _indexBuffer;

	//Material
	shared_ptr<InputLayout> _inputLayout;
	//VS
	shared_ptr<VertexShader> _vertexShader;
	//PS
	shared_ptr<PixelShader> _pixelShader;
	//SRV - 이미지를 어떻게 쓸것인가 - 텍스처
	shared_ptr<Texture> _texture1;



private:

};

MeshRenderer.cpp

#include "pch.h"
#include "MeshRenderer.h"
#include "Camera.h"
#include "Game.h"
#include "Pipeline.h"

MeshRenderer::MeshRenderer(ComPtr<ID3D11Device> device, ComPtr<ID3D11DeviceContext> deviceContext)
	: Super(ComponentType::MeshRenderer), _device(device)
{
	//정점정보 - 사각형 만들기
	_geometry = make_shared<Geometry<VertexTextureData>>();
	GeometryHelper::CreateRectangle(_geometry);

	//정점버퍼
	_vertexBuffer = make_shared<VertexBuffer>(device);
	_vertexBuffer->Create(_geometry->GetVertices());

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

	_vertexShader = make_shared<VertexShader>(device);
	_vertexShader->Create(L"Default.hlsl", "VS", "vs_5_0");

	//인풋레이아웃
	/// <summary>
	/// 입력이 어떻게 이뤄져있는지
	/// </summary>
	_inputLayout = make_shared<InputLayout>(device);
	_inputLayout->Create(VertexTextureData::descs, _vertexShader->GetBlob());

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

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

}

MeshRenderer::~MeshRenderer()
{

}

void MeshRenderer::Update()
{

}

 

이에 맞게 게임코드도 고쳐주자 이제 파이프라인부분은 RenderManager에서 관리하게 될 것이니 게임 클래스에서는 지워주자 

Game.h

#pragma once
#include "Graphics.h"
#include "GameObject.h"
#include "Pipeline.h"

class SceneManager;
class TimeManager;
class InputManager;
class ResourceManager;
class RenderManager;

class Game
{
public:
	Game();
	~Game();
public:
	void Init(HWND hwnd);		//윈도우 핸들받아줌
	void Update();
	void Render();

	shared_ptr<InputManager> GetInputManager() { return _input; }
	shared_ptr<TimeManager> GetTimeManager() { return _time; }
	shared_ptr<SceneManager> GetSceneManager() { return _scene; }
	shared_ptr<ResourceManager> GetResourceManager() { return _resource; }
	shared_ptr<RenderManager> GetRenderManager() { return _render; }

private:
	HWND _hwnd;

	shared_ptr<Graphics> _graphics;

private:
	shared_ptr<InputManager> _input;
	shared_ptr<TimeManager> _time;
	shared_ptr<SceneManager> _scene;
	shared_ptr<ResourceManager> _resource;
	shared_ptr<RenderManager> _render;
}; 

extern unique_ptr<Game> GGame;

 

Game.cpp

#include "pch.h"
#include "Game.h"
#include "Camera.h"
#include "MeshRenderer.h"
#include "SceneManager.h"
#include "InputManager.h"
#include "TimeManager.h"
#include "ResourceManager.h"
#include "RenderManager.h"

unique_ptr<Game> GGame = make_unique<Game>();
Game::Game()
{
	
}

Game::~Game()
{
}

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

	_graphics = make_shared<Graphics>(hwnd);

	_input = make_shared<InputManager>();
	_input->Init(hwnd);
	_time = make_shared<TimeManager>();
	_time->Init();
	_scene = make_shared<SceneManager>(_graphics);
	_scene->Init();
	_resource = make_shared<ResourceManager>(_graphics->GetDevice());
	_resource->Init();
	_render = make_shared<RenderManager>(_graphics->GetDevice(), _graphics->GetDeviceContext());
	_render->Init();

	SCENE->LoadScene(L"Test");
}

void Game::Update()
{
	TIME->Update();
	INPUT->Update();
	SCENE->Update();
}

void Game::Render()
{
	RENDER->Update(_graphics);
}

 

이렇게 해주면 이제 정상적으로 작동하는 모습을 볼 수 있다.

 

오늘부터는 유니티 엔진을 모작한다는 느낌으로 어떤 하나의 엔진을 천천히 만들어간다는 느낌으로 기존 코드를 정리하고 새로운 클래스를 추가해보자 

처음으로 지금은 게임오브젝트 코드자체에 렌더링관련 부분이 구현되어있는데 나중에 완성된 엔진에서 게임오브젝트는 보여주는 기능뿐만 아니라 다양한 기능을 가지게 된다.

그렇기 때문에 렌더링부분을  게임오브젝트 자체 코드에서 다루는 것이 아닌 그려지는 역활을 하게 될 Component로 옮겨줄 것이다.

1.Component  코드 정리

처음으로 할 것은 Component 코드에 유니티 엔진의 방식대로 Awake Start Update등의 기본 함수를 추가해주고

이를 상속 받는 모든 클래스에서 이 함수를 사용할 수 있도록 하자.

또한 오브젝트당 하나씩만 추가할 수 있는 즉 고정적인 개수의 Component를 Enum Class로 선언해주고 이 개수를 enum으로 저장해주자 

 Monobehaviour라는 클래스를 추가해 Script가 생성될 때는 이 클래스를 상속받아서 생성되도록 해주자

 

Component.h

#pragma once

class GameObject;
class Transform;

enum class ComponentType :uint8
{
	Transform,		//고정개수
	MeshRenderer,
	Camera,
	Animator,
	//Todo..
	Script,		//동적개수

	End,
};

enum
{
	//개수가 고정인 컴포넌트 개수
	FIXED_COMPONENT_COUNT = static_cast<uint8>(ComponentType::End)-1;
};

class Component
{
public:
	Component(ComponentType type);
	virtual ~Component();

	//생성될때 호출
	virtual void Awake() {}
	virtual void Start() {}
	virtual void Update() {}
	virtual void LateUpdate(){}
	virtual void FixedUpdate(){}

	shared_ptr<GameObject> GetGameObject();
	shared_ptr<Transform> GetTransform();

private:
	friend class GameObject;
	void SetGameObject(shared_ptr<GameObject> gameObject) { _gameObject = gameObject; }


protected:
	ComponentType _type;
	weak_ptr<GameObject> _gameObject;
};

 

Component.cpp

#include "pch.h"
#include "Component.h"

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

Component::~Component()
{
}


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

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

 

2.GameObject 코드 정리

다음엔 GameObject에도 Awake Update 등의 기본함수를 추가해준다.

 

그리고 중요한 부분을 요약해보자면 

1.GameObject에서 고정적인 개수의 컴포넌트는 std::array 를 통해 관리하여 만약 배열의 길이를 넘어서는 값을 호출하면 크러쉬가 발생하게 하여 좀 더 안전하게 관리할 수 있도록 하자.

2.개수가 여러개 추가될 수 있는 Script는 배열의 크기가 동적으로 바뀔 수 있는 Vector를 통해 관리해주자

3.각 기본 함수에서 자신이 가지고 Component의 기본함수가 호출 되도록 구현하자. 

즉 GameObject의 Update가 수행되면 각 Component의 Update도 수행되는 식으로 말이다.

4.GameObject에서 Transform과 같은 가장 핵심적인 부품을 가지고 올 수 있고 없는 경우에 생성하는 헬퍼함수를 만들어주자.

5.GameObject에서 Component가 추가될 수 있도록 함수를 만들어주자 이때 스마트포인터를 넘겨주어야하는 데 자기자신의 값인 this 포인터를 넘겨주는 것이 아니라 shared_from_this 를 사용하여 메모리 오염을 방지하도록 하자.

이 때 Shared_from_this 방식은 weak_ptr을 가지고 있다가 자체적으로 생성시에 자신의 스마트 포인터를 채워주는 방식으로 작동한다.

-생성자에서 사용 시에는 크러쉬가 발생할 수 있다.

 

 

GameObject.h

#pragma once

class MonoBehaviour;

class GameObject : public enable_shared_from_this<GameObject>
{
public:
	GameObject(ComPtr<ID3D11Device> device, ComPtr<ID3D11DeviceContext> deviceContext);
	~GameObject();

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

	//헬퍼함수
	shared_ptr<Component> GetFixedComponent(ComponentType type);
	shared_ptr<Transform> GetTransform();

	shared_ptr<Transform> GetOrAddTransform();
	void AddComponent(shared_ptr<Component> component);

	//..

	void Render(shared_ptr<Pipeline> pipeline);
private:
	ComPtr<ID3D11Device> _device;

	//기하학적 도형 - cpu
	//vector<Vertex> _vertices;
	//vector<uint32> _indices;

	shared_ptr<Geometry<VertexTextureData>> _geometry;

	shared_ptr<VertexBuffer> _vertexBuffer;

	//인덱스버퍼 - 이거도 Geometry에 포함
	shared_ptr<IndexBuffer> _indexBuffer;
	shared_ptr<InputLayout> _inputLayout;

	//VS
	shared_ptr<VertexShader> _vertexShader;

	//RS
	shared_ptr<RasterizerState> _rasterizerState;

	//PS
	shared_ptr<PixelShader> _pixelShader;

	//SRV - 이미지를 어떻게 쓸것인가 - 텍스처
	shared_ptr<Texture> _texture1;

	shared_ptr<SamplerState> _samplerState;
	shared_ptr<BlendState> _blendState;
private:
	//SRT scale, rotate translate
	//쉐이더단계에서 더해줄 수 있는 인자같은 존재
	TransformData _transformData;
	shared_ptr<ConstantBuffer<TransformData>> _constantBuffer;


protected:
	//개수가 고정인 
	std::array<shared_ptr<Component>, FIXED_COMPONENT_COUNT> _components;
	//개수가 동적인
	vector<shared_ptr<MonoBehaviour>> _scripts;
};

 

GameObject.cpp

#include "pch.h"
#include "GameObject.h"
#include "MonoBehaviour.h"
#include "Transform.h"

GameObject::GameObject(ComPtr<ID3D11Device> device, ComPtr<ID3D11DeviceContext> deviceContext)
	:_device(device)
{
	//정점정보 - 사각형 만들기
	_geometry = make_shared<Geometry<VertexTextureData>>();
	GeometryHelper::CreateRectangle(_geometry);

	//정점버퍼
	_vertexBuffer = make_shared<VertexBuffer>(device);
	_vertexBuffer->Create(_geometry->GetVertices());

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

	_vertexShader = make_shared<VertexShader>(device);
	_vertexShader->Create(L"Default.hlsl", "VS", "vs_5_0");

	//인풋레이아웃
	/// <summary>
	/// 입력이 어떻게 이뤄져있는지
	/// </summary>
	_inputLayout = make_shared<InputLayout>(device);
	_inputLayout->Create(VertexTextureData::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();

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

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

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

}

GameObject::~GameObject()
{
}

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

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

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

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

	return GetTransform();
}

void GameObject::AddComponent(shared_ptr<Component> component)
{
	component->SetGameObject(shared_from_this());
	//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));
	}
}

void GameObject::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, _constantBuffer);
	pipeline->SetTexture(0, SS_PixelShader, _texture1);
	pipeline->SetSamplerState(0, SS_PixelShader, _samplerState);
	pipeline->DrawIndexed(_geometry->GetIndexCount(), 0, 0);
}

 

 

이렇게 해주게 되면 이전처럼 화면이 나오게 된다.

 

3.카메라 추가

이제 카메라 컴포넌트를 추가해보자

 

카메라의 가지게 될 주요기능을 적어보자

1.카메라가 원근 투영을 하는지 직교투영을 하는지 알고 있어야한다. 

2.원근투영인지 직교투영인지에 따라 Update할 때 Matrix 또한 Update해주어야한다.

이때 카메라 변환 행렬을 계산할 때 XMMatrixLookAtLH 함수를 사용하는 방법이 있고 

월드 행렬의 역행렬을 구하는 방법도 있다. 이게 가능한 이유는 원래는 카메라를 기준으로 하는 좌표에서 월드를 기준으로

하는 좌표로 넘어가는 것이 월드 변환 행렬이었는데 거꾸로 카메라를 기준으로 하고 싶으면 역행렬을 곱해줘도 된다는 것이다.

일단은 카메라가 한개라고 가정하고 static을 통해 카메라와 관련된 행렬을 선언해주자

 

Camera.h

#pragma once
#include "Component.h"

enum class ProjectType
{
	Persperctive,		//원근투영
	Orthographic,		//직교투영
};

class Camera : public Component
{
	using Super = Component;
public:
	Camera();
	virtual ~Camera();

	virtual void Update() override;

	void SetProjectionType(ProjectType type) { _type = type; }
	ProjectType GetProjectionType() { return _type; }

	void UpdateMatrix();

private:
	ProjectType _type = ProjectType::Orthographic;

public:
	//일단 카메라가 한개라고 가정
	static Matrix S_MatView;
	static Matrix S_MatProjection;
};

 

Camera.cpp

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

	//방법2 - 월드의 역행렬
	//S_MatView = GetTransform()->GetWorldMatrix().Invert();

	if (_type == ProjectType::Persperctive)
	{
		//필드오브 뷰,비율,니어,파
		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);
	}
}

 

카메라 컴포넌트가 추가되었으니 이에 맞게 기본 쉐이더 코드도 수정해주자 이때 카메라는 하나이고 물체는 여러개가 

있을 수 있으니 업데이트되는 버퍼를 따로 지정해주자

//카메라와 물체 따로 -> 카메라는 하나 물체는 계속 바꾸면서 사용
cbuffer CameraData : register(b0)
{
    row_major matrix matView;
    row_major matrix matProjection;
}

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

 

이에 맞게 카메라 데이터와 TransformData를 받아주는 구조체도 수정해주자

Struct.h

#pragma once
#include "Types.h"

//
//struct Vertex
//{
//	Vec3 position;		//12바이트 0부터시작
//	//Color color;		//12부터시작
//	Vec2 uv;
//};

struct CameraData
{
	Matrix matView = Matrix::Identity;
	Matrix matProjection= Matrix::Identity;
};

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

 

GameObject 에서도 동일하게 수정해주자 

 

GameObject.h

#pragma once

class MonoBehaviour;
class Transform;
class Camera;

class GameObject : public enable_shared_from_this<GameObject>
{
public:
	GameObject(ComPtr<ID3D11Device> device, ComPtr<ID3D11DeviceContext> deviceContext);
	~GameObject();

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

	//헬퍼함수
	shared_ptr<Component> GetFixedComponent(ComponentType type);
	shared_ptr<Transform> GetTransform();
	shared_ptr<Camera> GetCamera();

	shared_ptr<Transform> GetOrAddTransform();
	void AddComponent(shared_ptr<Component> component);

	//..

	void Render(shared_ptr<Pipeline> pipeline);
private:
	ComPtr<ID3D11Device> _device;

	//기하학적 도형 - cpu
	//vector<Vertex> _vertices;
	//vector<uint32> _indices;

	shared_ptr<Geometry<VertexTextureData>> _geometry;

	shared_ptr<VertexBuffer> _vertexBuffer;

	//인덱스버퍼 - 이거도 Geometry에 포함
	shared_ptr<IndexBuffer> _indexBuffer;
	shared_ptr<InputLayout> _inputLayout;

	//VS
	shared_ptr<VertexShader> _vertexShader;

	//RS
	shared_ptr<RasterizerState> _rasterizerState;

	//PS
	shared_ptr<PixelShader> _pixelShader;

	//SRV - 이미지를 어떻게 쓸것인가 - 텍스처
	shared_ptr<Texture> _texture1;

	shared_ptr<SamplerState> _samplerState;
	shared_ptr<BlendState> _blendState;
private:
	//Camera
	//쉐이더단계에서 더해줄 수 있는 인자같은 존재
	CameraData _cameraData;
	shared_ptr<ConstantBuffer<CameraData>> _cameraBuffer;

	//SRT scale, rotate translate
	TransformData _transformData;
	shared_ptr<ConstantBuffer<TransformData>> _transformBuffer;

protected:
	//개수가 고정인 
	std::array<shared_ptr<Component>, FIXED_COMPONENT_COUNT> _components;
	//개수가 동적인
	vector<shared_ptr<MonoBehaviour>> _scripts;
};

 

GameObject.cpp

#include "pch.h"
#include "GameObject.h"
#include "MonoBehaviour.h"
#include "Transform.h"
#include "Camera.h"

GameObject::GameObject(ComPtr<ID3D11Device> device, ComPtr<ID3D11DeviceContext> deviceContext)
	:_device(device)
{
	//정점정보 - 사각형 만들기
	_geometry = make_shared<Geometry<VertexTextureData>>();
	GeometryHelper::CreateRectangle(_geometry);

	//정점버퍼
	_vertexBuffer = make_shared<VertexBuffer>(device);
	_vertexBuffer->Create(_geometry->GetVertices());

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

	_vertexShader = make_shared<VertexShader>(device);
	_vertexShader->Create(L"Default.hlsl", "VS", "vs_5_0");

	//인풋레이아웃
	/// <summary>
	/// 입력이 어떻게 이뤄져있는지
	/// </summary>
	_inputLayout = make_shared<InputLayout>(device);
	_inputLayout->Create(VertexTextureData::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();

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

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

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

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

}

GameObject::~GameObject()
{
}

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

	//TMP
	if (GetCamera())
		return;

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

	_transformData.matWorld = GetOrAddTransform()->GetWorldMatrix();
	_transformBuffer->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();
	}
}

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

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

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

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

	return GetTransform();
}

void GameObject::AddComponent(shared_ptr<Component> component)
{
	component->SetGameObject(shared_from_this());
	//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));
	}
}

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

이렇게 해주게 되면 아직은 아무것도 안보이게 된다. 왜냐하면 지금 설정해둔 카메라의 윈도우 크기가 너무 크게 설정되어 있어서 물체가 거의 안보이는 것이다.
물체가 보이게 되려면 물체자체의 크기를 키우거나 윈도우사이즈를 작게 조정하면 된다.

지금은 Camera 코드에서 카메라 뷰포트의 크기를 조절해보자

Camera.cpp


	if (_type == ProjectType::Persperctive)
	{
		//필드오브 뷰,비율,니어,파
		S_MatProjection = ::XMMatrixPerspectiveFovLH(XM_PI / 4.f, 800.f / 600.f, 1.f, 100.f);
	}
	else
	{
		//화면의 크기, 니어,파
		S_MatProjection = ::XMMatrixOrthographicLH(8, 6, 0.f, 1.f);
	}

 

이렇게 해주게되면 작지만 고양이가 보이게 된다.

 

이제 GameObject에서 가지고 있던 렌더링부분을 따로 빼주도록 하자

MeshRenderer라는 클래스로 따로 만들어 줄텐데 이 컴포넌트를 가지고있는 GameObject만이 렌더링 파이프라인을 거쳐

보일 수 있도록하자.

GameObjct의 렌더부분을 모두 MeshRenderer로 가져온 뒤 코드를 정리해주자

지금은 렌더링부분을 모두가 넣어줄 것이지만 나중에 물체에 종속적인 부분과 공통적인 부분을 나눠볼 것이다. 

MeshRenderer.h

#pragma once
#include "Component.h"
class MeshRenderer : public Component
{
	using Super = Component;
public:
	MeshRenderer(ComPtr<ID3D11Device> device, ComPtr<ID3D11DeviceContext> deviceContext);
	virtual ~MeshRenderer();

	virtual void Update() override;
	void Render(shared_ptr<Pipeline> pipeline);
private:
	ComPtr<ID3D11Device> _device;

	shared_ptr<Geometry<VertexTextureData>> _geometry;

	shared_ptr<VertexBuffer> _vertexBuffer;

	//인덱스버퍼 - 이거도 Geometry에 포함
	shared_ptr<IndexBuffer> _indexBuffer;
	shared_ptr<InputLayout> _inputLayout;

	//VS
	shared_ptr<VertexShader> _vertexShader;

	//RS
	shared_ptr<RasterizerState> _rasterizerState;

	//PS
	shared_ptr<PixelShader> _pixelShader;

	//SRV - 이미지를 어떻게 쓸것인가 - 텍스처
	shared_ptr<Texture> _texture1;

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

private:
	//Camera
	//쉐이더단계에서 더해줄 수 있는 인자같은 존재
	CameraData _cameraData;
	shared_ptr<ConstantBuffer<CameraData>> _cameraBuffer;

	//SRT scale, rotate translate
	TransformData _transformData;
	shared_ptr<ConstantBuffer<TransformData>> _transformBuffer;
};

 

MeshRenderer.cpp

#include "pch.h"
#include "MeshRenderer.h"
#include "Camera.h"

MeshRenderer::MeshRenderer(ComPtr<ID3D11Device> device, ComPtr<ID3D11DeviceContext> deviceContext) 
	: Super(ComponentType::MeshRenderer), _device(device)
{
	//정점정보 - 사각형 만들기
	_geometry = make_shared<Geometry<VertexTextureData>>();
	GeometryHelper::CreateRectangle(_geometry);

	//정점버퍼
	_vertexBuffer = make_shared<VertexBuffer>(device);
	_vertexBuffer->Create(_geometry->GetVertices());

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

	_vertexShader = make_shared<VertexShader>(device);
	_vertexShader->Create(L"Default.hlsl", "VS", "vs_5_0");

	//인풋레이아웃
	/// <summary>
	/// 입력이 어떻게 이뤄져있는지
	/// </summary>
	_inputLayout = make_shared<InputLayout>(device);
	_inputLayout->Create(VertexTextureData::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();

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

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

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

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

MeshRenderer::~MeshRenderer()
{

}

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 = GetTransform()->GetWorldMatrix();
	_transformBuffer->CopyData(_transformData);


	//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에서 MeshRenderer를 가져올 수 있게 헬퍼함수를 추가해주자

GameObject.cpp

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

 

마지막으로 정상적으로 작동할 수 있도록 Game 즉 메인 코드에서 cat부분에 Renderer component를 추가해주고 Render부분 코드도 수정해주자 

Game.cpp

#include "pch.h"
#include "Game.h"
#include "Camera.h"
#include "MeshRenderer.h"

Game::Game()
{
}

Game::~Game()
{
}

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

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

	//GO
	_cat = make_shared<GameObject>(_graphics->GetDevice(), _graphics->GetDeviceContext());
	{
		_cat->GetOrAddTransform();
		_cat->AddComponent(make_shared<MeshRenderer>(_graphics->GetDevice(), _graphics->GetDeviceContext()));
		//_cat->GetTransform()->GetScale(Vec3(100.f,100.f,100.f));
		//...
	}
	_camera = make_shared<GameObject>(_graphics->GetDevice(), _graphics->GetDeviceContext());
	{
		_camera->GetOrAddTransform();
		_camera->AddComponent(make_shared<Camera>());
	}
}

void Game::Update()
{
	_cat->Update();



	_camera->Update();
}

void Game::Render()
{
	_graphics->RenderBegin();			//초기화

	auto _deviceContext = _graphics->GetDeviceContext();
	// IA - VS - RS - PS -OM
	//TODO : 그리기
	{
		//TMP
		_cat->GetMeshRenderer()->Render(_pipeline);
	}

	_graphics->RenderEnd();			//제출
}

 

이렇게 하면 고양이가 출력된다.

+ Recent posts