这个部分和DirectX12的创建框架和窗口的过程相似。
在开始使用OpenGL 4.0进行编码之前,我建议您构建一个简单的代码框架。该框架将处理Windows的基本功能,并提供一种以有组织的可读方式扩展代码的简便方法。由于这些教程的目的只是为了尝试OpenGL 4.0的不同功能,因此我们将有目的地使框架尽可能的薄。
框架
框架将从五个项目开始。它将具有WinMain函数来处理应用程序的入口点。它还将具有一个系统类,该类封装将在WinMain函数中调用的整个应用程序。在系统类内部,我们将具有一个用于opengl系统调用的opengl类,一个用于处理用户输入的输入类以及一个用于处理OpenGL图形代码的图形类。这是框架设置的图:
main
现在,我们将了解如何设置框架,让我们先看一下main.cpp文件中的WinMain函数。
// Filename: main.cpp
#include "systemclass.h"
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR pScmdline, int iCmdshow)
{
SystemClass* System;
bool result;
//创建系统对象.
System = new SystemClass;
if(!System)
{
return 0;
}
// 初始化并运行系统对象
result = System->Initialize();
if(result)
{
System->Run();
}
// 关闭并释放系统对象.
System->Shutdown();
delete System;
System = 0;
return 0;
}
如您所见,我们使WinMain函数保持相当简单。
我们创建系统类,然后对其进行初始化。如果初始化没有问题,则调用系统类Run函数。Run函数将运行自己的循环并执行所有应用程序代码,直到完成为止。运行功能完成后,我们然后关闭系统对象并清理系统对象。因此,我们使其保持非常简单,并将整个应用程序封装在系统类中。
systemclass
现在,让我们看一下系统类的头文件。
// Filename: systemclass.h
#ifndef _SYSTEMCLASS_H_
#define _SYSTEMCLASS_H_
///
// PRE-PROCESSING DIRECTIVES //
///
#define WIN32_LEAN_AND_MEAN
//
// INCLUDES //
//
#include <windows.h>
///
// MY CLASS INCLUDES //
///
#include "openglclass.h"
#include "inputclass.h"
#include "graphicsclass.h"
// 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();
bool InitializeWindows(OpenGLClass*, int&, int&);
void ShutdownWindows();
private:
LPCWSTR m_applicationName;
HINSTANCE m_hinstance;
HWND m_hwnd;
OpenGLClass* m_OpenGL;
InputClass* m_Input;
GraphicsClass* m_Graphics;
};
/
// FUNCTION PROTOTYPES //
/
static LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
/
// GLOBALS //
/
static SystemClass* ApplicationHandle = 0;
#endif
我们定义WIN32_LEAN_AND_MEAN。我们这样做是为了加快构建过程,它通过排除一些较少使用的API来减小Win32头文件的大小。
包含Windows.h,以便我们可以调用函数来创建/销毁窗口并能够使用其他有用的win32函数。
#include“ openglclass.h”
#include“ inputclass.h”
#include“ graphicsclass.h”
此时,我们已将其他三个类的头文件包含在框架中,因此我们可以在系统类中使用它们。
该类的定义非常简单。我们看到在此处定义的WinMain中调用的Initialize,Shutdown和Run函数。在这些函数内部还将调用一些私有函数。我们还在类中放置了一个MessageHandler函数,以处理Windows系统消息,这些消息将在运行时发送到应用程序。最后,我们有一些私有变量m_OpenGL,m_Input和m_Graphics,它们将指向三个对象的指针,这三个对象将处理opengl,输入和图形。
WndProc函数和ApplicationHandle指针也包含在此类文件中,因此我们可以将Windows系统消息重定向到系统类内部的MessageHandler函数中。
现在让我们看一下系统类的源文件:
// Filename: systemclass.cpp
#include "systemclass.h"
SystemClass::SystemClass()
{
m_OpenGL = 0;
m_Input = 0;
m_Graphics = 0;
}
SystemClass::SystemClass(const SystemClass& other)
{
}
SystemClass::~SystemClass()
{
}
bool SystemClass::Initialize()
{
int screenWidth, screenHeight;
bool result;
// 将屏幕的宽度和高度初始化为零
screenWidth = 0;
screenHeight = 0;
// 创建OpenGL对象.
m_OpenGL = new OpenGLClass;
if(!m_OpenGL)
{
return false;
}
// 创建应用程序将使用的窗口,并初始化OpenGL.
result = InitializeWindows(m_OpenGL, screenWidth, screenHeight);
if(!result)
{
MessageBox(m_hwnd, L"Could not initialize the window.", L"Error", MB_OK);
return false;
}
//创建输入对象。该对象将用于处理从用户读取输入.
m_Input = new InputClass;
if(!m_Input)
{
return false;
}
// 初始化输入对象
m_Input->Initialize();
// 创建图形对象。该对象将处理渲染此应用程序的所有图形。
m_Graphics = new GraphicsClass;
if(!m_Graphics)
{
return false;
}
// 初始化图形对象。
result = m_Graphics->Initialize(m_OpenGL, m_hwnd);
if(!result)
{
return false;
}
return true;
}
void SystemClass::Shutdown()
{
// 释放图形对象
if(m_Graphics)
{
m_Graphics->Shutdown();
delete m_Graphics;
m_Graphics = 0;
}
// 释放输入对象
if(m_Input)
{
delete m_Input;
m_Input = 0;
}
// 释放Opengl对象
if(m_OpenGL)
{
delete m_OpenGL;
m_OpenGL = 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);
}
// 如果Windows发出结束应用程序的信号,则退出。
if(msg.message == WM_QUIT)
{
done = true;
}
else
{
// 否则进行帧处理.
result = Frame();
if(!result)
{
done = true;
}
}
}
return;
}
bool SystemClass::Frame()
{
bool result;
// 检查用户是否按了转义键并想退出该应用程序。
if(m_Input->IsKeyDown(VK_ESCAPE))
{
return false;
}
// 对图形对象进行框架处理。
result = m_Graphics->Frame();
if(!result)
{
return false;
}
return true;
}
LRESULT CALLBACK SystemClass::MessageHandler(HWND hwnd, UINT umsg, WPARAM wparam, LPARAM lparam)
{
switch(umsg)
{
// 检查是否在键盘上按下了键.
case WM_KEYDOWN:
{
// 如果释放了一个键,则将其发送到输入对象,以便它可以取消设置该键的状态。
m_Input->KeyDown((unsigned int)wparam);
return 0;
}
// 检查键盘上的键是否已释放。
case WM_KEYUP:
{
//如果释放了一个键,则将其发送到输入对象,以便它可以取消设置该键的状态。
m_Input->KeyUp((unsigned int)wparam);
return 0;
}
// 其他任何发送到默认消息处理程序的消息,因为我们的应用程序将不会使用它们
default:
{
return DefWindowProc(hwnd, umsg, wparam, lparam);
}
}
}
bool SystemClass::InitializeWindows(OpenGLClass* OpenGL, int& screenWidth, int& screenHeight)
{
WNDCLASSEX wc;
DEVMODE dmScreenSettings;
int posX, posY;
// 获取指向该对象的外部指针。
ApplicationHandle = this;
// 获取此应用程序的实例
m_hinstance = GetModuleHandle(NULL);
// 为应用程序命名
m_applicationName = L"Engine";
// 使用默认设置设置Windows类。
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);
// 为OpenGL扩展程序创建一个临时窗口
m_hwnd = CreateWindowEx(WS_EX_APPWINDOW, m_applicationName, m_applicationName, WS_POPUP,
0, 0, 640, 480, NULL, NULL, m_hinstance, NULL);
if(m_hwnd == NULL)
{
return false;
}
// 不显示窗口。
ShowWindow(m_hwnd, SW_HIDE);
// 扩展已初始化,请释放临时窗口。
DestroyWindow(m_hwnd);
m_hwnd = NULL;
// 确定客户端桌面屏幕的分辨率
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_POPUP,
posX, posY, screenWidth, screenHeight, NULL, NULL, m_hinstance, NULL);
if(m_hwnd == NULL)
{
return false;
}
// 在屏幕上打开窗口并将其设置为主要焦点。
ShowWindow(m_hwnd, SW_SHOW);
SetForegroundWindow(m_hwnd);
SetFocus(m_hwnd);
///隐藏鼠标光标。
ShowCursor(false);
return true;
}
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;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT umessage, WPARAM wparam, LPARAM lparam)
{
switch(umessage)
{
// 检查窗口是否关闭。
case WM_CLOSE:
{
PostQuitMessage(0);
return 0;
}
// 所有其他消息都传递给系统类中的消息处理程序。
default:
{
return ApplicationHandle->MessageHandler(hwnd, umessage, wparam, lparam);
}
}
}
在这里,我创建了一个空副本构造函数和一个空类析构函数。在此类中,我不需要它们,但是如果未定义它们,则一些编译器将为您生成它们,在这种情况下,我宁愿它们为空。
您还将注意到,我没有在类析构函数中清除任何对象。相反,我在Shutdown函数中对所有对象进行了清理,您将进一步了解。原因是我不相信它会被调用。某些Windows函数(例如ExitThread())以不调用类析构函数而导致内存泄漏而闻名。您当然可以立即调用这些函数的安全版本,但是在Windows上进行编程时请注意。
bool Initialize();
初始化函数将为应用程序进行所有设置。它首先调用InitializeWindows,它将创建供我们的应用程序使用的窗口,并将初始化OpenGL。此函数还创建并初始化输入和图形对象,应用程序将使用它们来处理用户输入并将图形呈现到屏幕。
void Shutdown();
关机功能进行清理。它关闭并释放与opengl,图形和输入对象关联的所有内容。同样,它还会关闭窗户并清理与之相关的句柄。
void Run();
Run函数是我们的应用程序将循环执行并进行所有应用程序处理的地方,直到我们决定退出为止。应用程序处理在称为每个循环的Frame函数中完成, 也叫帧处理。这是一个重要的概念,需要理解,因为在编写应用程序的其余部分时必须牢记这一点。伪代码如下所示:
while 没有完成
检查Windows系统消息
处理系统消息
流程应用程序循环
检查用户是否要在帧处理期间退出
bool SystemClass :: Frame();
以下Frame函数是我们应用程序的所有处理完成的地方。到目前为止,这非常简单,我们检查输入对象以查看用户是否按下了转义键并想要退出。如果他们不想退出,则我们调用图形对象进行其帧处理,这将为该帧渲染图形。随着应用程序的增长,我们将在其中放置更多代码。
MessageHandler函数是我们将Windows系统消息定向到的地方。通过这种方式,我们可以侦听某些我们感兴趣的信息。当前,我们仅读取是否按下了某个键或释放了一个键,然后将该信息传递给输入对象。所有其他信息,我们将传递回Windows默认消息处理程序。
LRESULT CALLBACK SystemClass::MessageHandler(HWND hwnd, UINT umsg, WPARAM wparam, LPARAM lparam);
MessageHandler函数是我们将Windows系统消息定向到的地方。通过这种方式,我们可以侦听某些我们感兴趣的信息。当前,我们仅读取是否按下了某个键或释放了一个键,然后将该信息传递给输入对象。所有其他信息,我们将传递回Windows默认消息处理程序。
bool SystemClass :: InitializeWindows(OpenGLClass * OpenGL,int&screenWidth,int&screenHeight);
InitializeWindows函数是我们在其中放置代码以构建将用于渲染的窗口的地方。它将screenWidth和screenHeight返回给调用函数,因此我们可以在整个应用程序中使用它们。我们使用一些默认设置创建窗口,以初始化无边框的纯黑色窗口。该函数将创建一个小窗口或一个全屏窗口,具体取决于名为FULL_SCREEN的全局变量。如果将其设置为true,则我们将屏幕覆盖整个用户桌面窗口。如果将其设置为false,我们只需在屏幕中间制作一个800x600的窗口。我将FULL_SCREEN全局变量放在graphicsclass.h文件的顶部,以防您要修改它。以后我为什么要在文件中放置全局变量而不是文件头,这才有意义。
现在,此函数也是我们初始化OpenGL的地方。其原因是因为它与窗口的创建完全相关。过程是,首先我们创建一个临时窗口,以便我们可以与OpenGL对话。接下来,我们获得opengl的扩展,然后释放临时窗口。有了opengl扩展,我们现在可以创建使用最新版本的OpenGL渲染所需的适当窗口。但是请注意,在本教程中我们将不介绍OpenGL初始化,而仅介绍基本的Windows初始化。设置OpenGL将是下一教程的整个主题。
如前所述,我们在这里为opengl创建一个临时窗口以获取扩展。但是,我们不会在本教程中设置OpenGL,这将在下一个教程中完成。本教程仅专注于创建框架并在屏幕上显示基本窗口。
m_hwnd = CreateWindowEx(WS_EX_APPWINDOW,m_applicationName,m_applicationName,WS_POPUP,0,0,640,480
,NULL,NULL,m_hinstance,NULL);
然后进行常规窗口创建。
SystemClass :: ShutdownWindows();
ShutdownWindows就是这样做的。它将屏幕设置恢复为正常,并释放窗口和与其关联的句柄。
LRESULT CALLBACK WndProc(HWND hwnd,UINT umessage,WPARAM wparam,LPARAM lparam)
Windows将其消息发送到WndProc函数。您会注意到,当我们在上面的InitializeWindows函数中使用wc.lpfnWndProc = WndProc初始化窗口类时,我们告诉窗口它的名称。我将其包含在此类文件中,因为我们通过将所有消息发送到SystemClass内部定义的MessageHandler函数来将其直接绑定到系统类中。这使我们可以将消息传递功能直接挂接到我们的类中,并保持代码的整洁。
inputclass
为了使教程简单,我暂时使用Windows输入,直到我在DirectInput上进行了教程(这要好得多)。输入类处理来自键盘的用户输入。此类是从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
// 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
图形类是由系统类创建的另一个对象。此应用程序中的所有图形功能都将封装在此类中。我还将在此文件中的标头用于所有我们可能要更改的图形相关的全局设置,例如全屏或窗口模式。当前该类为空,但在将来的教程中将包含所有图形对象。
其中包含了四个全局变量。
// Filename: graphicsclass.h
#ifndef _GRAPHICSCLASS_H_
#define _GRAPHICSCLASS_H_
///
// MY CLASS INCLUDES //
///
#include "openglclass.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(OpenGLClass*, HWND);
void Shutdown();
bool Frame();
private:
bool Render();
private:
};
#endif
// Filename: graphicsclass.cpp
#include "graphicsclass.h"
GraphicsClass::GraphicsClass()
{
}
GraphicsClass::GraphicsClass(const GraphicsClass& other)
{
}
GraphicsClass::~GraphicsClass()
{
}
bool GraphicsClass::Initialize(OpenGLClass* OpenGL, HWND hwnd)
{
return true;
}
void GraphicsClass::Shutdown()
{
return;
}
bool GraphicsClass::Frame()
{
return true;
}
bool GraphicsClass::Render()
{
return true;
}
Openglclass
就像GraphicsClass一样,我们创建了一个空的OpenGLClass,将用于以后的教程,但是基本框架工作所必需的。
// Filename: openglclass.h
#ifndef _OPENGLCLASS_H_
#define _OPENGLCLASS_H_
//
// INCLUDES //
//
#include <windows.h>
// Class name: OpenGLClass
class OpenGLClass
{
public:
OpenGLClass();
OpenGLClass(const OpenGLClass&);
~OpenGLClass();
private:
};
#endif
// Filename: openglclass.cpp
#include "openglclass.h"
OpenGLClass::OpenGLClass()
{
}
OpenGLClass::OpenGLClass(const OpenGLClass& other)
{
}
OpenGLClass::~OpenGLClass()
{
}
现在,我们有了一个框架和一个窗口,它将在屏幕上弹出。现在,此框架将成为将来所有教程的基础,因此了解此框架非常重要。在继续下一个教程之前,请尝试执行“练习”以确保代码能够编译并为您工作。如果您不了解此框架,则仍然可以继续学习其他教程,并且在框架中填写更多内容后,它们对您可能更有意义。
练习
在graphicsclass.h标头中将FULL_SCREEN参数更改为true,然后重新编译并运行该程序。窗口显示后,按退出键退出。