Coding Feature.

[DirectX 11] 1. 개발을 위한 간단한 프레임워크 만들기 본문

Computer Graphics/DirectX 11

[DirectX 11] 1. 개발을 위한 간단한 프레임워크 만들기

codingfeature 2023. 12. 27. 15:24

(개인 공부용으로 정리한 내용입니다! 수정이 필요한 부분 발견 시 피드백 부탁드립니다!)

 

아래 사이트를 직접적으로 참고해서 작성한 내용입니다!

https://www.rastertek.com/tutdx11win10.html

 

DirectX 11 on Windows 10 Tutorials

Tutorial 8: Scaling, Rotation, and Translation

www.rastertek.com

 

앞으로 개발하기 위한 간단한 프레임워크를 만들어야 합니다.

 

DirectX 개발 공부를 하기 전에 프레임워크를 짜놓으면 추후에 다양한 기능을 추가 및 변경하고 싶을때 더욱 편리하게 해줄 수 있습니다. 즉 프로젝트 관리를 더욱 쉽게 할 수 있습니다.

 

우선 프레임워크를 다음과 같은 구조로 구현합니다.

 

WinMain 함수는 일반적인 main 함수와 같이 프로그램의 Entry Point 역할을 합니다.

 

또한 SystemClass는 애플리케이션 전체를 캡슐화합니다. SystemClass 내부에는 사용자의 입력을 처리하는 InputClass와 DirectX와 연관된 처리를 하는 ApplicationClass를 구현합니다.

 

 

우선 WinMain을 구현합니다.

#include "systemclass.h"


int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR pScmdline, int iCmdshow)
{
	SystemClass* System;
	bool result;
	
	
	// SystemClass 오브젝트 생성.
	System = new SystemClass;

	// 시스템 오브젝트 초기화 및 result 반환.
	result = System->Initialize();
	if(result)
	{
		System->Run(); // 시스템 오브젝트 초기화가 잘 된 경우 Run() 실행
	}

	// Shutdown() 함수 실행 및 오브젝트 삭제.
	System->Shutdown();
	delete System;
	System = 0;

	return 0;
}

 

SystemClass 오브젝트를 먼저 생성하고 Initialize 함수를 실행합니다. 초기화가 문제 없이 된 경우 Run 함수를 실행합니다. 그리고 Run 함수가 끝나고 Shutdown 함수를 실행하고 시스템 오브젝트를 삭제합니다.

 

 

그 다음 systemclass.h를 구현합니다.

#ifndef _SYSTEMCLASS_H_
#define _SYSTEMCLASS_H_

#define WIN32_LEAN_AND_MEAN

#include <windows.h>

#include "inputclass.h"
#include "applicationclass.h"

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;
	ApplicationClass* m_Application;
};

static LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

static SystemClass* ApplicationHandle = 0;

#endif

 

systemclass는 Initialize, Shutdown, Run 함수를 통해 시스템(Input, 그래픽 렌더링 application)을 초기화하거나 실행 또는 종료합니다.

 

또한 메시지 핸들러를 구현해서 윈도우즈 시스템 메시지를 받고 애플리케이션으로 전송하도록 해줍니다.

 

WndProc와 ApplicationHandle은 윈도우즈에서 생성된 메시지를 메시지 핸들러로 리다이렉트를 한다고는 적혀있는데 잘 이해가 되지 않습니다.. ㅠㅠ 나중에 구현하면서 이해해보도록 하겠습니다.

 

 

다음은 systemclass.cpp 입니다.

#include "systemclass.h"

SystemClass::SystemClass()
{
	m_Input = 0;
	m_Application = 0;
}

SystemClass::SystemClass(const SystemClass& other)
{
}


SystemClass::~SystemClass()
{
}

bool SystemClass::Initialize()
{
	int screenWidth, screenHeight;
	bool result;

	screenWidth = 0;
	screenHeight = 0;

	// windows api 초기화.
	InitializeWindows(screenWidth, screenHeight);

	m_Input = new InputClass;
	m_Input->Initialize();

	m_Application = new ApplicationClass;

	result = m_Application->Initialize(screenWidth, screenHeight, m_hwnd);
	if (!result)
	{
		return false;
	}

	return true;
}

void SystemClass::Shutdown()
{
	// Application -> Input -> Windows 종료

	if (m_Application)
	{
		m_Application->Shutdown();
		delete m_Application;
		m_Application = 0;
	}

	if (m_Input)
	{
		delete m_Input;
		m_Input = 0;
	}

	ShutdownWindows();

	return;
}

void SystemClass::Run()
{
	MSG msg;
	bool done, result;

	// 메시지 구조 초기화.
	ZeroMemory(&msg, sizeof(MSG));

	done = false;
	while (!done)
	{
		// 윈도우즈 메시지 핸들링.
		if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}

		// 메시지가 종료일 때
		if (msg.message == WM_QUIT)
		{
			done = true;
		}
		else
		{
			// 프레임 프로세싱.
			result = Frame();
			if (!result)
			{
				done = true;
			}
		}

	}

	return;
}

bool SystemClass::Frame()
{
	bool result;

	// ESC 키를 눌렀을 경우,
	if (m_Input->IsKeyDown(VK_ESCAPE))
	{
		return false;
	}

	// 애플리케이션 프레임 프로세싱
	result = m_Application->Frame();
	if (!result)
	{
		return false;
	}

	return true;
}

LRESULT CALLBACK SystemClass::MessageHandler(HWND hwnd, UINT umsg, WPARAM wparam, LPARAM lparam)
{
	switch (umsg)
	{

	case WM_KEYDOWN:
	{
		// input 오브젝트에 전달.
		m_Input->KeyDown((unsigned int)wparam);
		return 0;
	}

	case WM_KEYUP:
	{
		// input 오브젝트에 전달.
		m_Input->KeyUp((unsigned int)wparam);
		return 0;
	}

	// 나머지 메시지는 기존과 같은 방식으로 처리.
	default:
	{
		return DefWindowProc(hwnd, umsg, wparam, lparam);
	}
	}
}

void SystemClass::InitializeWindows(int& screenWidth, int& screenHeight)
{
	WNDCLASSEX wc;
	DEVMODE dmScreenSettings;
	int posX, posY;

	ApplicationHandle = this;

	// Get the instance of this application.
	m_hinstance = GetModuleHandle(NULL);

	// Give the application a name.
	m_applicationName = L"Engine";

	// Setup the windows class with default settings.
	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);

	// Register the window class.
	RegisterClassEx(&wc);

	// Determine the resolution of the clients desktop screen.
	screenWidth = GetSystemMetrics(SM_CXSCREEN);
	screenHeight = GetSystemMetrics(SM_CYSCREEN);

	// Setup the screen settings depending on whether it is running in full screen or in windowed mode.
	if (FULL_SCREEN)
	{
		// If full screen set the screen to maximum size of the users desktop and 32bit.
		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;

		// Change the display settings to full screen.
		ChangeDisplaySettings(&dmScreenSettings, CDS_FULLSCREEN);

		// Set the position of the window to the top left corner.
		posX = posY = 0;
	}
	else
	{
		// If windowed then set it to 800x600 resolution.
		screenWidth = 800;
		screenHeight = 600;

		// Place the window in the middle of the screen.
		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);

	// Bring the window up on the screen and set it as main focus.
	ShowWindow(m_hwnd, SW_SHOW);
	SetForegroundWindow(m_hwnd);
	SetFocus(m_hwnd);

	// 마우스 커서 보이도록 설정
	ShowCursor(true);

	return;
}

void SystemClass::ShutdownWindows()
{
	// Show the mouse cursor.
	ShowCursor(true);

	// Fix the display settings if leaving full screen mode.
	if (FULL_SCREEN)
	{
		ChangeDisplaySettings(NULL, 0);
	}

	// Remove the window.
	DestroyWindow(m_hwnd);
	m_hwnd = NULL;

	// Remove the application instance.
	UnregisterClass(m_applicationName, m_hinstance);
	m_hinstance = NULL;

	// Release the pointer to this class.
	ApplicationHandle = NULL;

	return;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT umessage, WPARAM wparam, LPARAM lparam)
{
	switch (umessage)
	{
		// Check if the window is being destroyed.
	case WM_DESTROY:
	{
		PostQuitMessage(0);
		return 0;
	}

	// Check if the window is being closed.
	case WM_CLOSE:
	{
		PostQuitMessage(0);
		return 0;
	}

	// All other messages pass to the message handler in the system class.
	default:
	{
		return ApplicationHandle->MessageHandler(hwnd, umessage, wparam, lparam);
	}
	}
}

 

 

여기서 Run 함수는 다음 내용을 Loop합니다.

윈도우즈 시스템 메시지 확인 -> 메시지 처리 -> 애플리케이션 루프 -> 종료에 대한 처리 -> (반복)

 

Loop를 진행하면서 Frame 함수를 실행하는데, 현재로써는 Frame 내에는 사용자가 ESC키를 누르는지만 확인하도록 되어있습니다.

 

그리고 메시지 핸들러에 윈도우즈의 메시지를 Feed 해줘서 특정 메시지에 대한 처리를 구현할 수 있습니다.

 

WndProc 함수는 윈도우즈의 메시지가 바로 Feed되는 곳입니다.

	// Setup the windows class with default settings.
..
	wc.lpfnWndProc = WndProc;
..

윈도우를 초기화할 때 위와 같이 구현해줬기 때문입니다!

 

 

input 클래스에 대한 헤더와 cpp 파일을 다음과 같이 구현해줍니다.

//inputclass.h
#ifndef _INPUTCLASS_H_
#define _INPUTCLASS_H_

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];
};

#endif
// inputclass.cpp

#include "inputclass.h"

InputClass::InputClass()
{
}


InputClass::InputClass(const InputClass& other)
{
}


InputClass::~InputClass()
{
}


void InputClass::Initialize()
{
	int i;

	// 모든 키에 대한 array를 초기화.
	for (i = 0; i < 256; i++)
	{
		m_keys[i] = false;
	}

	return;
}


void InputClass::KeyDown(unsigned int input)
{
	// 키를 눌렀을 경우, true state로 입력.
	m_keys[input] = true;
	return;
}


void InputClass::KeyUp(unsigned int input)
{
	// 키를 눌렀다가 뗀 경우, false state로 입력.
	m_keys[input] = false;
	return;
}


bool InputClass::IsKeyDown(unsigned int key)
{
	// 키가 눌렸는지 state 반환.
	return m_keys[key];
}

 

 

키가 눌렸는지 확인하기 위해 m_keys라는 boolean array를 사용합니다!

 

그 다음 applicationclass를 구현해줍니다.

//applicationclass.h

#ifndef _APPLICATIONCLASS_H_
#define _APPLICATIONCLASS_H_

#include <windows.h>

const bool FULL_SCREEN = true;
const bool VSYNC_ENABLED = true;
const float SCREEN_DEPTH = 1000.0f;
const float SCREEN_NEAR = 0.3f;

class ApplicationClass
{
public:
	ApplicationClass();
	ApplicationClass(const ApplicationClass&);
	~ApplicationClass();

	bool Initialize(int, int, HWND);
	void Shutdown();
	bool Frame();

private:
	bool Render();

private:

};

#endif
// applicationclass.cpp

#include "applicationclass.h"


ApplicationClass::ApplicationClass()
{
}


ApplicationClass::ApplicationClass(const ApplicationClass& other)
{
}


ApplicationClass::~ApplicationClass()
{
}


bool ApplicationClass::Initialize(int screenWidth, int screenHeight, HWND hwnd)
{

	return true;
}


void ApplicationClass::Shutdown()
{

	return;
}


bool ApplicationClass::Frame()
{

	return true;
}


bool ApplicationClass::Render()
{

	return true;
}

 

'Computer Graphics > DirectX 11' 카테고리의 다른 글

[DirectX 11] 0. DirectX와 환경 세팅.  (0) 2023.12.26