[DirectX11] Tutorial 2 - 프레임워크와 윈도우 만들기
Tutorial 2 - 프레임워크와 윈도우 만들기
원문 : http://www.rastertek.com/dx11s2tut02.html
DirectX 11로 코드를 작성하기 전에 간단한 코드 프레임 워크를 작성하는 것이 좋습니다. 차후에 작성될 대량의 효과적인 코드 확장 및 관리를 위해서는 프레임워크를 작성하여 클래스 구조로 뼈대를 세우는것이 가장 좋은 방법입니다.
프레임 워크
프레임 작업은 네 가지 항목으로 시작됩니다. 응용 프로그램의 진입 점을 처리하는 WinMain 함수가 있습니다. 또한 WinMain 함수 내에서 호출 될 전체 응용 프로그램을 캡슐화하는 시스템 클래스가 있습니다. 시스템 클래스 안에는 사용자 입력을 처리하기위한 입력 클래스와 DirectX 그래픽 코드를 처리하기위한 그래픽 클래스가 있습니다. 다음은 프레임 워크 설정 다이어그램입니다.
이제 프레임 워크가 어떻게 설정 될지 살펴 보았습니다.
우선 전방선언 사용의 이점과 빌드 시간 감축을 위해서 미리 컴파일된 헤더(stdafx.h)의 이점을 사용하기 위해서 미리 사용될 헤더파일을 따로 작성 'DxDefine.h' 하여 만들도록 하겠습니다.
Stdafx.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | // stdafx.h : 자주 사용하지만 자주 변경되지는 않는 // 표준 시스템 포함 파일 또는 프로젝트 관련 포함 파일이 // 들어 있는 포함 파일입니다. // #pragma once #include "targetver.h" #define WIN32_LEAN_AND_MEAN // 거의 사용되지 않는 내용은 Windows 헤더에서 제외합니다. // Windows 헤더 파일: #include <windows.h> // C 런타임 헤더 파일입니다. #include <stdlib.h> #include <malloc.h> #include <memory.h> #include <tchar.h> // TODO: 프로그램에 필요한 추가 헤더는 여기에서 참조합니다. #include "DxDefine.h" |
22 번째 코드 한줄을 추가합니다. 다음은 DxDefine.h 파일의 내용을 살펴보겠습니다.
DxDefine.h
1 2 3 | #pragma once |
지금은 내용이 없지만 다음 편 부터는 directx sdk를 사용하기 위한 헤더 및 라이브러리 파일 코드를 넣을 것입니다.
다음은 main.cpp 파일의 WinMain 함수를 살펴 보겠습니다.
WinMain
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | // Dx11Demo_02.cpp: 응용 프로그램의 진입점을 정의합니다. // #include "stdafx.h" #include "SystemClass.h" int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow) { // System 객체 생성 SystemClass* System = new SystemClass; if (!System) { return -1; } // System 객체 초기화 및 실행 if (System->Initialize()) { System->Run(); } // System 객체 종료 및 메모리 반환 System->Shutdown(); delete System; System = nullptr; return 0; } |
보시다시피 WinMain 함수는 매우 간단하게 유지했습니다. 시스템 클래스 생성, 초기화, 실행, 종료, 반환 순서로 구동됩니다. 따라서 우리는 매우 단순하게 유지하고 전체 애플리케이션을 시스템 클래스 내에 캡슐화했습니다.
이제 시스템 클래스 헤더 파일을 살펴 보겠습니다.
SystemClass.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | #pragma once class InputClass; class GraphicsClass; class SystemClass { public: SystemClass(); SystemClass(const SystemClass&); ~SystemClass(); bool Initialize(); void Shutdown(); void Run(); LRESULT CALLBACK MessageHandler(HWND, UINT, WPARAM, LPARAM); private: bool Frame(); void InitializeWindows(int&, int&); void ShutdownWindows(); private: LPCWSTR m_applicationName; HINSTANCE m_hinstance; HWND m_hwnd; InputClass* m_Input = nullptr; GraphicsClass* m_Graphics = nullptr; }; static LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); static SystemClass* ApplicationHandle = 0; |
클래스의 정의는 매우 간단합니다. 여기에 정의 된 WinMain에서 호출 된 Initialize, Shutdown 및 Run 함수가 표시됩니다. 또한 이러한 함수 내에서 호출되는 일부 개인 함수가 있습니다. 우리는 클래스에 MessageHandler 함수를 두어 응용 프로그램이 실행되는 동안 응용 프로그램에 전송 될 Windows 시스템 메시지를 처리합니다. 마지막으로 우리는 그래픽과 입력을 처리 할 두 객체에 대한 포인터가 될 m_Input과 m_Graphics라는 클래스 변수를 가지고 있습니다. 또한 전방 선언을 통해 빠른 컴파일을 작성할 수 있도록 하였습니다.
간단히 설명하자면 SystemClass는 WInAPI 윈도우 프로그래밍을 클래스화 한 것입니다
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 | #include "stdafx.h" #include "inputclass.h" #include "graphicsclass.h" #include "systemclass.h" SystemClass::SystemClass() { } SystemClass::SystemClass(const SystemClass& other) { } SystemClass::~SystemClass() { } bool SystemClass::Initialize() { // 윈도우 창 가로, 세로 넓이 변수 초기화 int screenWidth = 0; int screenHeight = 0; // 윈도우 생성 초기화 InitializeWindows(screenWidth, screenHeight); // m_Input 객체 생성. 이 클래스는 추후 사용자의 키보드 입력 처리에 사용됩니다. m_Input = new InputClass; if(!m_Input) { return false; } // m_Input 객체 초기화 m_Input->Initialize(); // m_Graphics 객체 생성. 그래픽 랜더링을 처리하기 위한 객체입니다. m_Graphics = new GraphicsClass; if(!m_Graphics) { return false; } // m_Graphics 객체 초기화. return m_Graphics->Initialize(screenWidth, screenHeight, m_hwnd); } void SystemClass::Shutdown() { // m_Graphics 객체 반환 if(m_Graphics) { m_Graphics->Shutdown(); delete m_Graphics; m_Graphics = 0; } // m_Input 객체 반환 if(m_Input) { delete m_Input; m_Input = 0; } // Window 종료 처리 ShutdownWindows(); } void SystemClass::Run() { // 메시지 구조체 생성 및 초기화 MSG msg; ZeroMemory(&msg, sizeof(MSG)); // 사용자로부터 종료 메시지를 받을때까지 메시지루프를 돕니다 while(true) { // 윈도우 메시지를 처리합니다 if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { // 종료 메시지를 받을 경우 메시지 루프를 탈출합니다 if (msg.message == WM_QUIT) break; TranslateMessage(&msg); DispatchMessage(&msg); } else { // 그 외에는 Frame 함수를 처리합니다. if (!Frame()) break; } } } bool SystemClass::Frame() { // ESC 키 감지 및 종료 여부를 처리합니다 if(m_Input->IsKeyDown(VK_ESCAPE)) { return false; } // 그래픽 객체의 Frame을 처리합니다 return m_Graphics->Frame(); } LRESULT CALLBACK SystemClass::MessageHandler(HWND hwnd, UINT umsg, WPARAM wparam, LPARAM lparam) { switch(umsg) { // 키보드가 눌러졌는가 처리 case WM_KEYDOWN: { // 키 눌림 flag를 m_Input 객체에 처리하도록 합니다 m_Input->KeyDown((unsigned int)wparam); return 0; } // 키보드가 떨어졌는가 처리 case WM_KEYUP: { // 키 해제 flag를 m_Input 객체에 처리하도록 합니다. m_Input->KeyUp((unsigned int)wparam); return 0; } // 그 외의 모든 메시지들은 기본 메시지 처리로 넘깁니다. default: { return DefWindowProc(hwnd, umsg, wparam, lparam); } } } void SystemClass::InitializeWindows(int& screenWidth, int& screenHeight) { // 외부 포인터를 이 객체로 지정합니다 ApplicationHandle = this; // 이 프로그램의 인스턴스를 가져옵니다 m_hinstance = GetModuleHandle(NULL); // 프로그램 이름을 지정합니다 m_applicationName = L"Dx11Demo_02"; // windows 클래스를 아래와 같이 설정합니다. WNDCLASSEX wc; wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; wc.lpfnWndProc = WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = m_hinstance; wc.hIcon = LoadIcon(NULL, IDI_WINLOGO); wc.hIconSm = wc.hIcon; wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); wc.lpszMenuName = NULL; wc.lpszClassName = m_applicationName; wc.cbSize = sizeof(WNDCLASSEX); // windows class를 등록합니다 RegisterClassEx(&wc); // 모니터 화면의 해상도를 읽어옵니다 screenWidth = GetSystemMetrics(SM_CXSCREEN); screenHeight = GetSystemMetrics(SM_CYSCREEN); int posX = 0; int posY = 0; // FULL_SCREEN 변수 값에 따라 화면을 설정합니다. if(FULL_SCREEN) { // 풀스크린 모드로 지정했다면 모니터 화면 해상도를 데스크톱 해상도로 지정하고 색상을 32bit로 지정합니다. DEVMODE dmScreenSettings; memset(&dmScreenSettings, 0, sizeof(dmScreenSettings)); dmScreenSettings.dmSize = sizeof(dmScreenSettings); dmScreenSettings.dmPelsWidth = (unsigned long)screenWidth; dmScreenSettings.dmPelsHeight = (unsigned long)screenHeight; dmScreenSettings.dmBitsPerPel = 32; dmScreenSettings.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT; // 풀스크린으로 디스플레이 설정을 변경합니다. ChangeDisplaySettings(&dmScreenSettings, CDS_FULLSCREEN); } else { // 윈도우 모드의 경우 800 * 600 크기를 지정합니다. screenWidth = 800; screenHeight = 600; // 윈도우 창을 가로, 세로의 정 가운데 오도록 합니다. posX = (GetSystemMetrics(SM_CXSCREEN) - screenWidth) / 2; posY = (GetSystemMetrics(SM_CYSCREEN) - screenHeight) / 2; } // 윈도우를 생성하고 핸들을 구합니다. m_hwnd = CreateWindowEx(WS_EX_APPWINDOW, m_applicationName, m_applicationName, WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_POPUP, posX, posY, screenWidth, screenHeight, NULL, NULL, m_hinstance, NULL); // 윈도우를 화면에 표시하고 포커스를 지정합니다 ShowWindow(m_hwnd, SW_SHOW); SetForegroundWindow(m_hwnd); SetFocus(m_hwnd); } void SystemClass::ShutdownWindows() { // 풀스크린 모드였다면 디스플레이 설정을 초기화합니다. if(FULL_SCREEN) { ChangeDisplaySettings(NULL, 0); } // 창을 제거합니다 DestroyWindow(m_hwnd); m_hwnd = NULL; // 프로그램 인스턴스를 제거합니다 UnregisterClass(m_applicationName, m_hinstance); m_hinstance = NULL; // 외부포인터 참조를 초기화합니다 ApplicationHandle = NULL; } LRESULT CALLBACK WndProc(HWND hwnd, UINT umessage, WPARAM wparam, LPARAM lparam) { switch(umessage) { // 윈도우 종료를 확인합니다 case WM_DESTROY: { PostQuitMessage(0); return 0; } // 윈도우가 닫히는지 확인합니다 case WM_CLOSE: { PostQuitMessage(0); return 0; } // 그 외의 모든 메시지들은 시스템 클래스의 메시지 처리로 넘깁니다. default: { return ApplicationHandle->MessageHandler(hwnd, umessage, wparam, lparam); } } } |
튜토리얼을 간결하게 유지하기 위해 DirectInput Tutorial을 사용할 때까지 당분간 윈도우 입력을 사용하도록 하겠습니다. 입력 클래스는 키보드의 사용자 입력을 처리합니다. 이 클래스는 SystemClass :: MessageHandler 함수로부터 입력을받습니다. 입력 객체는 키보드 배열에 각 키의 상태를 저장합니다. 질의를 받으면 특정 키가 눌려 졌는지를 호출 함수에 알려줍니다. 다음은 헤더입니다.
InputClass.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | #pragma once class InputClass { public: InputClass(); InputClass(const InputClass&); ~InputClass(); void Initialize(); void KeyDown(unsigned int); void KeyUp(unsigned int); bool IsKeyDown(unsigned int); private: bool m_keys[256]; }; |
InputClass.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | #include "stdafx.h" #include "inputclass.h" InputClass::InputClass() { } InputClass::InputClass(const InputClass& other) { } InputClass::~InputClass() { } void InputClass::Initialize() { // 키 배열을 초기화합니다 for(int i=0; i<256; ++i) { m_keys[i] = false; } } void InputClass::KeyDown(unsigned int input) { // 키가 눌렸다면 해당 키값을 true로 저장합니다 m_keys[input] = true; } void InputClass::KeyUp(unsigned int input) { // 키가 해제되었다면 해당 키값을 false로 저장합니다 m_keys[input] = false; } bool InputClass::IsKeyDown(unsigned int key) { // 현재 키값이 눌려졌는지 아닌지 상태를 반환합니다 return m_keys[key]; } |
그래픽 클래스는 시스템 클래스에 의해 생성되는 다른 객체입니다. 이 응용 프로그램의 모든 그래픽 기능은이 클래스에 캡슐화됩니다. 또한 이 파일의 헤더를 전체 화면 또는 창 모드와 같이 변경할 수있는 모든 그래픽 관련 전역 설정에 사용합니다. 현재 이 클래스는 비어 있지만 나중에 튜토리얼에는 모든 그래픽 객체가 포함됩니다.
GraphicsClass.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | #pragma once ///////////// // GLOBALS // ///////////// const bool FULL_SCREEN = false; const bool VSYNC_ENABLED = true; const float SCREEN_DEPTH = 1000.0f; const float SCREEN_NEAR = 0.1f; class GraphicsClass { public: GraphicsClass(); GraphicsClass(const GraphicsClass&); ~GraphicsClass(); bool Initialize(int, int, HWND); void Shutdown(); bool Frame(); private: bool Render(); }; |
GraphicsClass.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | #include "stdafx.h" #include "graphicsclass.h" GraphicsClass::GraphicsClass() { } GraphicsClass::GraphicsClass(const GraphicsClass& other) { } GraphicsClass::~GraphicsClass() { } bool GraphicsClass::Initialize(int screenWidth, int screenHeight, HWND hwnd) { return true; } void GraphicsClass::Shutdown() { } bool GraphicsClass::Frame() { return true; } bool GraphicsClass::Render() { return true; } |
출력 화면
마치면서
이제 우리는 기본 프레임 워크를 작성하였고 그 프래임워크를 바탕으로 화면에 윈도우 창을 띄울수 있게 되었습니다. 이 프레임워크는 향후 모든 듀토리얼의 기반이 될 것이므로이 프레임워크를 이해하는 것이 매우 중요합니다. 다음 튜토리얼로 넘어 가기 전에 아래 연습문제를 수행하여 코드가 컴파일되고 작동하는지 확인하십시오. 이 프레임워크를 이해하지 못하더라도 자신감을 가지고 다음 듀토리얼로 넘어가는 것이 좋습니다. 다음 듀토리얼들을 진행하면서 프레임워크를 조금씩 작성하며 배우다보면 나중에 좀더 이해할 수 있을 것입니다.
연습문제
graphicsclass.h 헤더에서 FULL_SCREEN 매개 변수를 true로 변경 한 다음 프로그램을 다시 컴파일하고 실행하십시오. 창을 표시 한 후에 종료하려면 ESC 키를 누릅니다.
소스코드
소스코드 : Dx11Demo_02.zip
'DirectX 11 > Basic' 카테고리의 다른 글
[DirectX11] Tutorial 5 - 텍스쳐 (4) | 2017.11.29 |
---|---|
[DirectX11] Tutorial 4 - 버퍼, 쉐이더 및 HLSL (7) | 2017.11.28 |
[DirectX11] Warning - warning C4316 에러 문제 (1) | 2017.11.27 |
[DirectX11] Tutorial 3 - DirectX 11 초기화 (8) | 2017.11.27 |
[DirectX11] Tutorial 1 - Visual Studio에서의 DirectX11 설정 (3) | 2017.11.26 |