Tutorial 2: Creating a Framework and Window
원문: http://www.rastertek.com/dx11tut02.html
저는 우선 DirectX 11 코딩을 시작하기보다는 간단한 코드 프레임워크를 만들어 두는 것을 추천합니다. 이 프레임워크는 간단한 윈도우 기능들을 제어하고 코드의 가독성을 높여주고 전체적으로 잘 설계되게 해줍니다. 이 연재물의 의도는 DIrectX 11의 달라진 기능들을 살펴보기 위함이기 때문에 가능한 한 가볍게 하겠습니다.
프레임워크
프레임워크는 네 가지 요소로 시작합니다. 우선 이 어플리케이션의 시작점이 되는 WinMain 함수가 있습니다. 또한 전체 어플리케이션을 캡슐화하는 SystemClass
가 있으며 WinMain
함수에서 사용합니다. SystemClass
의 내부에는 유저의 임력을 처리하는 InputClass
와 DirectX 그래픽 코드가 있는 GraphicsClass
가 있습니다. 이 설정을 다이어그램으로 표현하면 다음과 같습니다.
이제 어떻게 프레임워크가 실제로 설정되는지 보기 위해 우선 main.cpp
의 Winmain
함수부터 시작하도록 하겠습니다.
WinMain
////////////////////////////////////////////////////////////////////////////////
// Filename: main.cpp
////////////////////////////////////////////////////////////////////////////////
#include "systemclass.h"
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR pScmdline, int iCmdshow)
{
SystemClass* System;
bool result;
// system 객체를 생성한다.
System = new SystemClass;
if(!System)
{
return 0;
}
// system 객체를 초기화하고 run을 호출한다.
result = System->Initialize();
if(result)
{
System->Run();
}
// system객체를 종료하고 메모리를 반환한다.
System->Shutdown();
delete System;
System = 0;
return 0;
}
WinMain
함수는 상당히 간단합니다. 우리는 SystemClass
를 생성한 뒤 초기화합니다. 만일 아무 문제 없이 초기화되면 SystemClass::Run
함수를 호출합니다. SystemClass::Run
함수는 내부적으로 루프를 가지고 있으며 종료될 때까지 모든 어플리케이션 코드를 실행합니다. SystemClass::Run
함수가 종료되면 Shutdown
함수를 호출한 뒤 SystemClass
객체를 를 정리합니다. 이렇게 모든 작업을 SystemClass
안에 캡슐화하여 코드를 간단하게 할 수 있습니다. 이제 SystemClass
의 헤더 파일을 보죠.
Systemclass.h
////////////////////////////////////////////////////////////////////////////////
// Filename: systemclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _SYSTEMCLASS_H_
#define _SYSTEMCLASS_H_
여기서 WIN32_LEAN_AND_MEAN
을 define
으로 선언합니다. 이렇게 하는 이유는 자주 사용되지 않는 API들을 담고 있는 Win32헤더를 포함하지 않음으로 빌드 속도를 높이기 위해서입니다.
///////////////////////////////
// PRE-PROCESSING DIRECTIVES //
///////////////////////////////
#define WIN32_LEAN_AND_MEAN
Windows.h
를 선언해야 우리가 윈도우를 생성/제거하는 함수들을 호출할 수 있으며 다른 유용한 win32함수들을 사용할 수 있습니다.
//////////////
// INCLUDES //
//////////////
#include <windows.h>
SystemClass
에서 사용하기 위한 프레임워크의 다른 두 클래스의 헤더를 포합합니다.
///////////////////////
// MY CLASS INCLUDES //
///////////////////////
#include "inputclass.h"
#include "graphicsclass.h"
클래스의 정의는 꽤 간단합니다. 앞서 WinMain
함수에서 사용했던 Initialize
, Shutdown
, 그리고 Run
함수를 볼 수 있습니다. 또한 위 함수들에서 사용하는 몇몇 private로 선언된 함수들이 있습니다. 그리고 클래스에 MessageHandler
이라는 함수를 넣어 윈도우로부터 오는 시스템 메세지들을 어플리케이션이 돌아가는 동안 전달하도록 합니다. 마지막으로 그래픽과 유저 입력을 처리하는 두 클래스에 대한 포인터인 private변수 m_Input
과 m_Graphics
가 있습니다.
////////////////////////////////////////////////////////////////////////////////
// Class name: SystemClass
////////////////////////////////////////////////////////////////////////////////
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;
GraphicsClass* m_Graphics;
};
/////////////////////////
// FUNCTION PROTOTYPES //
/////////////////////////
static LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
/////////////
// GLOBALS //
/////////////
static SystemClass* ApplicationHandle = 0;
#endif
WndProc
함수와 ApplicationHandle
포인터 역시 클래스 파일 안에 포함하여 윈도우의 시스템 메세지들을 MessageHandler
로 전달할 수 있게 하였습니다.
이제 SystemClass
의 소스를 보도록 하죠.
Systemclass.cpp
////////////////////////////////////////////////////////////////////////////////
// Filename: systemclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "systemclass.h"
이 클래스의 생성자에서는 객체들의 포인터를 null로 초기화합니다. 이것이 중요한 이유는 초기화에 실패하면 곧바로 Shutdown
함수를 호출하게 되는데 이 함수에서는 객체가 null이 아니라면 이를 올바르게 생성된 객체로 취급하고 이 객체의 메모리를 정리해야 한다고 판단하고 실제로 정리를 수행하려고 하기 때문입니다. 어플리케이션에서 모든 포인터와 변수들을 null로 초기화하는 것은 좋은 습관입니다. 이렇게 하지 않으면 일부 릴리즈 빌드에서는 프로그램이 실패할 수 있습니다.
SystemClass::SystemClass()
{
m_Input = 0;
m_Graphics = 0;
}
여기서는 아무것도 하지 않는 복사 생성자와 아무것도 하지 않는 파괴자를 만들었습니다. 사실 이 클래스에서는 필요하지 않지만 만약 이 정의가 없다면 일부 컴파일러에서는 자동으로 이를 생성합니다. 하지만 여기 선언한 것처럼 완전히 빈 함수로 만드는 것은 아니기에 일부러 이렇게 만들었습니다.
클래스 파괴자에서 아무런 객체 정리도 하지 않음을 볼 수 있습니다. 대신에 모든 정리 작업을 아래에 있는 Shutdown
함수에서 하도록 하였습니다. 그 이유는 제가 파괴자의 호출이 올바로 되지 않는다고 생각하기 때문입니다. ExitThread()
와 같은 일부 윈도우 함수는 파괴자를 호출하지 않아 메모리 누수를 발생시키는 것으로 알려져 있습니다. 물론 더 안전한 버전의 함수를 사용할 수 있지만 저는 윈도우에서 프로그래밍을 할 때에는 상당히 조심스럽습니다.
SystemClass::SystemClass(const SystemClass& other)
{
}
SystemClass::~SystemClass()
{
}
그 다음으로 Initialize
함수에서는 어플리케이션의 모든 초기화 작업을 수행합니다. 우선 InitializeWindows
함수를 호출하여 어플리케이션이 사용할 윈도우를 생성합니다. 또한 m_Input
과 m_Graphics
객체를 초기화하여 유저의 입력을 받아들이고 화면에 그래픽을 그릴 수 있도록 합니다.
bool SystemClass::Initialize()
{
int screenWidth, screenHeight;
bool result;
// 함수에 높이와 너비를 전달하기 전에 변수를 0으로 초기화한다.
screenWidth = 0;
screenHeight = 0;
// 윈도우즈 api를 사용하여 초기화한다.
InitializeWindows(screenWidth, screenHeight);
// input 객체를 생성합니다. 이 객체는 유저로부터 들어오는 키보드 입력을 처리하기 이해 사용합니다.
m_Input = new InputClass;
if(!m_Input)
{
return false;
}
// Input 객체를 초기화합니다.
m_Input->Initialize();
// graphics 객체를 생성합니다. 이 객체는 이 어플리케이션의 모든 그래픽 요소를 그리는 일을 합니다.
m_Graphics = new GraphicsClass;
if(!m_Graphics)
{
return false;
}
// graphics 객체를 초기화합니다.
result = m_Graphics->Initialize(screenWidth, screenHeight, m_hwnd);
if(!result)
{
return false;
}
return true;
}
Shutdown
함수는 객체를 정리합니다. 이것은 종료되면서 m_Graphics
와 m_Input
객체와 관련된 모든 것들을 반환하며 윈도우와 그 윈도우와 관련된 핸들들도 정리합니다.
void SystemClass::Shutdown()
{
// Graphics 객체를 반환합니다.
if(m_Graphics)
{
m_Graphics->Shutdown();
delete m_Graphics;
m_Graphics = 0;
}
// Input 객체를 반환합니다.
if(m_Input)
{
delete m_Input;
m_Input = 0;
}
// 창을 종료시킵니다.
ShutdownWindows();
return;
}
Run
함수는 프로그램이 종료될 때까지 루프를 돌면서 어플리케이션의 모든 작업을 처리합니다. 어플리케이션의 모든 작업은 매 루프마다 불리는 Frame
함수에서 수행됩니다. 이것은 어플리케이션의 다른 부분을 작성할 때에도 항상 마음에 담고 고려해야 하는 중요한 개념입니다. 이를 의사코드로 표현하면 다음과 같습니다.
while 종료되지 않은 동안
윈도우의 시스템 메세지를 확인
메세지 처리
어플리케이션의 작업
유저가 작업중 프로그램의 종료를 원하는지 확인
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
{
// 그 외에는 Frame 함수를 처리합니다.
result = Frame();
if(!result)
{
done = true;
}
}
}
return;
}
뒤이어 나오는 Frame
함수는 어플리케아션의 모든 작업이 처리되는 곳입니다. 보면 아시겠지만 단순히 m_Input
객체를 통해 유저가 Esc키를 눌러 종료하기를 원하는가만 체크하고 있습니다. 만약 누르지 않았다면 m_Graphics
객체를 통해 화면에 그리는 작업을 수행합니다. 어플리케이션이 더 커진다면 이 코드도 확장될 것입니다.
bool SystemClass::Frame()
{
bool result;
// 유저가 Esc키를 눌러 어플리케이션을 종료하기를 원하는지 확인합니다.
if(m_Input->IsKeyDown(VK_ESCAPE))
{
return false;
}
// graphics객체의 작업을 처리합니다.
result = m_Graphics->Frame();
if(!result)
{
return false;
}
return true;
}
MessageHandler
함수는 윈도우의 시스템 메세지가 전달되는 곳입니다. 이렇게 함으로 우리는 관심있는 몇 가지 정보들을 들을 수 있습니다. 현재 우리는 단지 키가 눌려있는지, 떼어지는지를 알 수 있고 이 정보를 m_Input
객체에 전달합니다. 다른 정보들은 윈도우의 기본 메세지 처리기에 전달합니다.
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);
}
}
}
InitializeWindows
함수에는 우리가 렌더링을 하게 될 윈도우를 만드는 코드가 들어갑니다. 이 함수는 호출한 함수에게로 screenWidth
와 screenHeight
값을 다시 되돌려주므로 이 값을 어플리케이션에서 활용할 수 있습니다. 우리는 여기서 까맣고 아무런 경계선이 없는 기본 설정으로 윈도우를 만듭니다. 그리고 전역변수인 FULL_SCREEN
의 값에 따라 그냥 작은 윈도우가 될 수도, 아니면 풀스크린의 윈도우를 만들 수 있습니다. 만약 이 변수의 값이 false
라면 단지 800x600크기의 윈도우를 만들어 화면 가운데에 위치시킵니다. 이 FULL_SCREEN
전역변수는 graphicsclass.h
의 윗쪽에 선언되어 있습니다. 이 값을 바꾸고자 하면 그 곳을 보시면 됩니다. 왜 SystemClass
의 헤더파일이 아니라 그곳에 있어야 하는지는 나중에 알게 될 것입니다.
void SystemClass::InitializeWindows(int& screenWidth, int& screenHeight)
{
WNDCLASSEX wc;
DEVMODE dmScreenSettings;
int posX, posY;
// 외부 포인터를 이 객체로 설정합니다.
ApplicationHandle = this;
// 이 어플리케이션의 인스턴스를 가져옵니다.
m_hinstance = GetModuleHandle(NULL);
// 어플리케이션의 이름을 설정합니다.
m_applicationName = L"Engine";
// 윈도우 클래스를 기본 설정으로 맞춥니다.
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);
// 윈도우 클래스를 등록합니다.
RegisterClassEx(&wc);
// 모니터 화면의 해상도를 알아옵니다.
screenWidth = GetSystemMetrics(SM_CXSCREEN);
screenHeight = GetSystemMetrics(SM_CYSCREEN);
// 풀스크린 모드 변수의 값에 따라 화면 설정을 합니다.
if(FULL_SCREEN)
{
// 만약 풀스크린 모드라면 화면 크기를 데스크톱 크기에 맞추고 색상을 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;
// 풀스크린에 맞는 디스플레이 설정을 합니다.
ChangeDisplaySettings(&dmScreenSettings, CDS_FULLSCREEN);
// 윈도우의 위치를 화면의 왼쪽 위로 맞춥니다.
posX = posY = 0;
}
else
{
// 윈도우 모드라면 800x600의 크기를 가지게 합니다.
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);
// 마우스 커서를 표시하지 않습니다.
ShowCursor(false);
return;
}
ShutdownWindows
는 화면 설정을 되돌리고 윈도우와 그 핸들들을 반환합니다.
void SystemClass::ShutdownWindows()
{
// 마우스 커서를 표시합니다.
ShowCursor(true);
// 풀스크린 모드를 빠져나올 때 디스플레이 설정을 바꿉니다.
if(FULL_SCREEN)
{
ChangeDisplaySettings(NULL, 0);
}
// 창을 제거합니다.
DestroyWindow(m_hwnd);
m_hwnd = NULL;
// 어플리케이션 인스턴스를 제거합니다.
UnregisterClass(m_applicationName, m_hinstance);
m_hinstance = NULL;
// 이 클래스에 대한 외부 포인터 참조를 제거합니다.
ApplicationHandle = NULL;
return;
}
WndProc
함수는 윈도우시스템에서 메세지를 보내는 곳입니다. 아마 위의 InitiaizeWindows
함수에서 윈도우 클래스를 초기화할때 wc.lpfnWndProc = WndProc
를 보고 WndProc
의 존재를 눈치채신 분도 있으리라 생각됩니다. MessageHandler
에서 처리할 메세지를 받아야 하기 때문에 WndProc
함수도 이 클래스 파일 안에 선언하였습니다. 이것은 SystemClass
에서 윈도우의 메세지를 가로채는 것을 가능하게 해 주고 코드 역시 간단하게 만들어줍니다.
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;
}
// 다른 모든 메세지들은 system 클래스의 메세지 처리기에 전달합니다.
default:
{
return ApplicationHandle->MessageHandler(hwnd, umessage, wparam, lparam);
}
}
}
Inputclass.h
튜토리얼 코드를 간단하게 유지하기 위해 저는 DirectInput 튜토리얼을 하기 전까지 윈도우에서 제공하는 입력을 사용할 것입니다(DirectInput이 훨씬 빠릅니다). InputClass
는 유저의 키보드 입력을 처리합니다. 이 클래스는 SystemClass::MessageHandler
함수로부터 입력을 전달받습니다. 이 객체는 키보드 배열에 있는 각 키의 상태를 저장합니다. 만약 키보드 상태에 대한 질의가 들어오면 이 객체는 현재 이 키가 눌려있는지에 대한 정보를 알려줍니다. 헤더 파일은 다음과 같습니다.
////////////////////////////////////////////////////////////////////////////////
// Filename: inputclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _INPUTCLASS_H_
#define _INPUTCLASS_H_
////////////////////////////////////////////////////////////////////////////////
// Class name: InputClass
////////////////////////////////////////////////////////////////////////////////
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
////////////////////////////////////////////////////////////////////////////////
// Filename: inputclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "inputclass.h"
InputClass::InputClass()
{
}
InputClass::InputClass(const InputClass& other)
{
}
InputClass::~InputClass()
{
}
void InputClass::Initialize()
{
int i;
// 모든 키들을 눌리지 않은 상태로 초기화합니다.
for(i=0; i<256; i++)
{
m_keys[i] = false;
}
return;
}
void InputClass::KeyDown(unsigned int input)
{
// 키가 눌렸다면 그 상태를 배열에 저장합니다.
m_keys[input] = true;
return;
}
void InputClass::KeyUp(unsigned int input)
{
// 키가 떼어졌다면 그 상태를 배열에 저장합니다.
m_keys[input] = false;
return;
}
bool InputClass::IsKeyDown(unsigned int key)
{
// 현재 키가 눌림/뗌 상태인지 반환합니다.
return m_keys[key];
}
Graphicsclass.h
GraphicsClass
는 SystemClass
가 생성하는 또다른 객체입니다. 이 어플리케이션의 모든 그래픽 기능이 이 객체 안에 캡슐화되어 있습니다. 저는 또한 이 헤더에 풀스크린 모드와 같은 그래픽과 관련된 전역 설정을 할 수 있도록 하였습니다. 현재 이 클래스는 거의 비어 있지만 나중에는 모든 그래픽 객체들을 담을 것입니다.
////////////////////////////////////////////////////////////////////////////////
// Filename: graphicsclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _GRAPHICSCLASS_H_
#define _GRAPHICSCLASS_H_
//////////////
// INCLUDES //
//////////////
#include <windows.h>
/////////////
// GLOBALS //
/////////////
const bool FULL_SCREEN = false;
const bool VSYNC_ENABLED = true;
const float SCREEN_DEPTH = 1000.0f;
const float SCREEN_NEAR = 0.1f;
////////////////////////////////////////////////////////////////////////////////
// Class name: GraphicsClass
////////////////////////////////////////////////////////////////////////////////
class GraphicsClass
{
public:
GraphicsClass();
GraphicsClass(const GraphicsClass&);
~GraphicsClass();
bool Initialize(int, int, HWND);
void Shutdown();
bool Frame();
private:
bool Render();
private:
};
#endif
Graphicsclass.cpp
지금은 모든 함수가 비어 있지만 프레임워크의 완성을 위해 뼈대만 만들었습니다.
////////////////////////////////////////////////////////////////////////////////
// Filename: graphicsclass.cpp
////////////////////////////////////////////////////////////////////////////////
#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()
{
return;
}
bool GraphicsClass::Frame()
{
return true;
}
bool GraphicsClass::Render()
{
return true;
}
마치면서
우리는 이제 프레임워크와 화면에 출력되는 윈도우를 갖게 되었습니다. 이 프레임워크는 이제부터 모든 튜토리얼들의 기초가 되기 때문에 프레임워크의 구조를 아는 것은 대단히 중요합니다. 그러므로 다음 튜토리얼을 보기 전에 아래 연습문제를 해서 실제로 저 코드가 컴파일되고 실행되는지 확인하시기 바랍니다. 만약 이 구조를 이해하지 못한다 하더라도 긍정적인 마인드를 가지고 다음 튜토리얼로 넘어가시기 바랍니다. 이 프레임워크의 코드가 점점 채워져 나가면서 다시 이 코드를 보게 되면 좀 더 이해가 잘 되리라 생각됩니다.
연습문제
graphicsclass.h
의FULL_SCREEN
변수의 값을true
로 바꾸고 다시 컴파일하여 프로그램을 실행해 보십시오. 윈도우가 나타나면 Esc키를 눌러 종료하십시오.
소스 코드
Visual Studio 2010 프로젝트: dx11tut02.zip
소스코드: dx11src02.zip
실행파일: dx11exe02.zip
'강좌번역 > DirectX 11' 카테고리의 다른 글
DirectX11 Tutorial 6 - 조명 (1) | 2013.01.22 |
---|---|
DirectX11 Tutorial 5 - 텍스쳐 (14) | 2013.01.16 |
DirectX11 Tutorial 4 - 버퍼, 셰이더, HLSL (20) | 2013.01.06 |
DirectX11 Tutorial 3 - DirectX 11의 초기화 (20) | 2013.01.05 |
DirectX11 Tutorial 1 - Visual Studio에서의 DirectX11 설정 (7) | 2013.01.05 |