在开始写directx 11的代码前,我建议建立一个简单的框架.这个框架将会使用基于窗口功能和在提供简单的扩展原始代码方法并且是易读的方式.本教程的目的只是尝试directx11不同的特性,我们尽量保持框架简洁.
框架
框架由4个部分共同工作. WinMain函数是应用程序的入口点.他是个系统类,这个类封装了整个应用系统然后在WinMain函数中被调用.在这个系统类中我们有个InputClass来处理用户输入,有个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;
// Create the system object.
System = new SystemClass;
if(!System)
{
return 0;
}
// Initialize and run the system object.
result = System->Initialize();
if(result)
{
System->Run();
}
// Shutdown and release the system object.
System->Shutdown();
delete System;
System = 0;
return 0;
}
就像你看到的, 我们尽量保持WinMain函数很简单.我们创建SystemClass并且初始化它.如果初始化没问题我们就能调用SystemClass的Run函数. Run函数会运行自己的循环函数并且运行所有应用程序代码直达结束.在Run函数结束后我们就关闭System对象并且清除System对象.所以我们要保持它的简单和封装在该SystemClass中.现在让我们看下SystemClass头文件.
Systemclass.h
// Filename: systemclass.h
#ifndef _SYSTEMCLASS_H_
#define _SYSTEMCLASS_H_
这里我们定义WIN32_LEAN_AND_MEAN.我们定义这个来加速创建过程.通过排除一些很少用到的API来减少win32头文件的数量
///
// PRE-PROCESSING DIRECTIVES //
///
#define WIN32_LEAN_AND_MEAN
Window.h被包含,所以我们可以调用函数来创建和销毁窗口和能够使用其他有用的win32函数.
//
// INCLUDES //
//
#include
这时候在框架中我们包含了两个类的头文件,所以我们可以在SystemClass中使用他们.
///
// MY CLASS INCLUDES //
///
#include "inputclass.h"
#include "graphicsclass.h"
这个类的定义是相当简单的.我们看到initialize(初始化),Shutdown(关闭)和Run(运行)函数在WinMain中被调用.这里也有些私有的函数被这3个函数调用..我们也把消息处理函数放入这个类中来处理windows系统消息,当它运行是它将会发送给应用程序.最后我们有些私有的变量 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指针包含在该类文件中, 所以我们可以在SystemClass中让windows消息重新指向我们的MessageHandler函数
现在让我们看看SystemClass的源文件
Systemclass.cpp
// Filename: systemclass.cpp
#include "systemclass.h"
在类的构造函数中我初始化两个对象指针为null. 这很重要,应为因为如果初始化这些对象失败的话Shutdown函数会进一步的尝试清理这些对象.如果对象不为null,那么它假设他们是有效的对象并且需要清理它. 初始化所有指针和变量为null在你的程序中是很好的习惯. 有些发行版将会失败如果你没这样做.
SystemClass::SystemClass()
{
m_Input = 0;
m_Graphics = 0;
}
这里我创建一个空的复制构造函数和空的析构函数.在这个类中我不必定义他们, 但是如果没有定义他们, 有些编译器将会帮你定义, 在现在的情况下, 我倾向让他们为空.
你也会注意到在析构函数中我没有清理任何对象. 反而我把清理所有对象放在Shutdown函数中如果你往下看. 原因是我不相信它会被调用. 某些windows函数像ExitThread()众所周知在内存泄漏时将不调用你的析构函数, 你当然可以调用安全版本的函数,但是我只是很小心当在windows上编程时
SystemClass::SystemClass(const SystemClass& other)
{
}
SystemClass::~SystemClass()
{
}
下面的Initialize函数做了所有的程序设置.它首先调用InitializeWindows,它会创建我们应用程序窗口.他也可以创建和初始化所有的输入和图形对象,应用程序能够使用它来处理用户的输入和图形渲染到屏幕.
bool SystemClass::Initialize()
{
int screenWidth, screenHeight;
bool result;
// Initialize the width and height of the screen to zero before sending the variables into the function.
screenWidth = 0;
screenHeight = 0;
// Initialize the windows api.
InitializeWindows(screenWidth, screenHeight);
// Create the input object. This object will be used to handle reading the keyboard input from the user.
m_Input = new InputClass;
if(!m_Input)
{
return false;
}
// Initialize the input object.
m_Input->Initialize();
// Create the graphics object. This object will handle rendering all the graphics for this application.
m_Graphics = new GraphicsClass;
if(!m_Graphics)
{
return false;
}
// Initialize the graphics object.
result = m_Graphics->Initialize(screenWidth, screenHeight, m_hwnd);
if(!result)
{
return false;
}
return true;
}
Shutdown函数做清理工作.它关闭和释放所有跟graphics和input对象相关的.同样的也关闭窗口和清除句柄.
void SystemClass::Shutdown()
{
// Release the graphics object.
if(m_Graphics)
{
m_Graphics->Shutdown();
delete m_Graphics;
m_Graphics = 0;
}
// Release the input object.
if(m_Input)
{
delete m_Input;
m_Input = 0;
}
// Shutdown the window.
ShutdownWindows();
return;
}
Run函数使我们的程序一直循环进行直到我们决定退出. 在每个循环函数中应用程序被执行.这是一个很重要的观念 其他的应用程序也要这样写.伪代码像下面的:
while not done
检查windows系统消息
处理系统消息
处理应用循环
检查用户是否想退出
void SystemClass::Run()
{
MSG msg;
bool done, result;
// Initialize the message structure.
ZeroMemory(&msg, sizeof(MSG));
// Loop until there is a quit message from the window or the user.
done = false;
while(!done)
{
// Handle the windows messages.
if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
// If windows signals to end the application then exit out.
if(msg.message == WM_QUIT)
{
done = true;
}
else
{
// Otherwise do the frame processing.
result = Frame();
if(!result)
{
done = true;
}
}
}
return;
}
下面frame函数是为我们处理所有应用程序的地方.到目前为止他是相当的简单的. 我们检查Input对象来发现是否按下escape键来退出.如果不想退出然后我们调用graphics对象来处理渲染图形框架事务. 随着程序的发展, 我们会在这里面放更多的代码.
bool SystemClass::Frame()
{
bool result;
// Check if the user pressed escape and wants to exit the application.
if(m_Input->IsKeyDown(VK_ESCAPE))
{
return false;
}
// Do the frame processing for the graphics object.
result = m_Graphics->Frame();
if(!result)
{
return false;
}
return true;
}
我们在MessageHandler函数中管理我们的系统信息。这样,我们可以监听我们感兴趣的信息。现在我们可以读取通过input对象的信息判断是否有按键按下或释放。所有其它的信息我们将会回传给windows默认消息句柄。
LRESULT CALLBACK SystemClass::MessageHandler(HWND hwnd, UINT umsg, WPARAM wparam, LPARAM lparam)
{
switch(umsg)
{
// Check if a key has been pressed on the keyboard.
case WM_KEYDOWN:
{
// If a key is pressed send it to the input object so it can record that state.
m_Input->KeyDown((unsigned int)wparam);
return 0;
}
// Check if a key has been released on the keyboard.
case WM_KEYUP:
{
// If a key is released then send it to the input object so it can unset the state for that key.
m_Input->KeyUp((unsigned int)wparam);
return 0;
}
// Any other messages send to the default message handler as our application won't make use of them.
default:
{
return DefWindowProc(hwnd, umsg, wparam, lparam);
}
}
}
InitializeWindows函数用来建立渲染窗口。它返回screenWidth和screenHeight给调用它的函数,所有我们可以使用他们在整个应用程序中。
我们用一些默认的设置来创建一个简单的黑色的无边框的窗口。这个函数靠FULL_SCREEN全局变量可以让窗口最小化或全屏。如果该变量为
true,然后应用程序将充满整个桌面窗口。如果该变量为false,我们在屏幕中间得到800x600的窗口。我把FULL_SCREEN全局变量放在
graphicsclass.h文件中假使你想修改它。
void SystemClass::InitializeWindows(int& screenWidth, int& screenHeight)
{
WNDCLASSEX wc;
DEVMODE dmScreenSettings;
int posX, posY;
// Get an external pointer to this object.
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;
}
// Create the window with the screen settings and get the handle to it.
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);
// Hide the mouse cursor.
ShowCursor(false);
return;
}
ShutdownWindows就像这样。它让屏幕设置到正常,并且释放窗口句柄
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;
}
WndProc函数是接受windows发送消息的地方。你可能注意到在上面在InitializeWindows函数中初始化窗口类的时候我们告诉windows它的名字wc.lpfnWndProc。我在这个类中包含它,绑定到系统类中,然后系统发送消息到MessageHandler函数中。这允许我们直接获取信息到我们的类中并且保持代码的整洁
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);
}
}
}
Inputclass.h
保持教程简单,在当时我使用windows的input,以后我会做个DrectInput指南(长远).InputClass处理用户从键盘输入的消息.这个类从SystemClass::MessageHandler 函数接收输入.Input对象会存储每个按键到一个数组中.当被询问时,它将会告诉调用函数是否有某个键被按下.这是头文件
// 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;
// Initialize all the keys to being released and not pressed.
for(i=0; i<256; i++)
{
m_keys[i] = false;
}
return;
}
void InputClass::KeyDown(unsigned int input)
{
// If a key is pressed then save that state in the key array.
m_keys[input] = true;
return;
}
void InputClass::KeyUp(unsigned int input)
{
// If a key is released then clear that state in the key array.
m_keys[input] = false;
return;
}
bool InputClass::IsKeyDown(unsigned int key)
{
// Return what state the key is in (pressed/not pressed).
return m_keys[key];
}
Graphicsclass.h
GraphicsClass是另一个被SystemClass创建的对象.所有图形功能被封装在这个类中,我也在这个文件中使用头文件,因为和图形有关的所有变量我们可能想改变,例如是否全屏.现在这个类是空的,但是在以后的教程中将会包含所有图形对象.
// Filename: graphicsclass.h
#ifndef _GRAPHICSCLASS_H_
#define _GRAPHICSCLASS_H_
//
// INCLUDES //
//
#include
/
// GLOBALS //
/
const bool FULL_SCREEN = false;
const bool VSYNC_ENABLED = true;
const float SCREEN_DEPTH = 1000.0f;
const float SCREEN_NEAR = 0.1f;
开始时我们需要这4个全局变量
// 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;
}
总结
现在我们有了ige框架并且窗口会在屏幕上弹出.现在这个框架会是以后教程的基础,所以理解这个框架是相当重要的.在继续下章教程前,请尝试去做一些练习来确定代码被编译并能够工作.如果你不理解这个框架你也可以继续下面的教程,它会给你更多的感觉,之后框架会被填写更多
做练习
在GraphicsClass.h中改变FULL_SCREEN为true然后编译和运行程序.当窗口显示时按下escape键来退出.