33 새 프로젝트 시작

gksrudtlr
|2025. 1. 16. 00:43

새 프로젝트 설정

새 솔루션 만들기

  • 이전에 했던 프로젝트는 두고 새로운 프로젝트로 빈 프로젝트를 만들어준다
  • 그 후 프로젝트 하위에 모든 것들을 지워주고새로 만들어 줄것이다
  • 새로 프로젝트 만들기로 lib 정적 라이브러리 파일을 Engine이라는 이름으로 만들고, 이는 Engine에 관련된 내용들을 가진 프로젝트가 될것이다
  • 또 새 프로젝트 만들기로 Windows 데스크톱 마법사를 Client라는 이름으로 만들고, 이는 클라 관련된 내용들을 가진 프로젝트가 될 것이다
  • 그리고 Client에 있는 모든 것들을 다시 만들것이므로 지워준다
  • 보면 이렇게 이런식으로 두 프로젝트가 공존하게 될 것이다

폴더 설정

  • Client, Engine 폴더는 자동 생성된 폴더들이고 다른 폴더들을 만들어주어 각각에 맞게 관리하도록 할 것이다
  • Binaries는 우리가 만든 최종적인 결과물이 저장될 폴더이다
  • Intermediate는 우리가 빌드를 할때 생기는 결과물들을 이 폴더에 모아둘 것이다
  • 나머지 폴더들도 라이브러리, 리소스, 쉐이더를 각각 저장하는 폴더가 될것이다

Libaries

  • 라이브러리 폴더는 저번에 사용했던 것처럼 Include, Lib 파일들을 나눠 가지고 있을 것이므로 두개의 폴더를 또 만들어준다
  • Include, Lib 폴더안에 각각 DirectXTex, Engine, FX11폴더를 만들어준다
  • Lib/Engine 는 Engine 프로젝트의 빌드한 결과물이 저장될 것이고, DirectXTex는 텍스처를 로딩하는 라이브러리 이므로 저번에 사용했던 것을 가져와 DirectTex안에 넣어준다
  • FX11은 Shader를 편하게 사용하게 해주는 라이브러리로 인터넷에서 직접 다운받아 빌드를 해도 되지만 공유해준 폴더에서 가져와 이 FX11폴더에 넣어준다
  • Include는 헤더들을 넣어주는 파일이므로 DirectX, FX11폴더에 공유해준 폴더에서 헤더들을 가져와 넣어주고, Engine에서는 우리가 Engine 프로젝트에서 추가하는 헤더들이 저장될 것이다(추가 헤더들을 관리하는건 어떻게 하든 자기 마음)

프로젝트 설정

  • 좀전에 만든 폴더들을 용도에 맞게 사용하기 위해 설정을 해줄것이다

Engine

  • Engine프로젝트의 속성-일반-출력 디렉토리는 라이브러리-Lib에 Engine을, 중간 디렉터리는 Intermediate 폴더를 연결해준다
    )))))
  • C/C++에 추가포함 디렉터리에 Include 폴더, 라이브러리에 추가 라이브러리 디렉터리에 Lib 폴더를 추가해준다

Client

  • Client 프로젝트에 속성에 현재 C/C++ 속성이 없는데 이는 프로젝트에 C/C++ 파일이 하나도 없기 때문이므로 미리컴파일된 헤더 클래스를 하나 만들어주면 뜨는것을 볼 수 있다
  • C/C++ 속성에 추가포함 디렉터리에 라이브러리 안에 Include 폴더와 Engine 프로젝트 폴더를 추가하여 Client에서 사용할 수 있게 해준다
  • 이번엔 링커에 추가 라이브러리 디렉터리에 Lib 파일을 추가해준다
  • 이번엔 미리 컴파일된 헤더속성을 변경해준다(Rider에서는 이상하게 cpp에 use가 create로 바뀌지 않아 visual studio로 열어 바꿔줘야함)

빌드 이벤트

  • Engine 프로젝트에 있는 .h가 모두 라이브러리의 Include->Engine에 저장되게 하고싶으니 Engine 프로젝트 속성에 빌드 이벤트에서 이를 할 수 있다
  • xcopy는 복사를 하겠다는 뜻이고, /Y는 오버라이트(이미 있는 파일에 덮어쓰기)를 하겠다는 뜻이고, Engine에 있는 *.h 는 모든 h를 뒤에 있는 경로로 복사해주겠다는 것이다
  • 이렇게 되면 Client에 추가포함 디렉터리에 Engine을 연결해도 되지만 라이브러리에 Include->Engine을 연결해줘도 된다

Engine 만들기

  • Engine은 저번 프로젝트에서 만든것을 대부분 복붙해서 사용할 것이다
  • 먼저 저번 프로젝트에서 만들었던 것처럼 필터를 만들어준다
  • pch.h ... #define WIN32_LEAN_AND_MEAN // 거의 사용되지 않는 내용을 Windows 헤더에서 제외합니다. ...
  • framework.h헤더는 사실상 위의 코드때문에 존재하므로 pch.h로 옮기고 삭제해줘도 된다
  • 먼저 Define.h는 아직까지는 별거 없지만 싱글톤을 사용할때 사용하는 메크로이다
  • EnginePCH.h는 엔진과 관련된 필수 헤더들이 모여있는 헤더로 저번 프로젝트에서는 pch.h에 있던 것들로 이름을 바꾼것으로 바뀐점은 FX11d3dx11effect.h가 추가됐는데 이것이 라이브러리에서 추가한 그 헤더이다
  • Type.h는 Type과 관련된 애들이 모여있는 헤더로 저번 프로젝트에 있는 그 .h이다
  • 이 3개를 먼저 가져와준다

Utils

  • 저번 프로젝트때 다 봤던 파일들이고 Untils라는 파일이 새로 추가됐는데 이는 지금 당장은 중요하지 않지만 온갖 잡동사니들을 필요하겠다 생각이 들면 작업을 할 파일이다

pch.h

  • pch.h에 모든 내용은 EngeinPch.h에 있으므로 다 지우고 EnginePch.h만 include해주면 된다
  • 이렇게 빼서 나눈 이유는 Client에서도 EnginePch.h 파일을 긁어 사용할 예정이기 때문이다

Manager

  • 위에 추가된 파일들의 .h에는 모두 싱글톤을 사용할 것이므로 DECLARE_SINGLE()메크로가 추가되어있다

TimeMGR, InputMGR

  • 지난번 사용하던 파일과 동일

ResourceMGR

  • 저번 프로젝트에서 마지막에 만들었던 리소스를 관리하는 메니저로 여러 리소스들의 타입들을 편리하게 관리할 수 있는 MGR이다

Graphics

  • 이 클래스는 Device, DeviceContext를 통해 GPU에 일을 시키는 등을 하면서 온갖 리소스들을 만들고 파이프라인에 연결시켜주는 용도로 사용할 클래스이다
  • 또한 SwapChain을 이용해 더블 버퍼링을 이용해 화면에 그려주는 것까지 하는 클래스이다

#define 메크로

  • 이들을 싱글톤으로 만들어 이제는 Device나 DeviceContext등을 넘겨주고 받아서 일을 처리하는게 아닌 직접 불러서 사용할 수있게 하였는데 이들을 메크로로 이름을 지정해 부를 수 있게 만들어 줬다
  • EnginePch.h #define CHECK(p) assert(SUCCEEDED(p)) #define GAME GET_SINGLE(Game) #define GRAPHICS GET_SINGLE(Graphics) #define DEVICE GRAPHICS->GetDevice() #define DC GRAPHICS->GetDeviceContext() #define INPUT GET_SINGLE(InputManager) #define TIME GET_SINGLE(TimeManager) #define DT TIME->GetDeltaTime() #define RESOURCES GET_SINGLE(ResourceManager)`

Game

  • 이 클래스는 우리가 저번에 만들었던 프로젝트의 Main 함수가 있는 창을 보면 윈도우 창 정보를 등록하고, 윈도우 창을 생성한 뒤 메인루프를 돌면서 PeekMessage, Update등을 하는 것을 클래스 형태로 빼놓은 것이다
  • 즉 메인 윈도우를 런치하는 부분을 엔진에 Game이라는 클래스로 만든것이다
  • 여기서 실행 단위라는 개념이 등장하는데 실제로 클라이언트를 하나 만들고 다른 실습을 할때 그 클라를 지우지 않고 실행 단위라는 개념을 도입해 편리하게 꽂는 기능을 도입했다
  • 이는 나중에 실습때 자세히 설명할 예정이며 00.Engine폴더에 추가했다

Graphics

ConstantBuffer

  • 상수 버퍼는 GPU에 데이터를 넘겨주는 버퍼로 Device->CreateBuffer()를 통해 만들어준 뒤 상수 버퍼에 값을 복사해 주는 역할을 하는 클래스이다 3D11_MAPPED_SUBRESOURCE subResource;
    ZeroMemory(&subResource, sizeof(subResource));
    이 구조체는 GPU 메모리에 메핑된 데이터에 접근하기 위한 정보를 가진 데이터로, 구조체의 메모리를 0으로 초기화 한것이다::memcpy(subResource.pData, &data, sizeof(data));
    를 통해 CPU의 데이터를 GPU로 복사해준다
  • DC->Unmap(_constantBuffer.Get(), 0);
    Unmap 함수를 통해 GPU 메모리와 CPU 메모리의 매핑을 해제하여 GPU에서 해당 상수버퍼를 사용할 수 있게 된다
  • DC->Map(_constantBuffer.Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &subResource);
    DeviceContext의 Map은 GPU 메모리의 특정 영역을 CPU에 매핑하거여 데이터를 읽거나 쓸 수 있는 함수이다
    상수 버퍼를 가져온 뒤, 버퍼 부분을 선택하는데 0은 전체 버퍼를 대상으로 한다는 의미를 가지며, D3D!!_MAP_WRITE_DISCARD는 버퍼에 데이터를 쓸 때 기존 데이터를 무시하고 새로 쓴다는 것을 나타낸 것이며, 특수 플래그를 사용할지 정한 뒤,위에서 만든 구조체의 주소를 넘겨 Map 함수에서 반환되는 매핑된 데이터에 대한 정보를 저장한다

Geometry

  • 어떤 기하학적 모형을 나타내기 위해 단위를 클래스로 만든 것이다
  • 이 클래스는 IndexBuffer, VertexBuffer를 배열로 가지고 있으며, 특정 모형을 그려줄 때 Index, VertexBuffer들을 배열에 저장하고 내보내는 역할로 이를 통해 점을 찍고 그 점을 연결해 모형을 나타낼 수 있다

VertexBuffer, IndexBuffer

  • 이 클래스는 IndexBuffer와 VertexBuffer를 만들어주는 클래스이다
  • VertexBuffer는 Geometry에서 도형을 표현한 것을 GPU에 복사해주는 역할을 한다
  • IndexBuffer는 VertexBuffer와 비슷하지만 Index와 관련된 것들을 GPU로 복사하는 역할을 한다

VertexData

  • 정점들어 어떤식으로 표현되는지를 나타내는 방법을 구조체로 가지고 있다
  • 이 정보들은 Shader와 밀접한 관계가 있으며 이 데이터들을 Buffer들을 통해 GPU로 복사되는 값들이다

Shader

  • Graphics에 Shader라는 폴더를 만들어 줄 것이다
  • 이제는 지금까지 만들었던 Shader와 다른 방식으로 진행할 것이다
  • 이전에 만들었던 쉐이더는 쉐이더 리소스를 로드한 다음 Vertex Shader나 Pixel Shader라는 클래스로 만들어 관리하고, 렌더링파이프라인 단계에서 실행되는 코드들이였고, 거기에 인자를 넣기 위해 Material이라는 개념을 통해 조립을 했었다
  • 하지만 이 방법은 Vertex 데이터에 InputLayout을 묘사한 후 연동시키고, 세부적인 상수버퍼를 통해 인자를 넘겨서 작업을 했는데 이는 Shader가 많아지면 짝을 맞춰주는 것이 엄청 힘들것이다
  • 그래서 우리는 이를 직접 관리하는 것이 아닌 effect11를 사용해서 할 것이다
  • effect11을 사용하기 전 이와 랩핑된 파일들을 사진처럼 가져와 줄것이다
  • 이들은 우리가 나중에 사용하면서 자세히 공부할 것이다이를 사용하기 위해 미리 컴파일된 헤더에 FX11에 Effects11을 사용하겠다 추가해주고, 이놈은 Shader를 로드함과 동시에 머터리얼 코드, 우리가 지난번에 만들었던 Shader에 인자를 꽂아넣는 작업까지 하나로 묶여있는 놈이다
  • EnginePch.h ... #pragma comment(lib, "FX11/Effects11.lib") ...
  • 한마디로 파이프라인, 머터리얼, Shader가 하나로 묶인 놈이다

Resource

  • 리소스 관련 오류들이 아직까지 뜨므로 이들을 가져와줄 것이다

ResourceBase

  • 리소스 데이터들의 부모 클래스로 어떤 리소스인지 판단해 그에대한 정보를 저장하거나 로드하는 함수를 가상함수로 만들어 자식에서 재정의해 사용할 수 있게 해놨다

Texture

  • Texture는 REsourceBase를 상속받아 만들었으며 DirectXTex라이브러리에서 우리가 로드하는 클래스이다

빌드

  • Engine 프로젝트를 빌드하면 이제 문제가 생기는 것들은 나중에 추가해 줄 것이므로 주석처리를 해준다
  • 이때 hinyxml2도 오류가 뜨는데 이는 cpp 파일에 미리컴파일된 헤더가 없는 것이므로 추가를 해주던지 미리컴파일된 헤더 사용안함을 하면 된다

Client 설정

pch.h

pch.h
#pragma comment(lib, "Engine/Engine.lib")
#include "EnginePch.h"
  • pch.h에서 Engine/Engine.lib와 EnginePch.h를 사용할 것이므로 추가해준다

Main

  • 이제 API를 만들때처럼 Main 함수를 작성해 줘야한다
Main.cpp

#include "pch.h"
#include "Main.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 = nullptr;//WindowsHandle은 그려질 창이 정해지고나서 정해지기 때문에 일단 비워둠  
    desc.width = 800;  
    desc.height = 600;  
    desc.clearColor = Color(0.5f, 0.5f, 0.5f, 0.5f);  
    desc.app = nullptr;//실행할 app으로 실행단위를 의미한다  
    GAME->Run(desc);
    return 0;
}
  • Main Class를 만들어준 뒤 cpp 파일에 WinAPI처럼 Main 함수를 만들어줄 것이다
  • 이때 Engine에 GAME 안에 화면에 창을 그려주는 것들이 모두 포함되어 있을 것이므로 GAME->Run()을 호출해 화면의 창을 그릴 준비를 한다
  • 이때 GameDesc 구조체를 호출해 설정 값들을 저장해 줄 것이다
  • 이 구조체에는 우리가 저번 프로젝트에서 창을 그리기 위해 필여했던 정보들인 appName, hInstance등이 존재할 것이다

TriangleDemo Class

  • 이제 Client를 시작 프로젝트로 설정 후 실행을 하면 Assert에서 IExecute가 없어 오류가 뜬다고 할 것이다
  • IExecute는 우리가 실행할 App을 Interface로 만든것이므로 실행할 app은 이 Interface를 상속받아 만들어야 할 것이다
  • 그래서 TriangleDemo Class를 만들어 Override된 함수들을 모두 만들어주고 Main에 desc.app에 추가해줘야한다
Main.cpp  
...  
desc.app = make\_shared<TriangleDemo()>  
...

  • 이제 실행하면 우리가 만든 창이 잘 뜨느것을 볼 수 있다
  • GAME안을 살펴보면 사실상 가장 핵심적인 역할을 하는 메인 함수일 것이다
  • 그 안들 들어가보면 Engine내의 Gmae 클래스로 Run 함수에서는 우리가 설정한 값을 가지고 윈도우 창의 정보를 등록하고, 윈도우 창을 생성 해줄 것이다
  • 그 후 Update 함수를 호출하는데 이는 MANAGER들의 Update 함수를 호출하고, Graphics의 RenderBegin과 우리가 설정한 app의 Update, Render 그리고 Grahpics의 RenderEnd를 호출하여 창에 그림까지 그려주게 되는 것이다
  • 이 과정이 저번 프로젝트에서 화면을 띄어 그림을 그려주는 과정이랑 동일한 과정이 될 것이다

Shader 입히기

  • 이제 TriangleDemo에 삼각형을 찍어볼 것이다
  • 그러기 위해 Shader를 추가해 작업을 해줄것이므로 HLSL을 꼭지점으로 추가해준다
  • 이때 솔루션 파일이 있는 위치에 Shader 폴더로 저장을 해줄것은데 이는 리소스 파일이므로 소스코드들과 분리되어 관리하도록 하기 위함이다

  • 추가한 HLSL 설정에서 DirectX11을 사용하므로 Shader Model을 5.0버전으로 바꿔준다
  • Shader 형식도 꼭지점에서 효과로 바꿔주는데 이는 문법은 거의 같지만 다른 다양한 기능들도 가능하도록 할 수 있다
  • 진입점 이름도 지워 진입점 함수를 우리가 원하는 함수로 사용할 수 있도록 설정도 해준다

Shader 코드 작성

  • 삼각형을 그리기 위해 hlsl에 코드를 작성하기전에 Shader 형식을 fx로 바꿔줬으므로 만들어진 파일의 확장자도hlsl이 아닌 fx로 바꿔준다
struct VertexInput
{
	float4 position : POSITION;
};

struct VertexOutput
{
	float4 position : SV_POSITION;
};

VertexOutput VS(VertexInput input)
{
	VertexOutput output;

	output.position = input.position;

	return output;
}

float4 PS(VertexOutput input) : SV_TARGET//우리가 지정한 렌더타겟에 그려주도록 하는 코드
{
	return 	float4(1,0,0,1);
}
float4 PS2(VertexOutput input) : SV_TARGET//우리가 지정한 렌더타겟에 그려주도록 하는 코드
{
	return 	float4(0,1,0,1);
}
float4 PS3(VertexOutput input) : SV_TARGET//우리가 지정한 렌더타겟에 그려주도록 하는 코드
{
	return 	float4(0,0,1,1);
}

technique11 T0
{
	pass P0
	{
		SetVertexShader(CompileShader(vs_5_0, VS()));
        SetPixelShader(CompileShader(ps_5_0, PS()));
    }
    pass P1
    {
        SetVertexShader(CompileShader(vs_5_0, VS()));
        SetPixelShader(CompileShader(ps_5_0, PS2()));
    }

};

technique11 T1
{
    pass P0
    {
        SetVertexShader(CompileShader(vs_5_0, VS()));
        SetPixelShader(CompileShader(ps_5_0, PS3()));
    }

};
  • 이젠 프로젝트에서 해왔던 것처럼 VertexInput을 통해 cpu에 정보를 gpu로 가져와주고 VertexOutput을 통해 내보낼 것이다
  • 그러기 위해 VertexOutput으로 VS 함수를 만들어 VertexOutput output변수에 position에 넘겨받은 인자 input.position을 저장해준 뒤 output을 리턴하여 VS단계를 수행할 것이다
  • 마찬가지로 float4 형태의 함수를 만들어 VertexOutput 인자를 받아준다
  • 이때 SV_TARGET 키워드를 통해 우리가 지정한 렌더 타겟에 그려주도록 한다
  • 원하는 색상을 return해 PS단계를 수행하는 함수이므로 float4로 색을 지정해 return해준다
  • 여기까지는 이전 프로젝트와 같지만 이후에 technique11이라는 새로운 놈이 보일것이다
  • 일단 코드를 작성해보자

TriangleDemo에 Shader 설정

class TriangleDemo:public IExecute
{
...
private:
	shared_ptr<Shader> shader;
	vector<VertexData>  vertices;
	shared_ptr<VertexBuffer> buffer;
}

TriangleDemo.cpp
void TriangleDemo::Init()
{
	shader = make_shared<Shader>(L"01.Triangle.fx");

	{
		vertices.resize(3);
		vertices[0].position = Vec3(-0.5f, 0.0f, 0.0f);
		vertices[1].position = Vec3(0.0f, 0.5f, 0.0f);
		vertices[2].position = Vec3(0.5f, 0.0f, 0.0f);
	}

	buffer = make_shared<VertexBuffer>();
	buffer->Create(vertices);
}

void TriangleDemo::Update()
{
}

void TriangleDemo::Render()
{
	uint32 stride = buffer->GetStride();
	uint32 offset = buffer->GetOffset();

	DC->IASetVertexBuffers(0, 1, buffer->GetComPtr().GetAddressOf(), &stride, &offset);

	shader->Draw(1, 0, 3);
}
  • Shader를 적용해 삼각형을 그리기 위해 shader, VertexBuffer 객체와 VertexData들을 저장할 vector 를 만들어준다
  • Init 함수에서 shader를 동적할당을 해줄때 우리가 사용할 Shader 파일의 이름과 확장자를 넘겨준다
  • Shader 클래스를 확인해보면 생성자에 Shader 폴더가 있는 곳을 가르키고 넘겨받은 wstring 을 더해 파일을 사용하도록 만든것을 볼 수 있다
  • 또한 CreateEffect 함수에 내용을 통해 자동으로 fx 파일의 technique11을 파악해 렌더링 파이프라인의 모든 것들을 만들어주는것을 하는것이다
  • 그래서 이제는 우리가 쉐이더에 연결하고 세부적인것을 만드는데 신경쓰지 않고 쉐이더에 정보들만 잘 넘겨주고 어떤것을 사용할지 선택만 해주면 되는것이다
  • 다시 돌아와 정점을 만들고 buffer를 만들어 정점을 넘겨준다
  • Render함수에서 이전과 같이 DC(Graphics클래스의 GetDeviceContext)에 IASetVertexBuffer를 호출하여 IA 단계에서 VertexBuffer를 묶어줄 것이다
  • 그 후 shader->Draw()를 해줄것인데 받는 인자들을 보면 technique, pass, vertexcount를 받는 것을 볼 수 있다
  • 이전에 Device를 이용한 Draw에서는 볼 수 없던 것들로, 이제는 technique, pass를 골라 넣어주면 shader에서 그에 맞는 것을 골라 알맞게 그려주게 된다

결과

  • 이제 technique, pass를 Shader에서 만든 번호로 바꿔가며 실행시켜보면 삼각형의 색이 다르게 실행되는 것을 볼 수 있다

'DirectX' 카테고리의 다른 글

35 Constant Buffer  (0) 2025.01.16
34 사각형 띄우기  (0) 2025.01.16
32 Data  (0) 2025.01.12
30 Material, Mesh  (1) 2025.01.12
29 Render Manager  (0) 2025.01.12