2023. 5. 1. 01:24ㆍDirectX12
이번에 진행할 DirectX12는 어떤 후배와 같이 진행하기로 했다. 그 후배는 엔진을 만드는 학생치고는 제법 규모있는 프로젝트를 할 생각이었다. 강의를 정주행하면서 우리 프로젝트에 맞게 예제를 작성하면서 진도를 나갈 것이고 여기에 적힐 소스 코드의 대부분은 아래의 github에 업로드 될 것이다. (라이센스 주의) 그럼 시작하겠다.
https://github.com/Unknown-Stryker/Frogman_API_Lab
1. 프로젝트 생성 및 초기 예제
학교에서 뭔가 과제를 한다거나 하면 빈 프로젝트에서 시작을 했을 것이다. 그런데 이제부터는 Windows 데스크 톱 애플리케이션이라는 항목을 선택할 것이다. 이게 마이크로 소프트가 제공하는 윈도우에서 돌아가는 애플리케이션을 만드는데 쓰이는 툴이다.
프로젝트를 만들자마자 바로 실행을 하면 하얀 창이 뜨는 것을 볼 수 있다. 우리가 맨 처음 Hello World를 출력할 때와 다르게 무한 루프를 돌면서 계속 프로그램이 실행되는 것을 알 수 있다. 근데 어찌보면 당연한 것이다. 게임을 만들었는데 가만히 놔뒀더니 혼자서 꺼지면 얼마나 당황스럽겠는가? 또 다른 차이점으로 프로그램 시작 지점이 main함수가 아니라 wWinMain함수라는 것을 볼 수 있는데 하는 역할은 거의 비슷하니 어렵게 생각하지 말자.
사실 이거 말고도 처음 보는 부분이 정말 많을 것이다. 한 번도 보지 못했던 함수들이 막 나올 건데 그 함수들이 내부적으로 어떻게 동작하는지 전부 외울 필요는 없다. 일단은 어떤 역할을 하는 녀석인지 알고 넘어가는 편이 좋다. 그렇지 않으면 나무만 보느라 정작 숲을 보지 못하게 될 수 있기 때문이다. 무엇보다도 하나씩 다 살펴보는 것 자체가 꽤 힘든 일이다. 나중에 다시 공부할 때, 궁금하면 찾아봐도 충분하다.
예를 들면 코드를 보다보면 GetMessage라는 함수가 보일텐데 이 녀석의 역할은 키보드나 마우스같은 입력을 메세지로 받아오는 역할을 하는 녀석이다. 이 정도만 알아도 무방하며 GetMessage가 정확하게 내부적으로 어떻게 동작해서 입력을 메세지로 받아오는지 세세하게 다루지는 않겠다는 것이다. 다만 주의해야 할 것이 마우스, 키보드의 실질적인 입력이 들어오지 않으면 계속 대기 상태가 되기 때문에 게임을 만들겠다면 코드를 아래와 같이 바꿔줄 필요가 있다. 물론 당연히 진도를 나가면서 코드는 계속 바뀔 것이다.
// 대충 프로그램 시작하는 코드
while (true)
{
if (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE))
{
if (msg.message == WM_QUIT)
{
break;
}
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
// 대충 프로그램 루프도는 코드
}
// 대충 프로그램 끝나는 코드
기존의 코드와 비교했을 때, 사용되는 함수가 바뀐 것을 알아채야 한다. GetMessage라는 함수가 입력이 들어올 때까지 대기 상태가 걸리는 것과 반대로 PeekMessage는 Peek라는 말 그대로 입력이 들어왔는지 살펴보고 bool값을 반환하고 넘어가게 된다. 입력이 들어오면 그것들이 어딘가에 순서대로 큐에 쌓이게 되는데 PW_REMOVE라는 것을 인자로 넘기면 큐에 쌓인 것이 순서대로 나와서 msg에 바로 입력 순서대로 집어넣는 역할을 하게 된다. 즉, 큐에서 입력된 것이 지워지고 msg로 전달되는 것이다. 그리고 msg에 프로그램을 종료하라는 메시지가 담기면 프로그램이 종료되는 것이다.
2. Precompiled Header
줄여서 PCH라고 쓰고 미리 컴파일된 헤더라고 생각하면 된다. 이 헤더는 어디에서든 광범위하게 쓰이는 헤더파일 포함이나 각종 선언들을 모아놓은 헤더파일이다. 따라서 자주 쓰이기 때문에 미리 컴파일을 시켜서 빌드 시간을 아끼고 프로젝트 진행을 더 원활하게 하는 것이다. 그래서 이 기능을 사용할 것이다. 사용하는 방법은 아래와 같다.
우선 PCH를 원하는 프로젝트를 선택하고 속성→구성속성→C/C++→미리 컴파일된 헤더로 들어간다. 그럼 아래와 같은 것이 뜬다.
여기서 PCH로 사용하고 싶은 헤더파일을 골라서 저기에 적용시키면 된다. 다음과 같이 세팅하되 컴파일된 헤더 파일은 본인이 원하는 것으로 세팅하면 된다. 참고로 지정된 헤더파일에 대응되는 cpp는 1번째를 사용이 아니라 만들기로 세팅해야 한다. 나같은 경우는 wWinMain함수가 있는 프로젝트와 별개로 정적 라이브러리로 사용할 다른 프로젝트를 따로 파서 그곳에 설정했다.
위의 이미지를 보면 여러 프로젝트 파일이 있는 것을 볼 수 있는데 이 프로젝트는 팀원과 같이 진행하는 프로젝트이고 프로젝트 초창기이긴 하지만 어쨋든 내가 중간에 합류했기 때문에 다른 사람의 코드는 함부로 건들면 안된다. 근데 이 PCH라는 기능을 사용하려면 해당 프로젝트 내에 있는 모든 cpp가 설정된 PCH를 전부 포함해야 하기 때문에 코드를 수정해야 한다. 이는 매우 비효율적이기 때문에 나는 이 기능을 사용하고 싶어서 따로 내가 작업할 프로젝트 파일을 만든 것이다.
그럼 이제 본격적으로 PCH를 작성해보자. PCH에는 작업을 하기 위해서 반드시 필요하다고 생각하는 헤더 파일이나 각종 선언이나 디파인 혹은 라이브러리 파일을 포함시키는 코드를 작성하게 된다. 나는 같이 프로젝트를 진행하는 후배가 작성한 몇몇 헤더파일과 부스트 라이브러리, DX12와 관련된 라이브러리를 포함시켰다. PCH의 네이밍은 pch.h 혹은 stdafx.h 정도로 네이밍을 하는데 그냥 취향껏 하면 된다.
#ifndef _STDAFX_H_
#define _STDAFX_H_
#define WIN32_LEAN_AND_MEAN
#include "resource.h"
#include <SDKDDKVer.h>
#include <d3d12.h>
#include <wrl.h>
#include <d3dcompiler.h>
#include <dxgi.h>
#include <DirectXMath.h>
#include <DirectXPackedVector.h>
#include <DirectXColors.h>
#pragma comment(lib, "d3d12")
#pragma comment(lib, "dxgi")
#pragma comment(lib, "dxguid")
#pragma comment(lib, "d3dcompiler")
using vec2 = DirectX::XMFLOAT2;
using vec3 = DirectX::XMFLOAT3;
using vec4 = DirectX::XMFLOAT4;
using matrix = DirectX::XMMATRIX;
#endif
참고로 PCH에 대한 설정을 할 때, 최소 하나의 cpp파일에 우클릭을 한 후에 아까처럼 미리 컴파일된 헤더에 대한 설정으로 들어가서 사용이 아니라 만들기로 설정해야 한다. 아래처럼 말이다.
3. lib 파일 만들기 및 설정
이번 내용은 살짝 길며 간단하게 정적 라이브러리와 동적 라이브러리가 뭔지 아는 것이 좋다. 내가 쓴 글인데 모른다면 읽고 오는 것도 좋겠지만 사실 그냥 프로젝트 설정하는 거라서 몰라도 그렇게 큰 지장은 없다. 다만 알고 있다고 가정하고 쓴 글이라는 것만 알아줬으면 좋겠다. 아래 링크에서 3번 내용을 읽고 오면 된다.
https://dafher-diary.tistory.com/4
우리가 지금까지 Visual Studio로 뭔가를 만들었을 때, 그것이 exe를 만들기 위한 것이었다면 지금부터 만들 것은 실행 파일을 만드는 것이 아니라 게임이라는 실행 파일을 만들기 위해 정적 라이브러리를 만드는 작업을 하게 될 것이다. 아래와 같이 솔루션에 마우스를 대고 우클릭을 한 뒤에 새 프로젝트를 추가할 수 있다.
그러면 아래와 같은 이미지가 나오는데 여기서 마우스 스크롤을 좀 내리다 보면 정적 라이브러리를 만드는 선택지가 보일 것이다. 그걸 누르면 이제 그 프로젝트는 실행 파일 즉, exe 파일을 만드는 프로젝트가 아니라 정적 라이브러리를 만들기 위한 프로젝트가 만들어진다.
오른쪽의 솔루션 탐색기를 자세히 보면 Frogman-Library라는 프로젝트 파일이 있는 것을 볼 수 있을텐데 그게 사실 정적 라이브러리로 만들어진 것이다. 우선 정적 라이브러리를 만들고 위에서 설명한 PCH 역시 이 프로젝트에서 만들었다. 이제 Frogman-Library 프로젝트의 소스 파일과 Frogman-Engine 프로젝트의 소스 파일이 서로 간편하게 인클루드를 할 수 있도록 만들 것이다.
이게 무슨 말이냐면 서로 다른 프로젝트 파일에 있는 헤더 파일들은 서로 다른 폴더 경로에 존재하게 된다. 보통 프로젝트 별로 폴더를 구분하기 때문에 그렇다. 그럼 어떤 프로젝트 파일의 소스 코드에서 다른 프로젝트 파일에 있는 헤더를 인클루드 하려면 #include를 한 후에 자신의 솔루션이나 프로젝트 파일의 위치를 기준으로 계속 타고가는 과정을 타이핑해야 한다. 이게 매우 귀찮고 보기도 싫기 때문에 이런 설정을 하는 것이다. 작동 자체에는 별 문제가 없겠지만... 그래도 나는 할 것이다.
정적 라이브러리와 메인 프로젝트 파일끼리 서로 편하게 인클루드를 시켜주기 위해서 아래와 같이 프로젝트 속성을 열어준다. 그럼 속성→구성 속성→C/C++→일반→추가 포함 디렉터리를 보면 어떤 경로를 입력하는 부분이 보일 것이다. 거기에 본인이 원하는 경로를 입력하면 헤더 파일을 찾을 때, 해당 경로도 같이 반영하게 된다. 그래서 미리 저기에 경로를 입력하면 귀찮은 타이핑이 필요없다.
다만 상대 경로로 입력하는 것이 좋다. $(Solution)과 같은 환경 변수를 이용하면 솔루션 경로가 자동으로 반영되며 이걸 이용해서 자동으로 이 라이브러리를 사용하는 사용자를 기준으로 헤더 파일을 탐색하게 만들 수 있는 것이다. 절대 경로로 하면 본인의 작업 환경이 바뀌었을 때, 다시 경로 설정을 해야하기 때문에 매우 번거롭다고 할 수 있다. 이 과정을 각각 메인 프로젝트 파일과 정적 라이브러리 프로젝트 파일에 진행하면 된다.
이번에는 정적 라이브러리를 빌드했을 때, 해당 .lib 파일이 어떤 경로로 나오게 할 것인지 설정하는 부분인데 이것 역시 원하는 경로를 입력하면 된다. 속성→구성속성→일반→출력 디렉터리를 원하는 경로로 설정하자. 기왕이면 결과물까지 프로젝트 단위로 깔끔하게 관리하는 것이 좋을테니 말이다.
이제 메인 프로젝트 파일에서 .lib 프로젝트 파일의 결과물로 나온 .lib 파일과 헤더 파일을 참조하는 방법을 알아볼 것이다. 여기까지만 하면 프로젝트 세팅이 끝난다. 아래의 이미지를 보면 메인 프로젝트 파일에 우클릭으로 속성으로 들어가서 구성속성→VC++ 디렉터리→라이브러리 디렉터리에서 경로를 입력한 모습을 볼 수 있다. 아까 본인이 .lib 파일이 빌드가 되었을 때, 어디로 나오는지 보고 그 경로를 여기에 적어주면 된다. 포함 디렉터리라는 것도 있는데 본인이 원하는 헤더 파일이 있는 경로를 역시 여기에 적어주면 된다. 물론 #pragma comment를 이용해서 추가할 수 있지만 그건 본인 취향껏 하면 된다.
이제 stdafx.h에서 전역 함수 하나 선언해주고 다른 곳에서 정의한 다음 메인 프로젝트에서 실행이 아래와 같이 잘 되면 프로젝트 세팅은 끝이 난다.
'DirectX12' 카테고리의 다른 글
DirectX12 개념 블로그 정주행 1회차 - Device와 CommandQueue (0) | 2023.06.08 |
---|---|
루키스 게임수학 정주행 3회차 - 장치 심화 (0) | 2023.06.06 |
루키스 게임수학 정주행 2회차 - 번외(버그) (0) | 2023.05.17 |
루키스 게임수학 정주행 2회차 - 장치 초기화 (2) | 2023.05.10 |
루키스 게임수학 정주행 - Intro (0) | 2023.04.17 |