一.前言
学习DirectX的初衷是为了做游戏,为了开发游戏引擎。我在之前其实学习过一段时间的DirectX,但是由于后来一些其他原因将DirectX的学习搁置到了一边。现在有了比较充裕的时间,想把DirectX的相关知识捡起来,复习以前学习过的知识,顺带学习新的知识。
二.windows相关
首先,其实我对windows编程了解也不是很多。大一的时候看过一段时间的windows程序设计这本书,但是好像看天书一样,学了没有多久就转去学Qt去了。后来回来学习DirectX的时候,发现需要windows编程的基础,当时还比较后悔没有好好看windows程序设计这本书。还在学习DirectX对windows编程能力的要求不是很高,当然你对windows编程越了解越好啦。不了解windows编程也没有关系,花一点点时间去了解一下windows窗口的建立和消息循环相关的知识就可以开始DirectX的学习了。当然以后要是有时间还是多学习下windows编程相关知识比较好。
因为我不想花费太多的时间在windows编程上面,所以说在这个地方我也不会做太多的介绍。而且为了不每次在写一个又一个的demo的时候,都要重复创建一个windows窗口的过程,所以我在这里做了一个小小的封装。我将这个封装后的类称为Engine_Application,它具体的定义如下:
1 /*======================================================== 2 * 应用程序类,用于创建窗口 3 *=======================================================*/ 4 5 6 #ifndef _APPLICATION_H_ 7 #define _APPLICATION_H_ 8 9 #include <windows.h> 10 #include <functional> 11 #include <fstream> 12 13 #pragma comment(lib, "winmm.lib") 14 15 typedef LRESULT (CALLBACK* MsgProc)(HWND, UINT msg, WPARAM, LPARAM); 16 17 class Engine_Application 18 { 19 20 public: 21 22 Engine_Application(MsgProc msg); 23 24 virtual ~Engine_Application(); 25 26 public: 27 28 bool InitWindow(HINSTANCE hInstance, wchar_t *className, wchar_t *winName, DWORD style, int x = 0, 29 int y = 0, int width = 800, int height = 600); 30 31 void Show(int nCmdShow); 32 33 int Run(); 34 35 void SetRenderFunc(std::function<void (float dt)> pFunc); 36 37 public: 38 39 int GetWinWidth()const; 40 41 int GetWinHeight()const; 42 43 HWND GetWinHwnd()const; 44 45 protected: 46 47 HWND m_hWnd; 48 HINSTANCE m_hInstance; 49 50 MsgProc m_pMsg; 51 52 wchar_t *m_pClassName; 53 54 int m_nWidth; 55 int m_nHeight; 56 57 std::function<void (float dt)> m_pRenderFunc; 58 }; 59 60 61 62 #endif
现在让我们来具体看一下这个类里面的内容:
a.消息处理函数
1 typedef LRESULT (CALLBACK* MsgProc)(HWND, UINT msg, WPARAM, LPARAM);
首先我定义了一个函数指针,注意看一下就会发现这个函数指针的原型和一个窗口的消息函数是一模样的。是的,我这里是定义了Application类中的消息函数,可以在后面的成员变量中发现一个MsgProc的变量,那个变量就是用于保存Application类的消息函数指针,作用是用于Application的消息处理。而且我们发现Application类的构造函数也必须要求传递一个MsgProc类型的变量,因为一个application需要有一个自己的消息函数。
b.窗口初始化函数
bool InitWindow(HINSTANCE hInstance, wchar_t *className, wchar_t *winName, DWORD style, int x = 0, int y = 0, int width = 800, int height = 600);
参数十分简单,都是一些初始化一个windows窗口的一些基本东西,从参数名称也可以看出他们的作用。
hInstance:应用的实例句柄
className:类名
style:窗口的风格
x,y:窗口左上角的坐标
width,height:窗口的宽度和高度
c.消息循环和渲染处理函数
int Run();
d.设置渲染函数回调
void SetRenderFunc(std::function<void (float dt)> pFunc);
这里用了一个C++11中的新特性std::function,没有接触过的朋友可以先去百度一下。说简单点,这就是一个和函数指针类似的东西。<>中的内容表明了,它是指向一个返回值为void,参数为float类型的函数。这里的float参数,传递的是两帧之间的时间间隔。
e.具体实现
介绍了大半天的类的定义,现在我把它的具体实现放在下面,实现很简单,相信有一点windows基础的都能看得很明白。因为我不想在windows下花太多的时间,具体代码如下:
1 #include "Engine_Application.h" 2 3 Engine_Application::Engine_Application(MsgProc msg) 4 { 5 m_pMsg = msg; 6 } 7 8 Engine_Application::~Engine_Application() 9 { 10 UnregisterClass(m_pClassName, m_hInstance); 11 } 12 13 14 bool Engine_Application::InitWindow(HINSTANCE hInstance, wchar_t *className, wchar_t *winName, DWORD style, 15 int x /* = 0 */, int y /* = 0 */, 16 int width /* = 800 */, int height /* = 600 */) 17 { 18 WNDCLASS wnd; 19 20 wnd.cbClsExtra = 0; 21 wnd.cbWndExtra = 0; 22 wnd.hbrBackground = static_cast<HBRUSH>(GetStockObject(WHITE_BRUSH)); 23 wnd.hCursor = LoadCursor(NULL, IDC_ARROW); 24 wnd.hIcon = LoadIcon(NULL, IDI_APPLICATION); 25 wnd.hInstance = hInstance; 26 wnd.lpfnWndProc = m_pMsg; 27 wnd.lpszClassName = className; 28 wnd.lpszMenuName = NULL; 29 wnd.style = CS_VREDRAW | CS_HREDRAW; 30 31 if (!RegisterClass(&wnd)) 32 { 33 return false; 34 } 35 36 HWND hwnd = CreateWindow(className, winName, style, x, y, width, height, NULL, NULL, hInstance, NULL); 37 38 if (!hwnd) 39 { 40 return false; 41 } 42 43 m_hWnd = hwnd; 44 m_hInstance; 45 46 m_nWidth = width; 47 m_nHeight = height; 48 49 m_pClassName = className; 50 51 return true; 52 } 53 54 55 void Engine_Application::Show(int nCmdShow) 56 { 57 ShowWindow(m_hWnd, nCmdShow); 58 UpdateWindow(m_hWnd); 59 } 60 61 void Engine_Application::SetRenderFunc(std::function<void (float dt)> pFunc) 62 { 63 m_pRenderFunc = pFunc; 64 } 65 66 67 int Engine_Application::Run() 68 { 69 MSG msg; 70 ZeroMemory(&msg, sizeof(MSG)); 71 72 while (msg.message != WM_QUIT) 73 { 74 if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) 75 { 76 TranslateMessage(&msg); 77 DispatchMessage(&msg); 78 79 } else 80 { 81 if (m_pRenderFunc) 82 { 83 static float lastTime = 0.0f; 84 static float currentTime = 0.0f; 85 86 currentTime = static_cast<float>(timeGetTime()); 87 float delta = (currentTime - lastTime) * 0.001f; 88 89 m_pRenderFunc(delta); 90 91 lastTime = currentTime; 92 } 93 } 94 } 95 96 97 return msg.wParam; 98 } 99 100 int Engine_Application::GetWinHeight()const 101 { 102 return m_nHeight; 103 } 104 105 int Engine_Application::GetWinWidth()const 106 { 107 return m_nWidth; 108 } 109 110 HWND Engine_Application::GetWinHwnd()const 111 { 112 return m_hWnd; 113 }
f.DirectD程序流程
这里不是说windows相关吗?为什么会说上Direct3D呢?因为上面的Application类我是为了写Direct3D封装的,在消息循环的地方做了下处理,所以先说下Direct3D程序流程。先看下面的图:
从上图来看windows窗口我们已经创建好了,至于DirectX的初始化,马上就会讲到。所以说我们现在关心的是消息循环的部分。可以看到进入消息循环之后做了两个事情,一个是消息处理,一个是渲染处理。消息循环的部分,我放在了Run()这个函数中。我们截取出一段代码来看。
while (msg.message != WM_QUIT) { if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) { TranslateMessage(&msg); DispatchMessage(&msg); } else { if (m_pRenderFunc) { static float lastTime = 0.0f; static float currentTime = 0.0f; currentTime = static_cast<float>(timeGetTime()); float delta = (currentTime - lastTime) * 0.001f; m_pRenderFunc(delta); lastTime = currentTime; } } }
在上面的代码中我们首先检测当前的消息是否为WM_QUIT,如果是则退出消息循环,程序结束。否则从消息队列从去取出消息,如果取出一个消息则将起发送到消息处理函数进行处理。否则则调用m_pRenderFunc进行渲染操作。在if语句中还计算了当前两次渲染之间的时间间隔。
三.DirectX基础
说了这么久终于要开始进入正题了,关于一些DirectX的发展史什么的就不细说了,相信大家也不喜欢听,我们直接开始吧。
1.开发环境的搭建
这个开发环境的搭建,网上的教程很多,而且我也比较懒,就直接贴一个链接吧。传送门:http://www.cnblogs.com/graphics/archive/2009/11/04/1595727.html
2.DirectX结构
a.HAL(硬件抽象层)
大多数基于Direcr3D API设计开发的三维图形程序都运行于硬件抽象层(Hardware Abstraction Layer),也就是HAL之上。使用HAL的好处,是既能充分利用系统硬件的加速功能,又隐藏了硬件相关的设备特性。这就是说,Direct3D利用HAL实现了设备无关的特性,通过Direct3D可以编写出与设备无关的高效代码。硬件抽象层是由硬件制造商提供的特定于硬件的接口,Direct3D利用该接口实现了对显示硬件的直接访问。所以,应用程序永远不需要访问HAL,直接与Direct3D API打交道就可以了。HAL可以是显卡的一部分,也可以是和显卡驱动程序相互关联的单独动态链接库(DLL)。
HAL仅仅是与设备相关的代码,对于硬件不支持的功能,它并不提供软件模拟。在DirectX 9.0中,针对同一种显示设备,HAL提供3种顶点处理模式:软件顶点处理、硬件顶点处理和混合顶点处理。还有,纯硬件设备是HAL的变体,纯硬件设备类型仅支持硬件顶点处理。
b.REF(参考光栅设备)
有时候,Direct3D提供的某些功能不被我们的显卡支持,但是我们偏偏想去使用这些功能的话,就可以使用一下Direct3D的辅助设备,也就是参考光栅设备(Reference Rasterizer Device,REF)。这种REF设备可以以软件运算的方式完全支持Direct3D API。借助REF设备,我们可以在代码中使用那些不为当前硬件所支持的特性,并对这些特征进行测试。
3.初始化DirectX
DirectX的初始化可以分解为一下几个步骤:
a.获取接口IDirect3D9指针。
这个接口主要用于获取硬件设备信息,并且创建接口IDirect3DDevice9。获取IDirect3D9的代码十分简单,如下所示:
IDirect3D9 *pD3D = nullptr; pD3D = Direct3DCreate9(D3D_SDK_VERSION);
Direct3DCreate9函数返回一个IDirect3D9d的指针,如果创建失败会返回NULL,它的参数必须为D3D_SDK_VERSION,这样才能保证会引用正确的头文件。
b.检测设备性能
检测设备性能(D3DCAPS9),判断显卡是否支持硬件顶点运算(当然还可以检测硬件对其他性能的支持情况)。为什么要进行检查,因为在后面初始化IDirect3DDevice9的时候,我们需要根据主显卡的一些性能来进行初始化。要检查设备性能,首先我们要获取一个D3DCAPS9对象,可以通过如下代码获取:
D3DCAPS9 caps; pD3D->GetDeviceCaps(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, &caps);
GetDeviceCaps的原型如下:
HRESULT GetDeviceCaps( [in] UINT Adapter, [in] D3DDEVTYPE DeviceType, [out] D3DCAPS9 *pCaps );
Adater:用于指定物理显卡的序号, D3DADAPTER_DEFAULT表示为当前正在使用的显卡。
DeviceType:指定设备的类型。 例如:D3DDEVTYPE_HAL(硬件设备),D3DDEVTYPE_REF(软件设备)
pCaps:返回已经被填充的D3DCAPS9结构
一旦获取了D3DCAPS9结构对象,就可以通过&运算检测当前的硬件是否支持某些功能。例如检测是否支持硬件顶点运算:
DWORD vp = 0; if (caps.DevCaps & D3DDEVCAPS_HWRASTERIZATION) { vp = D3DCREATE_HARDWARE_VERTEXPROCESSING; } else { vp = D3DCREATE_SOFTWARE_VERTEXPROCESSING; }
通过将DevCaps与D3DDEVCAPS_HWRASTERIZATION做&运算,如果为true则表示支持硬件顶点运算,将vp设为D3DCREATE_HARDWARE_VERTEXPROCESSING。否则支持设为D3DCREATE_SOFTWARE_VERTEXPROCESSING,表示采用软件顶点运算。这里将这个常量值保存在vp这个变量中,因为在后面创建IDirect3DDevice9对象的时候会用到这个变量。
c.填充D3DPRESENT_PARAMETER结构
IDirect3DDevice9的创建过程中,会要求一个D3DPRESENT_PARAMETER类型的参数。所以说,这一步,我们需要填充D3DPRESENT_PARAMETER结构中的内容,就好像我们在你创建一个windows窗口的时候需要填充WNDCLASS结构一样。该结构的定义如下:
typedef struct D3DPRESENT_PARAMETERS { UINT BackBufferWidth; UINT BackBufferHeight; D3DFORMAT BackBufferFormat; UINT BackBufferCount; D3DMULTISAMPLE_TYPE MultiSampleType; DWORD MultiSampleQuality; D3DSWAPEFFECT SwapEffect; HWND hDeviceWindow; BOOL Windowed; BOOL EnableAutoDepthStencil; D3DFORMAT AutoDepthStencilFormat; DWORD Flags; UINT FullScreen_RefreshRateInHz; UINT PresentationInterval; } D3DPRESENT_PARAMETERS, *LPD3DPRESENT_PARAMETERS;
BackBufferWidth:指定后台缓冲区的宽度
BackBufferHeigh:指定后台缓冲区的高度
BackBufferFormat:指定后台缓冲区的保存像素格式,可以用D3DFORMAT枚举定义。可以用GetDisplayMode获取当前像素格式。
BackBufferCount:指定后台缓冲区的数量。
MultiSampleType:表示多重采样的类型。通常我们将MultiSampleType设为D3DMULTISAMPLE_NONE。
MultiSampleQuality:表示多重采样的格式。通常我们将其设为0。
SwapEffect:用于指定Direct3D如何将后台缓冲区的内容复制到前台的缓存中,由于D3DSWAPEFFECT_DISCARD效率最高,通常我们都将其设为D3DSWAPEFFECT_DISCARD。
hDeviceWindow:窗口句柄,这里指定我们需要在哪个窗口上进行绘制。这个参数也可以设为NULL,这时就表示对当前被激活的窗口进行绘制。
Windowed:表示绘制窗体的显示模式,为TRUE使表示使用窗口模式,为FALSE则表示使用全屏模式。
EnableAutoDepthStencil:表示Direct3D是否为应用程序自动管理深度缓存,这个成员为TRUE的话,表示需要自动管理深度缓存,这时候就需要对下一个成员AutoDepthStencilFormat进行相关像素格式的设置。
AutoDepthStencilFormat:如果我们把EnableAutoDepthStencil成员设为TRUE的话,在这里就需要指定AutoDepthStencilFormat的深度缓冲的像素格式。具体格式可以在结构体D3DFORMAT中进行选取。
Flags:表示附加属性,通常都设为0.
FullScreen_RefreshRateInHz:表示在全屏模式时指定的屏幕的刷新率,,在全屏模式时在EnumAdapterModes枚举类型中进行取值,我们在全屏模式时将其设为默认值D3DPRESENT_RATE_DEFAULT,窗口模式时这个成员没有意义,我们把它就设为0了。
PresentationInterval:用于指定指定后台缓冲区与前台缓冲区的最大交换频率,可在D3DPRESENT中进行取值。
下面看一个填充的实例:
D3DPRESENT_PARAMETERS d3dpp; ZeroMemory(&d3dpp, sizeof(d3dpp)); d3dpp.AutoDepthStencilFormat = D3DFMT_D24S8; d3dpp.BackBufferCount = 1; d3dpp.BackBufferFormat = D3DFMT_A8R8G8B8; d3dpp.BackBufferHeight = 600; d3dpp.BackBufferWidth = 800; d3dpp.EnableAutoDepthStencil = TRUE; d3dpp.Flags = 0; d3dpp.FullScreen_RefreshRateInHz = D3DPRESENT_RATE_DEFAULT; d3dpp.hDeviceWindow = m_hWnd; d3dpp.MultiSampleQuality = 0; d3dpp.MultiSampleType = D3DMULTISAMPLE_NONE; d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE; d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; d3dpp.Windowed = TRUE;
上面的代码中我们将后台缓存的大小设置为800*600,并且是窗口模式。
d.创建IDirect3DDevice9对象
有上面三步的准备工作,现在我们可以创建IDirect3DDevice9对象了。该对象代表了我们用来显示3D图形的物理硬件设备,后面涉及到的一些图像的绘制就全靠它了,它的重要性就不言而喻了吧。IDirect3DDevice9对象的创建需要借助CreateDevice这个函数,它的原型如下:
HRESULT CreateDevice( [in] UINT Adapter, [in] D3DDEVTYPE DeviceType, [in] HWND hFocusWindow, [in] DWORD BehaviorFlags, [in, out] D3DPRESENT_PARAMETERS *pPresentationParameters, [out, retval] IDirect3DDevice9 **ppReturnedDeviceInterface );
这个函数的几个参数都十分简单,在前面我们基本上都已经介绍过了。前两个参数和GetDeviceCaps的前两个参数一模一样,至于第三个参数就是我们刚刚保存好的vp这个变量了,还记得吧。第四个参数就是我们刚刚填充好的D3DPRESENT_PARAMETER的一个指针,最后一个参数就是我们需要的IDirect3DDevice9了。下面看一个例子:
pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd, vp, &d3dpp, &pDevice);
怎么样,设备的创建十分简单吧。
下面再贴上一段DirectX初始化的完整代码:
m_pD3D = Direct3DCreate9(D3D_SDK_VERSION); FALSE_RETURN(m_pD3D); D3DPRESENT_PARAMETERS d3dpp; ZeroMemory(&d3dpp, sizeof(d3dpp)); d3dpp.AutoDepthStencilFormat = D3DFMT_D24S8; d3dpp.BackBufferCount = 1; d3dpp.BackBufferFormat = D3DFMT_A8R8G8B8; d3dpp.BackBufferHeight = m_nHeight; d3dpp.BackBufferWidth = m_nWidth; d3dpp.EnableAutoDepthStencil = TRUE; d3dpp.Flags = 0; d3dpp.FullScreen_RefreshRateInHz = D3DPRESENT_RATE_DEFAULT; d3dpp.hDeviceWindow = m_hWnd; d3dpp.MultiSampleQuality = 0; d3dpp.MultiSampleType = D3DMULTISAMPLE_NONE; d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE; d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; d3dpp.Windowed = bWindowed; m_bWindowed = bWindowed; D3DCAPS9 caps; m_pD3D->GetDeviceCaps(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, &caps); DWORD vp = 0; if (caps.DevCaps & D3DDEVCAPS_HWRASTERIZATION) { vp = D3DCREATE_HARDWARE_VERTEXPROCESSING; } else { vp = D3DCREATE_SOFTWARE_VERTEXPROCESSING; } m_pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, m_hWnd, vp, &d3dpp, &m_pDevice);
4.DirectX渲染的基本步骤
说了一大篇,终于说到渲染了。废话不多说,开始说渲染。
a.清屏操作
每当绘制画面之前,我们都需要通过IDirect3DDevice9接口的Clear方法将后台缓冲区中的内容进行清空,并设置上清屏之后的颜色。其中Clear函数的原型声明如下:
HRESULT Clear( [in] DWORD Count, [in] const D3DRECT *pRects, [in] DWORD Flags, [in] D3DCOLOR Color, [in] float Z, [in] DWORD Stencil );
Count, pRects:我将第一个和第二个参数放到一起来说。首先是pRects,是一个指向D3DRECT的指针。Clear允许进行部分清除,但是你要提供清除区域的矩形位置。pRects参数就是用于保存需要清除的矩形区域,Count参数则保存了矩形区域的数量。这有点类似于我们写程序的时候传递一个数组,一个参数传递首地址而另一个参数传递数组的长度,是一个道理。如果pRects设置为NULL,则Count必须设置为0,这个时候表示对整个视口进行清除。
Flags:这个参数用于指定那些缓存需要被清除,可以的取值有D3DCLEAR_TARGET(后台缓存),D3DCLEAR_ZBUFFER(深度缓存),D3DCLEAR_STENCIL(模板缓存)。可以用|运算符来进行多个缓存清除的操作。
Color:指定清除后的颜色。
Z:用于指定清空深度缓冲区后每个像素对应的深度值。
Stencil:用于指定清空模板缓冲区之后模板缓冲区中每个像素对应的模板值。
例如,下面的代码表示清除整个视口的后台缓存,深度缓存,模板缓存。将清除后深度缓存的深度值设为1.0,模板缓存的模板值设为0,视口颜色设为红色。
pDevice->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER | D3DCLEAR_STENCIL, D3DCOLOR_XRGB(255, 0, 0), 1.0, 0);
b.开始渲染
在使用DirectX进行绘制之前要首先调用IDirect3DDevice9::BeginScene函数,这个函数和IDirect3DDevice9::EndScene总是成对出现。IDirect3DDevice9::BeginScene()函数调用很简单,没有参数,返回值为HRESULT。
pDevice->BeginScene();
c.添加渲染内容
调用IDirect3DDevice9::BeginScene()之后,就可以在后面加上我们需要渲染的内容了。这里的内容就太广泛了,你可以在这里绘制一个简单的三角形,也可以再这里绘制一些复杂的3d场景,这个就看具体需求了。
d.完成绘制,翻转显示
渲染部分的代码添加完成之后,就需要调用IDirect3DDevice9::EndScene()来完成渲染了,前面就已经说过IDirect3DDevice9::EndScene()和IDirect3DDevice9::BeginScene()是成对出现的。光光完成渲染还不够,我们的渲染工作都是在后台缓存完成的,必须将它进行一个翻转,切换过来才能正常的显示。这个时候我们就需要调用Present来完成翻转工作。该函数的原型声明如下:
HRESULT Present( [in] const RECT *pSourceRect, [in] const RECT *pDestRect, [in] HWND hDestWindowOverride, [in] const RGNDATA *pDirtyRegion );
pSourceRect:表示指向复制源矩形区域的指针,一般我们都将其设为NULL。
pDestRect:表示指向复制目标矩形区域的指针,一般我们将其设为NULL。
hDestWindowOverride:表示指向当前绘制的窗口句柄。如果我们设为0或NULL就表示取我们之前初始化DirectX填充的D3DPRESENT_PARAMETERS结构体中的hDeviceWindows的值,一般我们将其设为NULL。
pDirtyRegion:表示指向最小更新区域的指针,一般我们将其设为NULL。
调用也很简单,如下:
pDevice->Present(NULL, NULL, NULL, NULL);
e.交换链
通过上面的内容,已经了解了使用DirectX进行渲染的一个基本流程。但是有没有考虑到这样一个问题,如果我们在渲染部分所做的一些操作很复杂,也许下一帧开始渲染的时候,当前帧的内容还没有渲染完成。而且一些不断重复的进行一些清屏操作,可能会造成闪屏的现象。为了解决这个问题,Direct3D采用了一个叫交换链的东西(Swap Chain)。用接口IDirect3DSwapChain9来表示。快速交换链一般维护着两个到三个的表面,工作方式有点像放电影。可以分为前台缓冲区和后台缓冲区两个部分,前台缓冲区和后台缓冲区是存在系统内存或者显存里的一个内存块。前台缓冲区中的内容是显示器显示的内容,我们可以看见的内容。而后台缓冲区则主要用于下一帧或者几帧的绘制,当当前的内容显示完成之后,则直接将后台缓存区的内容复制到前台缓冲区进行显示,而后台缓冲区则进行后面内容的绘制。这样就可以让渲染的内容显得更加的平滑。如下图所示:
四.Engine_Render
为了简化后面的一些工作,我打算对Direct3D也进行一个简单的封装,目前也才刚刚开始。涉及到的例如渲染状态设置,顶点,矩阵变换的内容都还未进行封装。目前仅仅是将初始化内容进行了一下封装,方便测试一些demo。我将这个类叫做Render,它的定义如下:
/*================================================= * 渲染设备类 *===============================================*/ #ifndef _RENDERDEVICE_H_ #define _RENDERDEVICE_H_ #include <windows.h> #include <d3d9.h> #include <d3dx9.h> #include <fstream> #include "Engine_Comment.h" #pragma comment(lib, "d3d9.lib") #pragma comment(lib, "d3dx9.lib") #define DEBUG class Engine_RenderDevice { public: Engine_RenderDevice(HWND hwnd, int width, int height); ~Engine_RenderDevice(); public: bool InitDevice(bool bWindowed = true); bool InitFontFPS(); bool IsRendering()const; bool BeginRendering(bool bTarget, bool bDepth, bool bStencil); void Clear(bool bTarget, bool bDepth, bool bStencil); void EndRendering(); void SetClearColor(unsigned r, unsigned g, unsigned b); D3DCOLOR GetClearColor()const; void ShowFPS(float dt); #ifdef DEBUG IDirect3DDevice9 *GetDevice()const; #endif // DEBUG protected: IDirect3D9 *m_pD3D; IDirect3DDevice9 *m_pDevice; ID3DXFont *m_pFontFPS; D3DCOLOR m_dwClearColor; HWND m_hWnd; int m_nWidth; int m_nHeight; bool m_bWindowed; bool m_bIsRendering; float m_fFPS; }; #endif // !_RENDERDEVICE_H_
再附上实现部分的源码:
#include <cstdio> #include <iostream> #include "Engine_RenderDevice.h" Engine_RenderDevice::Engine_RenderDevice(HWND hwnd, int width, int height) { m_pD3D = nullptr; m_pDevice = nullptr; m_pFontFPS = nullptr; m_hWnd = hwnd; m_nWidth = width; m_nHeight = height; m_dwClearColor = D3DCOLOR_XRGB(0, 0, 0); m_bIsRendering = false; m_fFPS = 0.0f; } Engine_RenderDevice::~Engine_RenderDevice() { Release<ID3DXFont *>(m_pFontFPS); Release<IDirect3DDevice9 *>(m_pDevice); Release<IDirect3D9 *>(m_pD3D); } bool Engine_RenderDevice::InitDevice(bool bWindowed /* = true */) { m_pD3D = Direct3DCreate9(D3D_SDK_VERSION); FALSE_RETURN(m_pD3D); D3DPRESENT_PARAMETERS d3dpp; ZeroMemory(&d3dpp, sizeof(d3dpp)); d3dpp.AutoDepthStencilFormat = D3DFMT_D24S8; d3dpp.BackBufferCount = 1; d3dpp.BackBufferFormat = D3DFMT_A8R8G8B8; d3dpp.BackBufferHeight = m_nHeight; d3dpp.BackBufferWidth = m_nWidth; d3dpp.EnableAutoDepthStencil = TRUE; d3dpp.Flags = 0; d3dpp.FullScreen_RefreshRateInHz = D3DPRESENT_RATE_DEFAULT; d3dpp.hDeviceWindow = m_hWnd; d3dpp.MultiSampleQuality = 0; d3dpp.MultiSampleType = D3DMULTISAMPLE_NONE; d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE; d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; d3dpp.Windowed = bWindowed; m_bWindowed = bWindowed; D3DCAPS9 caps; m_pD3D->GetDeviceCaps(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, &caps); DWORD vp = 0; if (caps.DevCaps & D3DDEVCAPS_HWRASTERIZATION) { vp = D3DCREATE_HARDWARE_VERTEXPROCESSING; } else { vp = D3DCREATE_SOFTWARE_VERTEXPROCESSING; } m_pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, m_hWnd, vp, &d3dpp, &m_pDevice); FALSE_RETURN(m_pDevice); InitFontFPS(); return true; } bool Engine_RenderDevice::InitFontFPS() { D3DXCreateFont(m_pDevice, 40, 0, 1000, D3DX_DEFAULT, FALSE, DEFAULT_CHARSET, 0, 0, 0, L"Times New Roman", &m_pFontFPS); FALSE_RETURN(m_pFontFPS); return true; } void Engine_RenderDevice::SetClearColor(unsigned r, unsigned g, unsigned b) { m_dwClearColor = D3DCOLOR_XRGB(r, g, b); } D3DCOLOR Engine_RenderDevice::GetClearColor()const { return m_dwClearColor; } bool Engine_RenderDevice::BeginRendering(bool bTarget, bool bDepth, bool bStencil) { DWORD flag = 0; if (bTarget) { flag |= D3DCLEAR_TARGET; } if (bDepth) { flag |= D3DCLEAR_ZBUFFER; } if (bStencil) { flag |= D3DCLEAR_STENCIL; } m_pDevice->Clear(0, NULL, flag, m_dwClearColor, 1.0f, 0); if (m_pDevice->BeginScene()) { m_bIsRendering = true; return true; } return false; } void Engine_RenderDevice::Clear(bool bTarget, bool bDepth, bool bStencil) { DWORD flag = 0; if (bTarget) { flag |= D3DCLEAR_TARGET; } if (bDepth) { flag |= D3DCLEAR_ZBUFFER; } if (bStencil) { flag |= D3DCLEAR_STENCIL; } if (m_bIsRendering) { m_pDevice->EndScene(); m_pDevice->Clear(0, NULL, flag, m_dwClearColor, 1.0f, 0); m_pDevice->BeginScene(); } } void Engine_RenderDevice::EndRendering() { if (m_bIsRendering) { m_pDevice->EndScene(); m_pDevice->Present(NULL, NULL, NULL, NULL); m_bIsRendering = false; } } void Engine_RenderDevice::ShowFPS(float dt) { static float delta = 0.0f; static int count = 0; delta += dt; ++count; if (delta >= 1.0f) { m_fFPS = count / delta; delta = 0.0f; count = 0; } /* 绘制FPS */ RECT rec; GetClientRect(m_hWnd, &rec); char fps[100]; sprintf(fps, "fps:%0.2f", m_fFPS); m_pFontFPS->DrawTextA(NULL, fps, -1, &rec, DT_LEFT, D3DCOLOR_XRGB(255, 0, 0)); } #ifdef DEBUG IDirect3DDevice9 *Engine_RenderDevice::GetDevice()const { return m_pDevice; } #endif // DEBUG
由于这个类目前的内容还十分简单,有了前面的介绍,看懂的话肯定没有什么问题,这里就不做说明了。
五.实例
前面说了这么长的篇幅,没有什么例子来支撑,怎么能行。毕竟实践是检验真理的唯一标准。本来我最开始的想法是直接做一个清屏色为红色的窗口的,但是觉得这样的效果给人太过枯燥,最后就还绘制了一个三角形。关于渲染部分的内容,可以不用抬去纠结。而且由于我还没有封装顶点,状态设置等相关的内容,所以绘制的时候又把IDirect3DDevice9从对象里面拿出来了。只是暂时这样做,以后会将这部分做好的。源码如下:
#include "Engine_Application.h" #include "Engine_RenderDevice.h" struct Vertex { float x, y, z, rhw; D3DCOLOR color; const static DWORD FVF; }; const DWORD Vertex::FVF = D3DFVF_XYZRHW | D3DFVF_DIFFUSE; Engine_RenderDevice *g_pDevice = nullptr; IDirect3DVertexBuffer9 *g_pVertexBuffer = nullptr; LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM w, LPARAM l) { switch (message) { case WM_KEYUP: if (w == VK_ESCAPE) { PostQuitMessage(0); } return 0; case WM_DESTROY: PostQuitMessage(0); return 0; default: return DefWindowProc(hwnd, message, w, l); } } void InitObjects() { Vertex triangle[] = { {400, 100, 0, 1.0, D3DCOLOR_XRGB(255, 0, 0)}, {700, 500, 0, 1.0, D3DCOLOR_XRGB(0, 255, 0)}, {100, 500, 0, 1.0, D3DCOLOR_XRGB(0, 0, 255)} }; void *temp; IDirect3DDevice9 *pDevice = g_pDevice->GetDevice(); pDevice->CreateVertexBuffer(sizeof(triangle), 0, Vertex::FVF, D3DPOOL_MANAGED, &g_pVertexBuffer, NULL); g_pVertexBuffer->Lock(0, sizeof(triangle), (void **)&temp, 0); memcpy(temp, triangle, sizeof(triangle)); g_pVertexBuffer->Unlock(); } void Update(float dt) { IDirect3DDevice9 *pDevice = g_pDevice->GetDevice(); pDevice->SetRenderState(D3DRS_LIGHTING, false); g_pDevice->BeginRendering(true, true, false); pDevice->SetStreamSource(0, g_pVertexBuffer, 0, sizeof(Vertex)); pDevice->SetFVF(Vertex::FVF); pDevice->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 1); g_pDevice->ShowFPS(dt); g_pDevice->EndRendering(); } int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) { Engine_Application *app = new Engine_Application(WndProc); app->InitWindow(hInstance, L"Engine", L"Engine_Application", WS_OVERLAPPEDWINDOW, 100, 100); app->SetRenderFunc(Update); app->Show(nShowCmd); g_pDevice = new Engine_RenderDevice(app->GetWinHwnd(), app->GetWinWidth(), app->GetWinHeight()); g_pDevice->InitDevice(); InitObjects(); return app->Run(); }
看了上面的代码有没有觉得,窗口的建立和Direct3D的初始化是多么简洁。最后附上一张运行截图:
PS:第一次写这么长的博客,那些地方写得不好的多多包涵。