GameConding이라는 솔루션 하위에 Engine이라는 이름으로 정적라이브러리를 위한 프로젝트를 우선 추가했다.
그리고 프로젝트 추가로 ~를 Client라는 이름으로 추가하였다.
설정해야할 네 가지
구성속성>일반 출력디렉토리/ 중간디렉토리
C++>일반 추가포함디렉토리
라이브러리>추가 라이브러리
Binaries (결과실행파일)
Intermediate (오브젝트파일)
Libraries
Libraries/Include (헤더파일)
Libraries/Include/DirectXTex
Libraries/Include/Fx11
Libraries/Include/Engine
Libraries/Lib (lib 정적파일)
Libraries/Lib/DirectXTex : 이미지 텍스쳐 사용위한 MS에서 제공하는 라이브러리
Libraries/Lib/Engine : 빌드한 라이브러리 결과물이 들어갈 곳
Libraries/Lib/Fx11 : 쉐이더를 편하게 사용하게 해주는 라이브러리. Effect11을 CMake같은것으로 빌드한것
Resources
Shader
디렉토리 생성하였다.
Engine프로젝트
일반>출력디렉토리 : $(SolutionDir)Libraries\Lib\Engine, 중간디렉토리 $(SolitionDir)Intermediate설정.
C/C++일반>추가포함디렉토리 : $(SolutionDir)Libraries\Include
미리컴파일된 헤더 pch.h로 첨부터 만들었음 좋고 없으면 만들고(만들때 모든 파일을 우클릭설정>미리컴파일된 헤더 사용하고 pch.cpp파일만 만듦인가? 로 하면됨)
라이브러리 관리자>일반>추가라이브러리 디렉터리 : $(SolutionDir)Libraries\Lib
Client프로젝트
일반>출력디렉토리 : $(SolutionDir)Binaries, 중간디렉토리 $(SolitionDir)Intermediate설정
C++파일을 가지고 있지 않으면 C/C++이 안뜨니깐 클래스 추가로 pch.h라도 만들어줬다.
다시 C/C++> 추가포함디렉토리 : $(SolutionDir)Libraries\Include; $(SolutionDir)Libraries\Include\Engine;(Engine은 자주 사용할 라이브러리이나 매번 #include 처리하기 귀찮으니깐.!)
링커>일반>추가라이브러리 디렉토리 : $(SolutionDir)Libraries\Lib
미리컴파일된헤더파일>사용 및 pch.h
그리고 Engine프로젝트에 대해서는 헤더파일과 소스파일이 모두 Engine디렉토리 하위에 남게되는데 Libriaries\Include\Engine에 헤더파일들이 복사되길 자동으로 원한다면 여러가지 방식이 있겠지만 Engine 속성페이지의 빌드이벤트에서 xcopy할수 있도록 빌드전 이벤트의 명령줄에
`
xcopy /y "$(SolutionDir)Engine\*.h" "$(SolutionDir)Libraries\Include\Engine"
xcopy /y "$(SolutionDir)Engine\*.ini" "$(SolutionDir)Libraries\Include\Engine"
`
이렇게 추가해주었다. 배치파일 명령어를 사용하는데 /y는 파일 덮어쓰기를 허용한다는 의미이다
그리고 Engine프로젝트에 전시간에 2D 프레임워크에서 사용했던것처럼 필터구조를 잡아줬다.
- 99.Headers필터 하위에는우선 싱글톤과 관련된 소스를 담아뒀던 Define.h와 엔진과 관련된 STL, DX, Windows 라이브러리드를 inlucde해두었던 EnginePch.h, 그리고 타입을 정의해둔 Types.h를 추가했다. 그리고 EnginePch.h에 Fx11라이브러리를 사용하기 위해 #include "Fx11/d3d11effect.h"도 추가되었다. 외에도 EnginePch.h에 있는 매니저나 Engine관련 소스파일들도 추가해줄 예정이다.
- 99.Utils필터에는 말그래도 유틸리티 관련 소스파일들을 추가해주는데 전에도 사용했던 수학 연산을 쉽게 해주는 SimpleMath.h,cpp,ini파일을 추가했고 XML데이터 파싱을 도와주는 TinyXml2, Utils.h를 넣었다.
- 02.Manager필터에는 입력을 받아주는 InputManager클래스파일, TimeManager, f리소스를 관리하는 ResourceManager, 2D 게임프레임워크에서 진입점이자 메인을 담당했던 Game, 렌더링 파이프라인을 담당하는 Graphics를 추가했다. 모두들 싱글톤으로 사용하고 있다.
Graphics클래스에 대해 복습하자면 device, devicecontext, swapchain을 가지고있는데 device와 devicecontext가 실질적으로 리소스를 만들고 GPU와 파이프라인에 전달하는 역할을 했고 swapchain은 더블버퍼링을 이용해 화면에 그려주는 용도였다. 이 클래스도 싱글톤이라서 GetDeivice,GetDeviceContext이렇게 사용하지 않아도 되도록 EnginePch.h에
#define GRAPHICS GET_SINGLE(Graphics)
#define DEVICE GRAPHICS->GetDevice()
#define DC GRAPHICS->GetDeviceContext()
이렇게 만들어줬었다.
Game클래스는 윈도우 등록과 생성, 메인루프를 돌면서 메시지가 있으면 PeekMessage, Translate를 하거나 없으면 Update내에서
void Game::Update()
{
TIME->Update();
INPUT->Update();
GRAPHICS->RenderBegin();
_desc.app->Update();
_desc.app->Render();
GRAPHICS->RenderEnd();
}
이렇게 그려주는 역할을 한다. _desc가 후에 Client프로젝트의 일부가 호출되는 부분이겠지.(작업단위를 만들어서 작업하고자 하는 IExecute 를 상속받은 클래스의 인스턴스를 넘겨주면 실행할 예정)
그리고 Game클래스에 미리 #include "IExecute.h"가 되어있는데 이건 실행단위를 도입할 예정인 것이다! 00.Engine에 IExecute클래스 파일들을 추가해주었다. 삼각형출력, 사각형출력 테스트 등 각각을 하나의 클래스로 분리하여 IExecute를 상속받아서 만들어쓸 예정이다.
- 01.Graphics필터에는 Buffer라는 필터를 만들고 ConstantBuffer클래스 헤더와 cpp파일을 넣었다. GPU에 데이터를 넘길때 복사해서 상수버퍼에 넘기면 쉐이더 단계에서 사용하도록 되었었다. 그리고 기하학 모형을 만들기 위해 Geometry클래스도 만들었었는데 vector<T> _vertices, vector<uint32> _indices로 짝을 이루어 들고 있어서 인덱스버퍼에서 사용했었다. 이도 Buffer필터 하위에 넣었다. 그리고 IndexBuffer, VertexBuffer, VertexData 도 같이 넣어줬다.
VertexBuffer는 Geometry에서 표현한 기하학 도형을 GPU에 복사할때 쓰는 버퍼였다. 그래서 buffer description을 만든다음에 immutable로 후에 고치지 않을거라 만들어서 고속복사를 하는데 사용했다.
IndexBuffer는 버텍스버퍼의 낭비를 줄이기 위해 만든 인덱스관련 정보를 복사하는 용도로 만들었다.
VertexData는 정점이 어떻게 표현되어 있는지를 정의한것으로 버텍스쉐이더에서 정의한것과 같아야했다. position, uv, color같은것을 가지고 있다.
- 01.Graphics하위에 Shader필터를 하나 더 만들었다. 그런데 2D프레임워크에서 쓰던 쉐이더와는 차이가 있을 예정인데 전에는 쉐이더리소스를 로드한 다음에 버텍스쉐이더나 픽셀쉐이더 클래스로 따로 관리하고 렌더링파이프라인단계에서 실행되는데 인자를 넣기 위해서는 material개념을 사용했었다. 그런데 이런 과정이 복잡하다. VertexData에다가 InputLayout이라고 어떻게 생겼는지 묘사해야하고 세부적인건 ConstantBuffer로 넘겨서 작업했는데 쉐이더가 많아지면 이 짝을 맞추는게 쉽지 않아서 직접관리하지 않고 편리하게 Effect11이라는 라이브러리를 사용할 예정이다. 그래서 Path, Shader, Technique 클래스를 추가했다. 요약하자면 쉐이더를 로드함과 동시에 머티리얼코드(쉐이더에 인자를 꽂는)작업까지도 한번에 동작하게 된다. 파이프라인+머티리얼+쉐이더가 하나인샘! EnginePch.h에도
- 00.Engine필터에 Resource필터를 만들고 ResourceManager클래스관련 파일들 추가했다. 텍스쳐 리소스관련 Texture.h/cpp도 같은필터에 넣어줬다. 여기선 DirectXTex라이브러리를 사용해서 텍스쳐 이미지를 로드하는 역할이었다.
- 참고로 Engine프로젝트의 Pch.h에는 #pragma_once와 #include "EnginePch.h"만을 해줄 뿐이다.
*우선 여기까지 추가하고 에러나는 부분은 주석처리한다음에 빌드해보았다. 외부라이브러리에서 pch관련 에러나는것은 소스에 pch.h를 인클루드해주거나 cpp소스파일 우클릭>미리컴파일된헤더 사용안함으로 해결가능하다.
이제 Client프로젝트를 수정해보자.
- Client프로젝트의 pch.h에는 우선 Engine프로젝트로 빌드해서 만들어진 정적라이브러리를 사용할수 있도록
#pragma comment(lib, "Engine/Engine.lib") // Client프로젝트의 링커>추가포함디렉토리에 Lib까지만 넣었기 때문에 Engine/을 명시했다.
#include "Engine/EnginePch.h"
만 추가해주었다. 그리고 Main필터를 만들어 그 하위에 옮겨줬다.
- Main필터에 Main.h, cpp를 추가한 다음에 WinMain함수를 추가한다. int WINAPI WinMain(HINSTANCE hInstance, ...)그리고 #include "Engine/Game.h"를 해주고 WinMain안에서 Engine/Game에서 필요한 정보들을 전달하고 GAME->Run()을 해줬다.
#include "pch.h"
#include "Main.h"
#include "Engine/Game.h"
#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;
}
여기서 GameDesc desc.app이 실행단위로 실행할 IExecute를 상속받은 클래스의 인스턴스를 포인터로 넘겨준다.
테스트를 위해 'TriangleDemo'클래스를 Client프로젝트의 Game필터 하위 '01. TriangleDemo.h'에 넣고 실행하면 빈화면이 뜨게된다.

IExecute를 상속받아서 Init,
- 그리고 Shaders필터에 새로운 쉐이더파일을 만들어본다. 우클릭>새항목추가>꼭짓점쉐이더파일(.hlsl)을 선택해서 이름을 01.Triangle.hlsl로 하였다. 이 파일은 솔루션과 같은 경로에 Shader디렉토리를 만들어줬어서 프로젝트에서 제거하고 거기에 옮기고 다시 추가하였다. 이 파일 속성에 들어가서 셰이더 모델을 5.0으로 설정하고(Fx11이므로..?) 셰이더 형식을 꼭짓점(Vertex)에서 효과(fx)로 바꿔서 진입점도 main을 지울수 있게했다. 그리고 아래와 같이 작성하였다.

VertexInput과 VertexOutput 형식을 지정해주고 position이라는 이름을 각각 주었는데 SV가 붙으면 시멘틱밸류라고 정해진 이름인 것이다..?
버텍스쉐이더에서는 받은 값을 그대로 돌려주고 반환된 VertexOutput값이 렌더링파이프라인의 단계에서 래스터라이저단계를 거쳐 픽셀쉐이더 단계로 가는데 반환값에 RGBA의 float4를 넣는다. 그런데 전에 삼각형을 띄울때는 하나의 PS만 만들어서 빨간색을 띄워줬는데 이번엔 PS2, PS3도 만들어서 초록색과 파란색도 반환하게 했다.
전에는 이러한 쉐이더들을 읽어들여서 blob에 저장했다가 사용하곤 했는데 Engine프로젝트의 Shader필터에 넣었던 technique, pass, shader를 어떻게 사용하는지 살펴보면
// Triangle.fx
// ...
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()));
}
};
technique안에 pass단위로 픽셀쉐이더와 버텍스쉐이더를 지정해줄수 있다. CompileShader의 첫째인자가 컴파일할 버전이고 두번째 인자가 쉐이더 이름으로 technique와 pass를 선택해서 바로 쉐이더를 고를수 있는것이다.
이를 다시 위의 TriangleDemo로 돌아가서 아래와 같이 작성했다.
쉐이더를 받아주고 삼각형을 만들것이니 VertexData를 벡터로 하나 만들어서 정점들을 정의할것이고 이를 넘길 버퍼를 추가했다. VertexData구조체에는 색상정보는 없고 position값만 있다. Init에서 방금 수정한 "01. Trangle.fx"를 쉐이더로 넘겨주고 Shader클래스에서는 쉐이더파일을 긁어와서 컴파일하도록 했다. 그리고 Shader::CreateEffect에서 자동으로 넘겨받은 파일을 자동으로 파싱해서 해석해준다. 그래서 전에 파이프라인만들고 인풋디스크립션 만들고 하던걸 생략하게 된것이다.!!
마저 TriangleDemo::Init에서는 버텍스 정보를 입력해 버텍스 버퍼에 넣어서 GPU에 넘길것이고 Render에서는 전에는 디바이스컨텍스트에서 IASetVertexBuffer 로 파이프라인에서 쓸 버텍스 버퍼를 정해주곤 했었다. 그리고 전에는 디바이스를 이용해 Draw를 했다면 흥미롭게 이번엔 _shader->Draw()를 호출해 인자로 테크닉과 패스를 정해준다.
// 2D 프레임워크에서 삼각형을 띄우기 위해 사용했던 Render 함수 코드
void Game::Render()
{
_graphics->RenderBegin();
{
uint32 stride = sizeof(VertexTextureData);
uint32 offset = 0;
auto _deviceContext = _graphics->GetDeviceContext();
// IA
_deviceContext->IASetVertexBuffers(0, 1, _vertexBuffer->GetComPtr().GetAddressOf(), &stride, &offset);
_deviceContext->IASetIndexBuffer(_indexBuffer->GetComPtr().Get(), DXGI_FORMAT_R32_UINT, 0);
_deviceContext->IASetInputLayout(_inputLayout->GetComPtr().Get());
_deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
// VS
_deviceContext->VSSetShader(_vertexShader.Get(), nullptr, 0);
_deviceContext->VSSetConstantBuffers(0, 1, _constantBuffer.GetAddressOf());
// RS
_deviceContext->RSSetState(_rasterizerState.Get());
// PS
_deviceContext->PSSetShader(_pixelShader.Get(), nullptr, 0);
_deviceContext->PSSetShaderResources(0, 1, _shaderResourveView.GetAddressOf());
_deviceContext->PSSetShaderResources(1, 1, _shaderResourveView2.GetAddressOf());
_deviceContext->PSSetSamplers(0, 1, _samplerState.GetAddressOf());
// OM
_deviceContext->OMSetBlendState(_blendState.Get(), nullptr, 0xFFFFFFFF);
//_deviceContext->Draw(_vertices.size(), 0);
_deviceContext->DrawIndexed(_geometry->GetIndexCount(), 0, 0);
}
_graphics->RenderEnd();
}
Draw의 첫번째인자가 테크닉, 두번째인자가 pass로 0,0을 하면 빨간삼각형이, 0,1을하면 초록 삼각형이 뜬다. 또 1,0을 하면 파란 삼각형이 01. Triangle.fx에서 지정했던 픽셀쉐이더에 의해 뜨게된다.
정리하면 쉐이더파일안에 테크닉을 가질수 있고 테크닉은 가장 작은 단위로 pass를 가져 그안에서 사용할 픽셀, 정점쉐이더를 지정해서 가지고있다. 일종의 계층구조처럼 shader-technique-pass를 이루고 있다. 그리고 draw함수를 더 살펴보면 Draw외에도 DrawIndexed, DrawInstanced, DrawIndexedInstanced가 있다. 그리고 살펴보면 사실 그 안은 begin/endrender사이에서 DC->Draw()를 호출하는것과 다를게 없다
다음시간엔 사각형을 띄워본다.
'[DirectX11]' 카테고리의 다른 글
| Light #2 - Diffuse (0) | 2024.08.30 |
|---|---|
| Light #1 - Ambient (0) | 2024.08.27 |
| 3D - Geometry, Sampling (0) | 2024.08.07 |
| Constant Buffer (0) | 2024.01.15 |
| 기본 프레임워크 만들기, 외부 라이브러리 추가 방법( feat. DirectXTex) (0) | 2023.11.05 |