哇卡卡卡,哈哈哈,这节咋们就来学习初始化DirectX的具体代码和细节吧。
还几次上节说的我们的初始化主要有两个步骤,我们再看下图。
是否心里已经明白我们大概要做什么了那?
1. 好,首先来解决初始化的第一步,获得IDirect3DDevice9这个接口。
IDirect3DDevice9这个接口又叫设备接口,设备,什么设备?当然就是我们的硬件了。回想之前我们创建窗口的时候,我们说要有一个自己的窗口,需要一些最基本的信息来告诉我们窗口长啥样子,同理,我们在创建IDirect3DDevice9这个设备接口时候我们也要知道一些基本的信息,才能让我们知道这个设备接口大概是什么样子(这里的样子指的是它的属性,并不是具体外观),而这些基本信息就是我们的硬件信息了,因为我们说过DirectX是可以直接和硬件打交道的对吧。那么,也就是说。创建一个设备接口,我们需要获取硬件信息,然后把这些消息告知我们的设备接口,我们的设备接口就算完成了。
总结起来就是两步:
1.1:获取硬件信息
1.2:将信息告知我们的设备接口,创建设备接口
没问题把,获得设备接口就这么两步,那我们先走第一步:获得硬件信息
首先,想要获取,硬件信息,我们需要创建一个LPDIRECT3D9接口,创建这个接口不需要任何条件,我们通过这个接口调用GetDeviceCaps这个函数,就能获得硬件信息,同时我们也是通过LPDIRECT3D9接口调用CreateDevice函数最终创建我们的IDirect3DDevice9设备接口的。稍后便会讲到。我们先看一下用LPDIRECT3D9获得硬件信息的代码。
LPDIRECT3D9 pD3D = Direct3DCreate9(D3D_SDK_VERSION);//创建接口对象获取硬件信息并为后面的设备对象做准备
if (NULL == pD3D) { return E_FAIL; }
D3DCAPS9 caps;//用于存储设备信息的结构体
int vp = 0;//显卡支持的顶点运算模式
if (FAILED(pD3D->GetDeviceCaps(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, &caps)))
{//填入显卡序号,设备类型(硬件,软件)
return E_FAIL;
}
if (caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT)
vp = D3DCREATE_HARDWARE_VERTEXPROCESSING;//硬件顶点运算模式
else
vp = D3DCREATE_SOFTWARE_VERTEXPROCESSING;//软件顶点运算模式
我们看一下这一段代码,首先第一句话,我们使用了一个Direct3DCreate9函数来创建了一个LPDIRECT3D9接口,Direct3DCreate9这个函数只有一个参数,这个参数的意义是我们当前的DirectX版本,保证我们包含的头文件能正确调用相应的.dll库。如果失败,说明版本协商有问题。
然后是我们定义了一个D3DCAPS9型的结构体来保存我们的硬件信息。大家看到了我们还定义了一个整形的vp变量,这个变量是用来保存我们硬件是否支持硬件顶点运算模式,如果支持,那么后面我们将告诉我们的设备接口,让它在渲染的时候使用硬件顶点运算模式,如果不行就只能使用软件顶点运算模式了。最后的那句if else语句就是用来判断是否支持硬件顶点运算模式,当然,硬件顶点运算模式是要比软件顶点运算模式快很多的。这也是我们判断的原因。
最后就是中间的GetDeviceCaps函数了,这个函数就是用来获取硬件信息的。
HRESULT GetDeviceCaps(
[in] UINT Adapter,//显卡序号
[in] D3DDEVTYPE DeviceType,//设备类型
[out] D3DCAPS9 *pCaps//保存硬件信息的结构体
);
第一个参数一般填写默认的:D3DADAPTER_DEFAULT
第二个参数,我们也一般填写硬件设备类型:D3DDEVTYPE_HAL
最后一个参数就是我们需要保存信息的结构体,当我们调用完这个函数以后,设备信息就存到caps里面去了。ok,取得硬件信息就是这么多了。
1.2:将信息告知我们的设备接口,创建设备接口
我们已经取得了,硬件信息,是不是马上可以开始创建设备接口了哪?是的,但是在创建设备接口之前,我们还需要设置设备接口的另一些详细信息。而幸运的是,这些信息都包含在一个结构体内,还记得我们填窗口填写的WINDCLASSEX吗?这次我们要为设备填写一个叫做D3DPRESENT_PARAMETERS的结构体,这个结构体非常重要!
typedef struct _D3DPRESENT_PARAMETERS_
{
UINT BackBufferWidth;//交换链后台缓存宽度
UINT BackBufferHeight;//交换链后台缓存高度
D3DFORMAT BackBufferFormat;//后台缓存格式,他的参数是D3DFORMAT的成员之一
UINT BackBufferCount;//后台缓存个数
D3DMULTISAMPLE_TYPE MultiSampleType;//多重采样格式
DWORD MultiSampleQuality;//多重采样类型
D3DSWAPEFFECT SwapEffect;//将后台缓存内容复制到前台的方式.
HWND hDeviceWindow;//我们的窗口句柄
BOOL Windowed;//是否全屏
BOOL EnableAutoDepthStencil;//自动深度缓存,如果为真,需要设置下一个参数。
D3DFORMAT AutoDepthStencilFormat;//深度缓存的像素格式
DWORD Flags;//附加属性
/* FullScreen_RefreshRateInHz must be zero for Windowed mode */
UINT FullScreen_RefreshRateInHz;//全屏模式时候的刷新率
UINT PresentationInterval;前台缓存与后台缓存的最大交换速率。在D3DPRESENT中取值
}
好了这就是这个可以为我们的设备接口做出巨大贡献的结构体,可能大家第一次看到会觉得很生疏,如果大家有看不懂的地方,请不要在意,因为你不懂的,后面都会慢慢讲的。大家只需要记住这是个重要的结构体就行。接下来看看它的赋值
D3DPRESENT_PARAMETERS d3dpp;//一个非常重要的结构体
ZeroMemory(&d3dpp, sizeof(d3dpp));//用零填充d3dpp这块内存
d3dpp.BackBufferWidth = WINDOW_WEIGHT;//缓冲区宽度
d3dpp.BackBufferHeight = WINDOW_HIGHT;//缓冲区高度
d3dpp.BackBufferFormat = D3DFMT_A8R8G8B8;//缓冲区保存像素格式D3DFORMAT//++
d3dpp.BackBufferCount = 1;//后台缓存的个数
d3dpp.MultiSampleType = D3DMULTISAMPLE_NONE;//启用多重采样的类型
d3dpp.MultiSampleQuality = 0;//多重采样类型
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;//如何将后台缓存复制到前台缓存中
d3dpp.hDeviceWindow = hwnd;
d3dpp.Windowed = true;//使用窗口模式还是全屏模式
d3dpp.EnableAutoDepthStencil = true;//是否自动管理深度缓存
d3dpp.AutoDepthStencilFormat = D3DFMT_D24S8;//深度缓存像素格式D3DFORMAT中取值
d3dpp.Flags = 0;//附加属性,通常为0
d3dpp.FullScreen_RefreshRateInHz = 0;//全屏模式时候的屏幕刷新率
d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;//前后台交换的最大频率可以在D3DPRESENT取值
好了 硬件信息有了,结构体也有啦,那就开始创建我们的设备接口把
pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL,hwnd, vp, &d3dpp, &g_pd3dDevice)
pD3D->Release();
好吧 创建是非常简单的,一个CreateDevice函数就搞定了,调用这个函数后我们就可以用设备接口IDirect3DDevice9搞事情了。不过在此之前我们还是介绍下CreateDevice
HRESULT CreateDevice(
[in] UINT Adapter,//显卡序号,一般填0,D3DADAPTER_DEFAULT=0
[in] D3DDEVTYPE DeviceType,//设备类型,当然是硬件
[in] HWND hFocusWindow,//窗口句柄
[in] DWORD BehaviorFlags,//顶点运算模式
[in, out] D3DPRESENT_PARAMETERS *pPresentationParameters,//千辛万苦填的接口体
[out, retval] IDirect3DDevice9 **ppReturnedDeviceInterface//指向我们的设备接口的指针,这个参数就是我们的设备接口.
);
好啦 到此为止我们Direct3D的初始化第一部分创建设备接口IDirect3DDevice9就讲完了,我们把完整代码贴出来
LPDIRECT3D9 pD3D = Direct3DCreate9(D3D_SDK_VERSION);//创建接口对象获取硬件信息并为后面的设备对象做准备
if (NULL == pD3D) { return E_FAIL; }
D3DCAPS9 caps;//用于存储设备信息的结构体
int vp = 0;//显卡支持的顶点运算模式
if (FAILED(pD3D->GetDeviceCaps(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, &caps)))
{//填入显卡序号,设备类型(硬件,软件)
return E_FAIL;
}
if (caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT)
vp = D3DCREATE_HARDWARE_VERTEXPROCESSING;//硬件顶点运算模式
else
vp = D3DCREATE_SOFTWARE_VERTEXPROCESSING;//软件顶点运算模式
D3DPRESENT_PARAMETERS d3dpp;//一个非常重要的结构体
ZeroMemory(&d3dpp, sizeof(d3dpp));//用零填充d3dpp这块内存
d3dpp.BackBufferWidth = WINDOW_WEIGHT;//后台缓冲区宽度
d3dpp.BackBufferHeight = WINDOW_HIGHT;//后台缓冲区高度
d3dpp.BackBufferFormat = D3DFMT_A8R8G8B8;//缓冲区保存像素格式D3DFORMAT
d3dpp.BackBufferCount = 1;//后台缓存的个数
d3dpp.MultiSampleType = D3DMULTISAMPLE_NONE;//多重采样的类型
d3dpp.MultiSampleQuality = 0;//多重采样的类型
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;//如何将后台缓存复制到前台缓存中
d3dpp.hDeviceWindow = hwnd;
d3dpp.Windowed = true;//使用窗口模式还是全屏模式
d3dpp.EnableAutoDepthStencil = true;//是否自动管理深度缓存
d3dpp.AutoDepthStencilFormat = D3DFMT_D24S8;//深度缓存像素格式D3DFORMAT中取值
d3dpp.Flags = 0;//附加属性,通常为0
d3dpp.FullScreen_RefreshRateInHz = 0;//全屏模式时候的屏幕刷新率,窗口时为0在AdapterModes枚举中取值
d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;//前后台交换的最大频率可以在D3DPRESENT取值
if (FAILED(pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL,
hwnd, vp, &d3dpp, &g_pd3dDevice)))
return E_FAIL;
pD3D->Release();//LPDIRECT3D9接口对象的使命完成释放
return S_OK;
以上就是初始化第一部分的全部代码了,记得当我们使用完LPDIRECT3D9这个接口时,要把他Release掉。
2.使用我们的设备接口进行渲染并且在主事件循环中不断渲染
由于现在我们只是在初始化D3D,所以我们的不会在平面上渲染任何东西,所以这部分的代码也非常简单,就是使用我们的IDirect3DDevice9调用几个函数就可以搞定,然后把他们放入我们的主事件循环中不断地渲染就好啦,那我们来看看这几个函数把。
void Render()
{
g_pd3dDevice->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0, 0, 255), 1.0, 0);//清空后台缓存
g_pd3dDevice->BeginScene();//开始绘制
g_pd3dDevice->EndScene();//结束绘制
g_pd3dDevice->Present(NULL, NULL, NULL, NULL);//交换与显示
}
首先还是来看看咋们是第一个函数Clear,这个函数的主要作用就是设置我们清空了后台缓冲之后的一些属性。
HRESULT Clear(
[in] DWORD Count,//指向一个矩形数组地数量,与下一个函数有关,如果下一个函数为NULL,则这里为零,反之,下一个参数不是NULL,这里就不能写零
[in] const D3DRECT *pRects,//指向一个矩形结构数组地指针
[in] DWORD Flags,//需要清空地缓冲区,它可以是模板缓冲,颜色缓冲,和深度缓冲三个值地任意组合,用|连接。
[in] D3DCOLOR Color,//清空缓冲区后每个像素地颜色值
[in] float Z,//清空深度缓冲后地每个像素地深度值
[in] DWORD Stencil//清空模板缓冲后地每个像素地模板值
);
好后面的三个函数都非常简单
BeginScene代表着开始绘制
EndScene代表着结束绘制
而我们以后需要在屏幕上绘制东西时的代码函数都在BeginScene和EndScene之间
当所有的绘制完成之后我们就调用Present将绘制的内容显示出来,这个函数也非常简单四个参数都填NULL就ok了!
既然渲染的函数都写好了,那就赶紧将我们的渲染函数放到主循环函数中开始渲染把。是的没错,但是在那之前我们还有最后一个问题需要处理,什么问题?就是关于我们主事件循环的问题,我们先贴一下主事件循环的代码。
MSG msg = {0};//定义meg
while(GetMessage(&msg, NULL, 0, 0)) //使用getmessage获得消息
{
TranslateMessage(&msg);//转换消息
DispatchMessage(&msg);//发消息给程序,然后交给os调用窗口处理函数
}
之前我们说过,当我们生成窗口之后,OS就会给我们的程序生成一个消息队列,而我们的GetMessage函数则通过While循环不断获取消息,但是大家有没有想过,一旦消息队列为空咋办?会咋办哪?其实,一旦消息队列为空,则GetMessage 函数会一直停在那里等待消息,而不会继续执行下面的代码,试想一下,假如你在玩儿一个游戏,一旦你停止按键盘或者鼠标,整个游戏画面就不再更新,这将是多么可怕的一件事情,所以。我们希望不论消息队列中有没有消息,我们的主循环函数都不会阻塞,并且不断地渲染我们的画面。所以这时候我们需要请出PeekMessage这个函数来代替GetMessage函数。那么PeekMessage有什么优点哪?它唯一的优点就是,当我们的消息队列中没有消息的时候,它会返回一个值继续往下执行,不会一直等待消息队列中有消息才返回。下面我们看看将主事件循环改为PeekMessage之后时什么样
MSG msg = { 0 };//定义并初始化 meg
while (msg.message != WM_QUIT)//消息循环
{
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) //使用peekmessage获得消息
{
TranslateMessage(&msg);//转换消息
DispatchMessage(&msg);//分发消息给程序,然后交给os调用窗口处理函数
}
else
{
Render();
}
}
好了,我们可以看到由于PeekMessage不会一直等待消息队列的消息,所以当消息队列没有消息的时候,我们就让它执行我们的渲染函数,这样我们的画面就会一直更新,还有一点就是PeekMessage的前四个参数和Get Message一样,只有最后多一个参数,这个参数是告诉PeekMessage在查看消息队列中的消息时,是否要将查看的消息移出消息队列。好吧,最后我们把一个完整的Direct3D初始化程序贴出来吧!
//======================================================================================================//
//————————————————————————程序说明———————————————————————//
//程序名称:DirectInitDemo
//2017.9.5 淡一抹夕霞
//======================================================================================================//
//==========================================//
//——————头文件部分——————————//
#include"windows.h"
#include<d3d9.h>
#include<d3dx9.h>
//==========================================//
//==========================================//
//——————库文件部分——————————//
#pragma comment(lib,"d3dx9.lib")
#pragma comment(lib,"d3d9.lib")
//==========================================//
//==========================================//
//———--———全局变量——————————//
LPDIRECT3DDEVICE9 g_pd3dDevice = NULL;//定义一个direct3D设备对象
int Color=0;//用于更改清空后台缓存时的颜色
//==========================================//
//==========================================//
//——————全局函数声明———————--—//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);//窗口处理函数
HRESULT D3Dinit(HWND hWnd, HINSTANCE hInstance);//d3d初始化函数
void Render();//d3d渲染函数
//==========================================//
//===========================================================================================//
//—————--------------------—程序的主函数WinMain———-----------------------------——-//
//===========================================================================================//
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
///
// //
//-----------设计窗口部分------------//
// //
///
WNDCLASSEX wndclassex = { 0 };//创建一个窗口类,并且记得初始化
wndclassex.cbSize = sizeof(wndclassex);//节数大小
wndclassex.style = CS_VREDRAW | CS_HREDRAW;//样式标记
wndclassex.lpfnWndProc = WndProc;//指向窗口事件处理函数的指针
wndclassex.hInstance = hInstance;//应用程序实例句柄
wndclassex.cbClsExtra = 0;//额外的类信息
wndclassex.cbWndExtra = 0;//额外的窗口信息
wndclassex.hIcon = LoadIcon(NULL, IDI_APPLICATION);//加载ico图标
wndclassex.hCursor = ::LoadCursor(NULL, IDC_ARROW);//指定窗口类的光标句柄
wndclassex.hbrBackground = (HBRUSH)GetStockObject(GRAY_BRUSH);//为成员指定一个灰色画刷
wndclassex.lpszMenuName = NULL;//要加入窗口的菜单名
wndclassex.lpszClassName = L"WINDOW_CLASS";//窗口类名
///
// //
//-----------注册窗口部分------------//
// //
///
RegisterClassEx(&wndclassex);//向我们的"政府申请注册"
///
// //
//-----------创建窗口部分------------//
// //
///
HWND hWnd = CreateWindowEx(NULL, L"WINDOW_CLASS",L"MyDirectDemo",//直接调用创建函数就好了
WS_OVERLAPPEDWINDOW | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT,
800, 600, NULL, NULL, hInstance, NULL);
ShowWindow(hWnd, nCmdShow);//显示窗口
UpdateWindow(hWnd);//更新窗口
///
/
//在此初始化Direct3D
D3Dinit(hWnd, hInstance);
///
// //
//------------主循环部分-------------//
// //
///
MSG msg = { 0 };//定义并初始化 meg
while (msg.message != WM_QUIT)//消息循环
{
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) //使用peekmessage或者getmessage获得消息
{
TranslateMessage(&msg);//转换消息
DispatchMessage(&msg);//分发消息给程序,然后交给os调用窗口处理函数
}
else
{
Render();
}
}
return msg.wParam;
}
/
// //
//------------窗口消息处理函数部分-------------//
// //
/
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
PAINTSTRUCT paintStruct;//定义一个paintstruct记录一些绘制信息
HDC hdc;//设备环境句柄
switch (message)//开始处理
{
case WM_PAINT://重绘消息 更新客户区
hdc = BeginPaint(hWnd, &paintStruct);//指定窗口进行绘图准备,并在ps结构中保存相关信息
EndPaint(hWnd, &paintStruct);//窗口绘图过程结束
break;
case WM_KEYDOWN://键盘按下消息
if (wParam == VK_ESCAPE)//如果是esc
DestroyWindow(hWnd);//销毁窗口,发送WM_DESTROY消息
break;
case WM_DESTROY://销毁消息
PostQuitMessage(0);//向os申请终止请求。
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);//默认的窗口过程处理函数
}
return 0;
}
HRESULT D3Dinit(HWND hWnd, HINSTANCE hInstance)
{
LPDIRECT3D9 pD3D = Direct3DCreate9(D3D_SDK_VERSION);//创建接口对象获取硬件信息并为后面的设备对象做准备
if (NULL == pD3D) { return E_FAIL; }
D3DCAPS9 caps;//用于存储设备信息的结构体
int vp = 0;//显卡支持的顶点运算模式
if (FAILED(pD3D->GetDeviceCaps(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, &caps)))
{//填入显卡序号,设备类型(硬件,软件)
return E_FAIL;
}
if (caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT)
vp = D3DCREATE_HARDWARE_VERTEXPROCESSING;//硬件顶点运算模式
else
vp = D3DCREATE_SOFTWARE_VERTEXPROCESSING;//软件顶点运算模式
D3DPRESENT_PARAMETERS d3dpp;//一个非常重要的结构体
ZeroMemory(&d3dpp, sizeof(d3dpp));//用零填充d3dpp这块内存
d3dpp.BackBufferWidth = 800;//缓冲区宽度
d3dpp.BackBufferHeight =600;//缓冲区高度
d3dpp.BackBufferFormat = D3DFMT_A8R8G8B8;//缓冲区保存像素格式D3DFORMAT//+
d3dpp.BackBufferCount = 1;//后台缓存的个数
d3dpp.MultiSampleType = D3DMULTISAMPLE_NONE;//多重采样的类型
d3dpp.MultiSampleQuality = 0;//多重采样的质量水平
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;//如何将后台缓存复制到前台缓存中
d3dpp.hDeviceWindow = hWnd;
d3dpp.Windowed = true;//使用窗口模式还是全屏模式
d3dpp.EnableAutoDepthStencil = true;//是否自动管理深度缓存
d3dpp.AutoDepthStencilFormat = D3DFMT_D24S8;//深度缓存像素格式
d3dpp.Flags = 0;//附加属性,通常为0
d3dpp.FullScreen_RefreshRateInHz = 0;//全屏模式时候的屏幕刷新率,
d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;//前后台交换的最大频率
if (FAILED(pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL,
hWnd, vp, &d3dpp, &g_pd3dDevice)))
return E_FAIL;
pD3D->Release();//LPDIRECT3D9接口对象的使命完成 释放
return S_OK;
}
void Render()
{
g_pd3dDevice->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(Color, 0, 255), 1.0, 0);
g_pd3dDevice->BeginScene();
g_pd3dDevice->EndScene();
g_pd3dDevice->Present(NULL, NULL, NULL, NULL);
Color += 1;
if (Color >= 255)
Color = 0;
}
好的,大家可以复制代码运行一下看看,是不是觉得很闪?因为我设置了个变量每次清空后台缓存时都将颜色的R值加1;
好吧,这一节就到这里,如果有不明白的,相信大家都明白,大家心中对渲染或许还有疑问,但是没关系,后面会详细介绍。当然有什么建议可以留言哦。感谢!