对3D编程期待已久,却一直叶公好龙浅尝辄止。近期在公司实习却无具体的工作安排,琢磨着学习个新的手艺,就又想起了3D Programming。这次从大名鼎鼎的龙书(Introduction to 3D Game Progamming with Directx 9.0)开始学起,坚持…
作为入门的第一篇笔记,本文对D3D初始化过程做了总结。DirectX3D的初始化需要做两部分工作:创建一个窗口、创建IDirect3DDevice9对象,窗口用于展示D3D渲染出来的场景;IDirect3DDevice9则是一个C++对象,表示用来displaying3D图形的物理设备。本文也主要包含两个部分:
1. D3D的初始化
2. 龙书中使用简单框架的代码分析
龙书中将一个D3D的初始化过程分为4个步骤:
1. 获取IDirect3D9接口的指针,该接口通常用于取得物理设备信息和创建IDirect3DDevice9对象。
2. 检测设备的处理能力(D3DCAPS9),检测显示适配器(display adapter)是否支持顶点的硬件处理。
3. 初始化结构体D3DPRESENT_PARAMETERS,该结构指定了一些参数用来创建IDirect3DDevice9。
4. 创建IDeirect3DDevice9对象。
D3D中有专门的函数来获取IDirect3D9指针
IDirect3D9* d3d9 = NULL; d3d9 = Direct3DCreate9(D3D_SDK_VERSION);
D3D_SDK_VERSION是创建IDirect3D9唯一需要的参数,该参数能够保证程序是基于当前版本的DirectX生成的。
在创建IDirect3DDevice9时需要指定其顶点处理类型(硬件或者软件),硬件顶点处理速度较快,但是不是所有的显示适配器都具有该能力。可以调用函数GetDeviceCaps
,该函数的原型如下
HRESULT IDirect3D9::GetDeviceCaps(
UINT Adapter,
D3DDEVTYPE DeviceType,
D3DCAPS9 *pCaps
);
// 检查硬件的顶点处理能力
D3DCAPS9 caps;
d3d9->GetDeviceCaps(D3DADAPTER_DEFAULT, deviceType, &caps);
int vp = 0 ; //顶点处理类型
if(caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT)
vp = D3DCREATE_HARDWARE_VERTEXPROCESSING;
else
vp = D3DCREATE_SOFTWARE_VERTEXPROCESSING;
D3DPRESENT_PARAMETERS用于指定IDirect3Device9的一些特性,其声明如下:
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;
/* FullScreen_RefreshRateInHz must be zero for Windowed mode */
UINT FullScreen_RefreshRateInHz;
UINT PresentationInterval;
} D3DPRESENT_PARAMETERS;
D3DPRESENT_PARAMETERS d3dpp;
d3dpp.BackBufferWidth = width;
d3dpp.BackBufferHeight = height;
d3dpp.BackBufferFormat = D3DFMT_A8R8G8B8;
d3dpp.BackBufferCount = 1;
d3dpp.MultiSampleType = D3DMULTISAMPLE_NONE;
d3dpp.MultiSampleQuality = 0;
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
d3dpp.hDeviceWindow = hwnd;
d3dpp.Windowed = windowed;
d3dpp.EnableAutoDepthStencil = true;
d3dpp.AutoDepthStencilFormat = D3DFMT_D24S8;
d3dpp.Flags = 0;
d3dpp.FullScreen_RefreshRateInHz = D3DPRESENT_RATE_DEFAULT;
d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;
填充D3DPRESENT_PARAMETERS后,IDirect3Device9就很简单了
d3d9->CreateDevice(D3DADAPTER_DEFAULT,deviceType,hwnd,vp,&d3dpp,device);
在龙书的第二章提供了一个简单框架,用于D3D的初始化工作。
d3dUtility.h如下:
namespace d3d
{
bool InitD3D(HINSTANCE hInstance, //应用程序实例
int width,int height, //Back buffer的size
bool windowed, //是否全屏
D3DDEVTYPE deviceType, //设备类型 HAL或者REF
IDirect3DDevice9** device); // 创建的设备 [out]
int EnterMsgLoop(bool (*ptr_display)(float timeDelta));
LRESULT CALLBACK WndProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam);
template<class T> void Release(T t)
{
if(t)
{
t->Release();
t = NULL;
}
}
template<class T> void Delete(T t)
{
if(t)
{
delete t;
t = NULL;
}
}
}
函数Init3D创建程序的主窗口,并进行D3D的初始化工作,初始化完成返回IDirect3Device9的指针。
EnterMsgLoop 进入窗口的消息循环,它的参数是一个显示函数,该函数是用于图形的绘制。
Release 用于释放一个COM接口
Delete 用于删除一个对象
WndProc 是窗口的消息处理函数。
VS中的项目编码默认的是Unicode,这样在进行一些字符串处理(例如设置窗口标题)非常容易出现乱码。这就需要加入头文件tchar.h
#include <tchar.h>
在使用字符串的时候使用宏_T
转换就可以避免乱码,例如:
wc.lpszClassName = _T("Direct3D9App");
在该框架中,进入窗口循环的代码如下:
int d3d::EnterMsgLoop(bool (*ptr_display)(float timeDelta))
{
MSG msg;
::ZeroMemory(&msg,sizeof(MSG));
static float lastTime = (float)timeGetTime();
while(msg.message != WM_QUIT)
{
if(::PeekMessage(&msg,0,0,0,PM_REMOVE))
{
::TranslateMessage(&msg);
::DispatchMessage(&msg);
}
else
{
float currTime = static_cast<float>(timeGetTime());
float timeDelta = (currTime - lastTime) * 0.001f;
ptr_display(timeDelta);
lastTime = currTime;
}
}
return msg.wParam;
}
在上述代码中取得消息使用的是PeekMessage
而不是常用的GetMessage
。PeekMessage和GetMessage都是从消息队列中抓取消息,如果消息队列为空,程序的主线程会被操作系统挂起。过一段时间,当操作系统再次执行该线程时,如果消息队列仍然为空,这时这两个函数的行为则就不一样了:
* GetMessage 会直接返回,阻塞当前线程,操作系统去执行其他的线程。
* PeekMessage 则不同,它会取回控制权,使得当前线程再执行一段时间。上面的代码中,消息队列如果为空就执行display函数绘制图形。
本文是龙书第二章的学习笔记,首先总结了D3D初始化的4个步骤,最后对龙书中提供的代码框架作为分析,并对出现的乱码问题和PeekMessage作了分析。