DirectX

32. DirectX11 3D 입문_프로젝트 설정

devRiripong 2024. 1. 31.
반응형

프로젝트를 빠르게 만들고 복붙을 하면서 기존에 만들었던 것들 중 필요한 것만 복사를 해서 이사를 할 건데 모든 거를 다 갖고 오진 않을 거고 파이프 라인 관련된 부분들을 조금 더 세련된 방법들이 있어서 그 방식으로 작업을 할 것이다.

 

1. 프로젝트의 Properties 폴더 세팅 

 

Visual studio를 실행하고 Create New Project를 선택한다.

 

Windosw Desktop Application,

Windows Desktop Wizard

둘 중에서 하나로 만들면 된다.

 

여기선 Windows Desktop Wizard로 만들어 본다.

 

Project name은 Client로

Solution name은 GameCoding2로 했다.

Create를 누르면 나오는 화면에서

이렇게 설정한다.

 

일단 솔루션탐색기에서 Client 프로젝트를 제거한다.

윈도우 탐색기로 들어가서 Client 폴더까지 삭제한다. 그래야 완전히 삭제된다. 

 

솔루션 탐색기에서 우클릭해서 새 프로젝트를 추가한다.

2가지를 만들 건데, 일단 라이브러리부터 만들어 본다.

 

Static Library (정적 라이브러리) 를 선택한다. Engine이란 이름으로 만든다.

Header Files 필터에 있던 pch.h와 framework.h를 Source Files 필터로 옮기고

Header Files 필터를 삭제한다.

 

그리고 다시 새 프로젝트를 추가한다.

Windows Desktop Application을 선택하고, 이름을 Client로 한다.

솔루션 탐색기에서 Client 프로젝트의 Header, Resource, Source Files 필터에 들어있는 코드 파일들을 다 삭제한다. Resource Files 필터도 삭제한다.

 

1) Engine 프로젝트의 폴더 세팅 

일단 Engine 프로젝트로 가서 Engine 세팅부터 하고 넘어간다.

우클릭해서 Properties에서 하던 것들이 있었다.

  1. Configuration Properties(구성 속성)의 General(일반)의 Output Directory를 잡아 주고,
  2. Intermediate Directory를 잡아 주고,
  3. C / C++의 General에서 Additional Include Directories를 세팅하고,
  4. Librarian(라이브러리 관리자)의 General에서 Additional Library Directories

이렇게 4가지를 설정했었다.

 

탐색기에서 폴더를 만들어서 경로를 맞춰 준다.

솔루션 파일이 있는 경로에서 Binaries 폴더를 만든다. 최종적인 결과물이 만들어지는 폴더다.

Intermediate라는 폴더를 만드는데 중간에 임시로 만들어지는 결과 파일들을 저장할 것이다.

Libraries라는 폴더를 만드는데 각종 라이브러리들을 여기에 저장할 것이다.

Libraries 폴더 안에는 Include라는 헤더를 넣는 폴더와, Lib라는 lib static(정적) 파일들을 넣는 폴더를 만든다.

다시 뒤로 가서 솔루션 파일이 있는 곳에 ResourcesShaders 폴더를 만든다.

 

Libraries / Lib 안에다가

DirectXTex 폴더와

Engine 폴더(실질적으로 사용하는 만들고 있는 Engine의 결과물을 저장),

FX11 폴더(셰이더를 편리하게 사용할 수 있게 하는 라이브러리)

를 만든다.

 

Libraries / Lib / DirectXTex 폴더에 DirectXTex.lib와 DirectXTex_debug.lib를 넣는다. 텍스쳐를 로딩하는 라이브러리다.

Libraries / Lib / Engine 폴더엔 오늘 빌드한 결과물들이 뜨게 할 것이고,

Libraries / Lib / FX11에 Effects11.lib, Effects11.pdb, Effects11d.lib, Effects11d.pdb 파일을 넣어 준다. 직접 빌드해서 넣고 싶으면 Effect11이라고 검색하면 소스코드가 나오는데 이를 빌드한 결과를 넣으면 된다.

 

Libraries / Include 폴더는 헤더들을 넣어주는 폴더다.

DirectXTex 폴더를 만들어 DirectXTex.h와 DirectXTex.inl파일을 넣는다.

FX11 폴더를 만들어 d3dx11effect.h와 d3dxGlobal.h 파일을 넣는다.

마지막으로 Engine 폴더를 만드는데 오늘 만들 Engine의 헤더파일들을 넣을 것이다. 이건 하나의 옵션이다. 여기다가 넣어줘도 되지만 원래 작업하고 있는 Engine 프로젝트의 경로를 세팅해 줘도 된다. 하지만 그렇게 하면 .cpp랑 여러 가지 파일들이 섞이기 때문에 원하는 건 헤더파일들만 모아서 관리하는 것이 편하기 때문에 Include / Engine에 모든 헤더 파일들을 복사해서 관리하는 경우도 있다. 만약 Engine을 다른 사람에게 공개할 때도 여기 있는 헤더파일들과 Lib 파일들만 전달해주면 된다.

 

폴더의 구조는 대강 잡힌 것이다.

 

이거를 이용해서 VS에서 세부적인 경로를 설정해 준다.

 

Engine 프로젝트에서 우클릭해서 Properties로 가서

이렇게 OutputDirectory와 Intermediate Directory를 설정한다.

 

C/C++의 General에서 Additional Include Directories에는

$(SolutionDir)Libraries\Include\;%(AdditionalIncludeDirectories)를 넣어준다.

 

Precompiled Header는 이미 되어 있다.

 

Librarian의 General에 

$(SolutionDir)Libraries\Lib\;%(AdditionalLibraryDirectories)

라이브러리 파일들이 어디에 있는지를 적어준다.

 

이렇게 Engine 세팅은 끝났다.

 

2) Client 프로젝트의 폴더 세팅

Client도 마찬가지로

General에서 Output Directory와 Intermediate Directory를 설정해 준다.

 

아직 C++ 설정하는 곳이 없는데 C++ 코드가 프로젝트에 없기 때문이다. Client프로젝트의 Source Files 필터에 pch 클래스를 추가한다.

그리고 다시 Properties에 들어가서

 

C/C++의 Additional Include Directories에

이렇게 넣어준다.

기본 경로의 헤더를 찾고 없으면 추가 포함 디렉토리에 가서 거기에 있는 헤더들도 찾으라고 하는 것이다.

 

Linker의 General로 가서 AdditionalLibraryDirectories에 

$(SolutionDir)Libraries\Lib\;%(AdditionalLibraryDirectories)

이렇게 써준다.

 

C/C++의 Precompiled Header가 안 만들어져 있다.

이렇게 바꿔준다.

 

이렇게 하면 모든 파일들이 적용이 되는데

pch.cpp 파일만

이렇게 Create로 바꿔줘야 한다.

 

Header Files 필터도 삭제한다.

 

Engine 프로젝트를 빌드를 하면

$(SolutionDir)Libraries\Lib\Engine에

이렇게 만들어지는 것을 확인할 수 있다.

 

그리고 Engine의 헤더파일들이 Libraries/Include/Engine 에 옵션으로 들어가길 원한다면

Client에서 Engine의 헤더 경로를 어디서 찾을 것인가를 입력할 수가 있는데

Libraries/Include/Engine 여기다 헤더를 모아 놓겠다고 지정을 했다고 하면

헤더파일들을 옮겨줘야 할 것이다.

그럴 때 단순한 방법 중 하나는 Properties에서 Build Event라는 걸 사용하는 것이다.

 

xcopy는 복사하겠다는 명령이다. 배치파일에서 사용하는 명령이라고 보면 된다.

/Y는 덮어쓰기를 하겠다는 말이다.

.h과 .inl 파일들을 복사해 주겠다고 되어 있는 것이다.

 

이렇게 해준 상태에서 다시 Engine을 빌드해본다면

\Libraries\Include\Engine에도

이렇게 헤더 파일들이 들어가 있다.

 

이런 식으로 헤더 파일을 몰아넣은 다음에 Client 쪽에서 그 폴더를 참조하게끔 만들어도 된다.

아니면 원래 하던 대로 엔진의 작업하고 있는 코드가 있는 경로 자체를 Client의 Additional Include Directory에 넣어줘도 된다.

 

2. 윈도우 창에 회색 화면 띄우기 초기화

1) Engine 프로젝트의 필터 세팅 및 코드 채우기

Engine의 Resource Files 필터를 지우고

 

99. Header 

98. Utils 

00. Engine

01. Graphics

02. Managers

03. GameObject

04. Component

 

이렇게 필터들 추가한다.

순서대로 정렬이 안되면 98, 99번 필터를 다른 필터에 넣었다가 빼면 정렬이 된다.

 

Engine.cpp를 삭제한다.

framework.h의

#define WIN32_LEAN_AND_MEAN             // Exclude rarely-used stuff from Windows headers

이 코드를 pch.h의 #include “framework.h”의 자리로 옮기고

framework.h는 삭제한다.

 

pch.h, pch.cpp는 99. Header로 옮기고 SourceFiles 필터는 삭제한다.

 

강의 수업 자료에 올려주신 프로젝트의 파일들을 하나씩 복사한다.

 

99. Header부터 채워주자면

탐색기에서 Define.h, EnginePch.h, Types.h를 Engine 프로젝트 폴더에 복사하고 99. Headers 필터에 넣어준다.

Define.h는 싱글톤을 사용하는 매크로를 지정해 놨고,

EnginePch.h 에는 엔진과 관련된 필수 헤더들이 들어있다. 

 

98. Utils를 채워주자면

SimpleMath.cpp, .h, .inl을 가져와서 넣는다.

수학 연산 같은 걸 편리하게 작업할 수 있게 해 준다.

 

그리고

tinyxml2.cpp, .h를 가져와서 넣는다.

XML 파싱을 이걸로 했다

 

그리고

Utils.cpp, .h를 가져와서 넣는다.

기타 등등의 작업을 여기서 해 줄 예정이다.

 

pch.h에는 원래 있던 코드를 삭제하고

#pragma once

#include "EnginePch.h"

 

이렇게 해준다.

어차피 EnginePch.h에 온갖 기능들이 다 있기 때문에 이렇게만 설정을 해주면 된다.

EnginePch.h를 만든 이유는 나중에 Client에서도 이 파일을 사용할 예정이기 때문이다.

 

에러 나는 부분은 주석 처리 하고, 필요할 때 채워주기로 한다.

 

 

02. Managers를 채워주자면

입력을 받아주는 InputManager.cpp, .h를 넣어준다.

시간을 관리할 때 썼던 TimeManager.cpp, .h를 넣어준다.

Graphics.cpp, .h를 넣어준다. Device, DeviceContext, SwapChain 첫 시간에 만들어 봤던 아이들이 들어있다.

Game.h, .cpp를 넣어준다. 처음으로 들어가는 애인데, 윈도우창 정보 등록하고, 생성하고, main 루프 돌면서 PeekMessage를 하고, TranslateMessage, DispatchMessage 한 다음에 Update로 들어가서 Gmae::Update가 실행하는 코드라고 보면 된다. 2D 작업할 때 콘텐츠 단에 있던 걸 Engine단으로 빼놓은 것이다. Run 함수가 어떻게 보면 Main 함수 같은 거라고 볼 수 있다. 창 관련 옵션들을 넘겨주는 방식으로 작업을 할 것이다.

ResourceManager.cpp, .h를 넣어준다. 리소스를 편하게 관리하게 만들어준 아이다.

싱글톤으로 사용할 거는 EngeinPch.h에 #define으로 정의해 놔서 편하게 쓸 수 있게 만들어 놨다.\

 

00. Engine을 채워주자면

실행 단위라는 개념을 등장시켜서 편리하게 실행하게 할 것이다. 사용하면서 볼 것이다.

00. Engine에 IExcute.cpp, .h를 넣는다. 일종의 Main 함수를 여러 개 만든다고 생각하면 된다.

빌드를 하면 아직 없는 게 많아서 에러가 난다.

 

01. Graphics 산하에 Buffer라는 필터를 만든다.

ConstantBuffer.cpp, .h를 넣어준다.

GPU에게 데이터를 떠넘길 때 데이터를 만든 다음 버퍼에 copy 데이터를 하는 식으로 만들었었고,

기하학적인 모형을 나타내기 위해 Geometry라는 단위를 만들었었다.

Buffer필터에 Geometry.cpp, .h를 넣는다. Geometry는 index, vertex로 이루어진 아이인데 처음에는 사용 안 하다가 나중에 인덱스 버퍼를 사용할 때 vertex와 index를 묶어서 사용하던 게 Geometry였다.

indexBuffer, vertexBuffer 두 가지 개념이 있었는데 VertexBuffer.cpp, .h와 IndexBuffer.cpp, .h를 Buffer 필터에 넣어준다.

VertexData.cpp, .h도 넣어준다. 정점이 어떤 식으로 표현이 되어 있는가가 나타나 있고, 셰이더와 맞게 만들어 줘야 했다.

 

01. GraphicsShader라는 필터를 추가한다.

지금까지 것과 느낌이 다르다. 이전까지는 쉐이더 리소스를 로드해서 그걸 VertexSahder혹은 PixelShader 클래스로 만들어서 관리를 하고, Shader라는 게 렌더링 파이프라인 단계에서 실행되는 코드들이었는데, 인자를 넣기 위해 material이라는 개념을 등장시켜서 이것저것 조립을 했었다.

근데 이 모든 상황을 맞추는 거 자체가 복잡했다.

VertexData에 InputLayout이라 해서 어떻게 생겼는지 묘사해서 그걸 연동시키고, ConstantBuffer를 통해서 인자를 넘기고 그런 식으로 작업을 했어야 되는데 Shader가 하나만 있으면 할 만하겠지만 Shader가 점점 많아지면 어떻게 짝을 맞춰줘야 할지가 어려워진다.

조금 편리한 방법으로 오늘 사용하는 새로운 방식인 effect11이란 라이브러리를 사용할 건데 그걸 하기 위해서 래핑 된 파일들을 가져와야 한다.

Pass, Shader, Technique라는 클래스 3개를 가져와서 Shader 필터에 넣어준다.

사용하면서 공부할 건데 기본적으로는 EnginePch.h에서 FX11 라이브러리를 사용하겠다고 해 놨고,

한마디로 하면 Shader를 로드함과 동식에 Material 코드, Shader에 인자를 꽂아 넣는 작업까지 편리하게 하나로 묶여 있다고 보면 된다. 다시 말해 파이프라인, material, shader가 하나로 묶여 있다고 보면 된다.

앞으로는 Shader와 관련된 부분을 생각할 필요 없이 shader를 로드 한 다음에 편리하게 작업할 수 있는 것이라는 게 크게 달라진 부분이다.

나중에 Shader를 사용하는 연습을 해보면서 익숙해지는 시간을 가져보도록 할 거다.

 

ResourceBase 클래스를 임시로 00. EngineResource 필터를 만들어 넣는다.

Texture 클래스도 넣어준다. DirectXTex라이브러리에서 로드하는 부분이다.

 

여기까지가 지난 시간에 하던 부분이고,

빌드하면 에러 나는 부분이 있는데, tinyxml2.cpp의 properties에서 C/C++의 PrecompiledHeader에서 사용 안 함으로 설정하면 해결된다.

 

이제 빌드가 되는데

어찌 됐건 복구를 한 것이다.

새로 추가한 것도 있지만 기본적으로는 지난 시간에 만든 것과 크게 달라진 거는 없다고 볼 수 있다.

 

Libraries\Include\Engine 폴더에 .h파일들이 복사된 것과

Libraries\Lib\Engine폴더에 lib 파일이 생긴 것을 볼 수 있다.

 

지금은 프로젝트 폴더에 코드들을 다 모아 놓고 있지만

너무 많아지면 폴더를 만들어서 작업하는 코드를 넣어 줘도 된다.

솔루션 탐색기의 필터들은 가상의 폴더이기 때문에 실제 경로에는 영향을 주지 않는다.

탐색기에서 폴더를 만들어서 관리하게 되면 #include””에 경로를 써야 하기 때문에 귀찮은 점이 있다.

그래서 지금의 규모에서는 그냥 프로젝트 폴더에 모아 넣어서 하나로 관리한다.

 

2) Client 프로젝트의 필터 세팅 및 코드 채우기

pch.h의 내용을 삭제하고

#pragma once

#pragma comment(lib, "Engine/Engine.lib")
#include "Engine/EnginePch.h"

이렇게 넣어준다.

Engine라이브러리를 사용할 것이고,

EnginePch.h를 사용할 것이다라고 해주면 된다.

 

Client 프로젝트에 Game 필터랑, Main 필터랑, Shaders필터를 만든다. Source Files 필터는 삭제한다.

pch.cpp, .h 는 Main 필터로 옮긴다.

 

Main 필터에 Main 클래스를 생성한다. 핵심적인 Mian 함수가 들어가게 될 것이다.

WinMain함수를 여기에 넣어주게 될 것이다.

 

Game.h의 내용을 삭제하고

Game.cpp에

#include "pch.h"
#include "Main.h"
#include "Engine/Game.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 = nullptr; // 실행 단위

	GAME->Run(desc); 

	return 0; 
}

 

이렇게 코드를 넣어준다. desc.app은 아까 얘기했던 실행 단위라는 거다.

실행할 app을 정해주면 되는데, 이는 새로 만들어서 전달해 주면 된다.

예를 들어 Gmae 필터에 새로운 클래스를 만들어 줄 건데, TriangleDemo라는 이름의 클래스를 만든다.

클래스 이름은 이렇게 되어 있지만 솔루션 탐색기에서 파일이름엔 번호를 붙여도 상관이 없다.

솔루션 탐색기에서 01. TriangleDemo라고 01.을 붙여 이름을 수정한다. 임시 테스트 용도로 이름을 좀 이상하게 지어 주는 것이다.

01부터 작업을 한다고 보면 된다.

 

01. TriangleDemo에서 

#pragma once
#include "IExecute.h"

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

이렇게 IExecute가 하나의 실행 단위가 될 거라고 연동을 해 주면 된다.

IExecute의 virtual 함수를 override 해서 동작을 시키는 것이다. 사용할 예정이 있으면 여기에 뭔가를 기입해 주면 된다.

 

다시 Main.cpp로 가서

#include "01. TriangleDemo.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<TriangleDemo>(); // 실행 단위

	GAME->Run(desc); 

	return 0; 
}

이렇게 TriangleDemo를 app에 넣어주면

TriangleDemo 코드가 실행이 된다.

 

01. TriangleDemo가 하나의 앱이라고 보면 되는 거고, 02, 03 이렇게 늘려 가서 다양한 앱을 실습할 수 있게 만들어 줄 수가 있게 된다.

 

저장을 한번 한다.

 

Engine은 Lib 파일이라 시작 프로젝트로 설정하고 실행을 하면 에러가 난다.

 

3) Client 프로젝트 실행

Client 프로젝트를 시작프로젝트로 설정을 하고, 실행을 하면

이렇게 창이 뜬다.

회색 화면이 뜬다는 건 좋은 신호다.

 

 

	GAME->Run(desc);

얘가 되는 이유는 분석하며 차근차근 보면 된다.

 

Game이 핵심적인 역할을 하는 메인 함수라고 볼 수 있는 거고,

Game이 Run을 했다는 건 윈도우 창을 등록하고, GRAPHICS, TIME, INPUT 같이 기본적인 애들을 초기화를 하고 있고, Update함수가 실행되는데

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

	GRAPHICS->RenderBegin();

	_desc.app->Update();
	_desc.app->Render();

	GRAPHICS->RenderEnd();
}

이 함수 내부에서는 app을 Update 하고 Render를 한다. app이라는 게 방금 넣어준 TriangleDemo였고, 나머지 부분들이 RenderBegin, RenderEnd 사이에 끼어서 이것저것 호출이 되며 동작하고 있기 때문에 지금까지 작업했던 구조와 완전히 똑같다고 볼 수 있다는 얘기가 되는 거다.

 

회색화면부터 시작해서 차근차근 앞으로 갔었다.

회색 화면을 이어서 삼각형 띄워보고

이거를 다시 한번 밟아 간다.

 

3. Shader 파일을 technique와 pass를 추가해 구현하기

1) 01. Triangle.hlsl파일을 생성 후 Shaders폴더로 옮기고 Shaders 필터에 넣어주기

쉐이더 부분이 많이 달라진다고 보면 된다.

ClientShader 필터에 셰이더를 추가한다.

추가를 누르고 HLSL로 가서 Vertex Shader File (꼭짓점 쉐이더 파일)을 선택한다.

이름은 01. Triangle.hlsl로 한다.

탐색기에서 보면 경로가 Client 프로젝트 폴더에 들어가 있는데,

솔루션 탐색기에서 Delete가 아닌 Remove를 하고,

탐색기에서 잘라내기 후 Shaders 폴더에 붙여 넣기 한다.

01. Triangle.hlsl 셰이더는 리소스 개념이기 때문에 따로 빼주었고 이걸 다시 Shaders 필터에 넣어준다.

위치는 바뀌었지만 여전히 사용할 수 있는 파일이 되었다.

이렇게 배치만 해줘도 빌드를 하면 셰이더에 문제가 있는지 없는지 잡아준다.

 

2) 01. Triangle.hlsl의 peoperties 세팅하기

01. Triangle.hlsl의 Properties에 가서 HLSL Compiler의 Entrypoint Name을 보면 main으로 되어 있다.

Shader Model은 5.0으로 바꾸고,

Shader를 작성할 때 정해진 문법을 그대로 만들기가 귀찮은 경우가 많다.

Shader Type은 기본적으로 Vertex Shader로 되어 있는데 그러면 main 함수를 요구하기 때문에 귀찮다. 그래서 Effect로 해준다. 문법은 HLSL과 유사하다 볼 수 있는데 여러 추가적 기능이 있다.

Entrypoint Name은 삭제한다.

이렇게 일단은 바꿔준다.

 

3) 01. Triangle.hlsl에 기존의 shader 파일에서 있던 내용 구현하기

01. Triangle.hlsl로 가서 내용을 채워준다.

 

결국 VertexShader와 PixelShader를 묶어서 하나로 관리하긴 할 것이다.

struct VertexInput
{
    float4 position : POSITION; // POSITION을 찾아서 연결해 줄 것이다.
};

struct VertexOutput
{
    float4 position : SV_POSITION;  // System Value 라고 예약된 이름이라 명시
};

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

float4 PS(VertexOutput input) : SV_TARGET   // 지정한 RenderTarget에 색상을 그려줄 것이다.
{ 
    return float4(1, 0, 0, 1); // 빨강색을 그려준다.
}

float4 PS2(VertexOutput input) : SV_TARGET // 지정한 RenderTarget에 색상을 그려줄 것이다.
{
    return float4(0, 1, 0, 1); // 초록
}

float4 PS3(VertexOutput input) : SV_TARGET // 지정한 RenderTarget에 색상을 그려줄 것이다.
{
    return float4(0, 0, 1, 1); // 파랑
}

이렇게 01. Triangle.hlsl을 채워줬는데, 3가지 버전으로 만든 게 원래 버전과 다른 점이다.

 

4) 01. Triangle.hlsl에 technique11과 pass를 추가해 구현하기 

원래는 컴파일할 때는 Pixel Shader라는 클래스를 만든 다음에 로드를 할 때 PS2, PS3 같은 걸 직접적으로 연결해서 Blob이라는 걸 만들어서 사 용하고 했었는데

이제는 Shader필터에 넣어준 Pass, Shader, Technique가 어떻게 동작하는지 살펴볼 것이다.

 

01. Triangle.hlsl에 이어서 아래 코드를 추가한다.

technique11 T0
{
    pass P0
    {
        SetVertexShader(CompileShader(vs_5_0, VS())); // 버전은 5.0, main 함수는 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()));
    }
};

technique11이라는 다른 문법의 뭔가를 추가하고 있다.

이걸 이용해서 어떤 애를 사용을 할지 골라줄 수가 있다. 

 

4. Shader 테스트

실습을 해보자면

1) Technique라이브러리로 Shader를 읽고 세하게 해서 삼각형 띄우기

01. TriangleDemo.h로 가서

#pragma once
#include "IExecute.h"

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

	shared_ptr<Shader> _shader; 
	vector<VertexData> _vertices; 
	shared_ptr<VertexBuffer> _buffer; 
};

Shader를 하나 받아 주고,

VertexData를 하나 받아서 삼각형을 만들고,

VertexBuffer를 통해 삼각형을 묘사하는 VertexData를 이용해서 Buffer를 만들어서 GPU에 떠넘기는 걸 VertexBuffer를 통해서 했었다.

이 작업을 해줄 것이다.

 

01. TriangleDemo.cpp로 가서

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

이렇게 해준다.

01. Triangle.hlsl 파일의 확장자를 .fx로 수정한다.

 

Shader의 생성자를 보면

Shader::Shader(wstring file) : _file(L"..\\Shaders\\" + file)
{
	_initialStateBlock = make_shared<StateBlock>();
	{
		DC->RSGetState(_initialStateBlock->RSRasterizerState.GetAddressOf());
		DC->OMGetBlendState(_initialStateBlock->OMBlendState.GetAddressOf(), _initialStateBlock->OMBlendFactor, &_initialStateBlock->OMSampleMask);
		DC->OMGetDepthStencilState(_initialStateBlock->OMDepthStencilState.GetAddressOf(), &_initialStateBlock->OMStencilRef);
	}

	CreateEffect();
}

Client.exe 파일이 있는 Binaries 폴더에서 코드가 시작이 될 것이기 때문에 상위 폴더로 가서, Shaders폴더 안에 있는 01. Triangle.hlsl 이 아이를 컴파일하라고 경로가 잡혀 있다. 나중에 빌드를 해서 출시할 때는 이렇게 상대 경로로 할 것이 아니라 실제로 리소스 파일의 경로를 받아서 그거에 대한 Shader 파일의 경로 등등을 깔끔하게 관리해야겠지만, 일단은 개발 단계에서는 이렇게 뒷칸으로 가서 Shaders 폴더 안에 찾아보세요라고 이렇게 만들었다.

 

이 부분에서 Shader가 만들어지면, CreateEffect라는 걸 하는데, 함수를 보면 여기서 Triangle.fx를 읽어서 뭘 해주고 있는데,

void Shader::CreateEffect()
{
	_shaderDesc = ShaderManager::GetEffect(_file);

넣어줬던 technique11 T0 이런 부분들을 실시간으로 분석을 해서 파이프라인이란 복잡한 모든 것들을 만들어 주는 걸 얘가 자동적으로 해준다고 보면 된다.

그래서 연결하고 그런 세부적인 걸 신경 쓰지 않고,

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

이렇게만 해주면, 이전에 복잡하게 파이프라인 만들고 InputDescription 만들고 하던 부분들을 생략하고 이 코드에만 집중할 수 있다. 삼각형 만드는 걸 하자면,

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

	{
		_vertices.resize(3); 

		_vertices[0].position = Vec3{ -0.5f, 0.f, 0.f }; 
		_vertices[1].position = Vec3{ 0.f, 0.5f, 0.f }; 
		_vertices[2].position = Vec3{ 0.5f, 0.f, 0.f }; 
	}

	_buffer = make_shared<VertexBuffer>(); 
	_buffer->Create(_vertices); 
}

이렇게 버퍼를 이용해서 GPU한테 그려라고 할 수 있게 된 것이다.

 

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

	// DeviceContext에서 IA단계에서 사용할 VertextBuffer를 묶어주는 함수였다.
	DC->IASetVertexBuffers(0, 1, _buffer->GetComPtr().GetAddressOf(), &stride, &offset);

	// 원래 그렸던 device를 이용한 Draw에서는 technique, pass라는 인자가 없었어. 
	// 이거 만들어준 Shader라는 클래스에 포함이 되어 있는 기능이다. 
	// technique이란 pass를 골라줄 수 있다는 특징이 있다. 
	// _buffer->GetCount()이걸로 넣어줘도 되지만 몇 개 없으니 3으로 넣어준다.
	_shader->Draw(0, 0, 3); 
}

빌드를 하면 빌드가 된다.

실행을 하면

 

삼각형이 뜬다.

수동으로 모든 걸 작업할 때에 비해 쉽게 할 수 있었다.

 

2) technique와 pass인자를 바꿔서 테스트하기

_shader->Draw(0, 0, 3); 

에서 인자 앞의 3개가 thechnique, pass, vertexCount였다.

pass를 1로 바꾸면 어떻게 될까?

 

01. Triangle.fx에서

technique11 T0
{
    pass P0
    {
        SetVertexShader(CompileShader(vs_5_0, VS())); // 버전은 5.0, main 함수는 VS다는 뜻
        SetPixelShader(CompileShader(ps_5_0, PS())); // 버전은 5.0, main 함수는 VS다는 뜻
    }

    pass P1
    { 
        SetVertexShader(CompileShader(vs_5_0, VS())); // 버전은 5.0, main 함수는 VS다는 뜻
        SetPixelShader(CompileShader(ps_5_0, PS2())); // 버전은 5.0, main 함수는 VS다는 뜻
    }
};

T0 tequnique로 pass P1을 사용한다는 의미가 되니까

이렇게 뜬다.

 

technique11 T1
{
    pass P0
    {
        SetVertexShader(CompileShader(vs_5_0, VS()));

        SetPixelShader(CompileShader(ps_5_0, PS3()));
    }
};

이걸 사용하고 싶다면

_shader->Draw(1, 0, 3);

이렇게 해주면

파란색으로 뜬다.

 

5. 맺음말

지난 시간에 했던 것과 기술은 같지만 파이프라인이나 다른 것들을 한 번에 해준다는 것이고, 세부적으로 분석을 하면 되겠지만 기본적으로 pass, technique, shader라는 게 01. Triangle.fx에서 사용하는 것을 의미한다. pass가 작은 단위고, 뭉친 게 technique고 tehchnique를 여러 개 들고 있는 게 shader다.

 

아직까지는 모든 걸 이해하기 힘들겠지만 많이 보던 애가 있을 것이다.

Pass.cpp에서 보면 Draw와 DrawIndexed가 있다.

근데 DrawInstanced와 DrawIndexedInstanced는 아직 하지 않았다. 아는 부분이 보이면 그 부분을 위주로 보면 된다.

 

앞으로 Pass, Shader, Technique 이런 애들을 활용할 때 정확하게 어떻게 등장하는지는 몰라도 되지만 이런저런 기능을 이용해서 Shader에 접근해서 사용하는 걸 편리하게 할 수 있다는 게 결론이다.

 

렌더링파이프라인에 묶어주는 단계는 신경 쓰지 않고, Client의 01. TriangleDemo.cpp의 Init에서 유니티 엔진을 다루듯 세부적인 렌더링 코드에 대해서만 신경 써서 만들 수 있게 되었다는 얘기가 된다.

편리하게 작업을 할 수 있게 되었다.

 

엔진은 한 번만 만들어 두면 윗단에서 편리하게 만들 수 있다가 결론이다.

 

1차적으로 지금까지 작업했던 내용들을 정리하는 작업까지 해 보았다.

반응형

'DirectX' 카테고리의 다른 글

34. DirectX11 3D 입문_Constant Buffer  (0) 2024.02.02
33. DirectX11 3D 입문_사각형 띄우기  (0) 2024.02.01
31. 엔진구조_Data  (0) 2024.01.29
30. 엔진구조_Animation  (0) 2024.01.28
29. 엔진구조_Material  (0) 2024.01.26

댓글