對3D編程期待已久,卻一直葉公好龍淺嘗輒止。近期在公司實習卻無具體的工作安排,琢磨着學習個新的手藝,就又想起了3D Programming。這次從大名鼎鼎的龍書(Introduction to 3D Game Progamming with Directx 9.0)開始學起,堅持...
作為入門的第一篇筆記,本文對D3D初始化過程做了總結。DirectX3D的初始化需要做兩部分工作:創建一個窗口、創建IDirect3DDevice9對象,窗口用於展示D3D渲染出來的場景;IDirect3DDevice9則是一個C++對象,表示用來displaying3D圖形的物理設備。本文也主要包含兩個部分:
- D3D的初始化
- 龍書中使用簡單框架的代碼分析
1.D3D的初始化
龍書中將一個D3D的初始化過程分為4個步驟:
- 獲取IDirect3D9接口的指針,該接口通常用於取得物理設備信息和創建IDirect3DDevice9對象。
- 檢測設備的處理能力(D3DCAPS9),檢測顯示適配器(display adapter)是否支持頂點的硬件處理。
- 初始化結構體D3DPRESENT_PARAMETERS,該結構指定了一些參數用來創建IDirect3DDevice9。
- 創建IDeirect3DDevice9對象。
1.1取得IDirect3D9指針
D3D中有專門的函數來獲取IDirect3D9指針
IDirect3D9* d3d9 = NULL;
d3d9 = Direct3DCreate9(D3D_SDK_VERSION);
D3D_SDK_VERSION是創建IDirect3D9唯一需要的參數,該參數能夠保證程序是基於當前版本的DirectX生成的。
1.2 檢測硬件的處理能力
在創建IDirect3DDevice9時需要指定其頂點處理類型(硬件或者軟件),硬件頂點處理速度較快,但是不是所有的顯示適配器都具有該能力。可以調用函數GetDeviceCaps
,該函數的原型如下
HRESULT IDirect3D9::GetDeviceCaps(
UINT Adapter,
D3DDEVTYPE DeviceType,
D3DCAPS9 *pCaps
);
- Adpater 指定要檢測的顯示適配器
- DeviceType 硬件類型,D3DDEVTYPE_HAL(硬件設備)、D3DDEVTYPE_REF(軟件設備)。
- pCaps 返回D3DCAPS9結構,包含了硬件的所支持的各種特性。
// 檢查硬件的頂點處理能力
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;
1.3 初始化 D3DPRESENT_PARAMETERS
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;
- BackBufferWidth,BackBufferHeight bakck buffer的寬和高
- BackBufferFormat back buffer的像素格式,例如:D3DFMT_A8R8G8B8
- BackBufferCount back buffer的數量,通常為1
- MultiSampleType,MultiSampleQuality 抗鋸齒的類型和質量
- SwapEffect 指定buffer在交換鏈(flipping chain)是如何被交換的,改值是D3DSWAPEFFECT類型的枚舉,其中D3DSWAPEFFECT_DISCARD是效率 最高的。
- hDeviceWindow 用於繪制的窗口句柄
- Windowed 是窗口模式還是全屏,true為全屏。
- EnableAutoDepthStencil 設置為true,D3D將自動的創建深度/模板buffer
- AutoDepthStencilFormat 深度/模板buffer的格式
- Flags 附加的一些特性,可以指定為0或者D3DPRESENTFLAG中某個值。比較推薦下面兩個值
- D3DPRESENTFLAG_LOCKABLE_BACKBUFFER back buffer能給被鎖定,這會降低程序的性能。
- D3DPRESENTFLAG_DISCARD_DEPTHSTENCIL 在present下一個buffer時,當前的深度/模板buffer會被“丟棄(discard)",這有利於提升程序的性能。”discard“的意思是深度/模板buffer所占用的存儲空間會被丟棄或者不可用。
- FullScreen_RefreshRateInHz 刷新頻率,使用D3DPRESENT_RATE_DEFAULT指定為默認的刷新頻率。
- PresentationInterval D3DPRESENT成員,兩個常用的標志
- D3DPRESENT_INTERVAL_IMMEDIATE 立即呈現
- D3DPRESENT_INTERVAL_DEFAULT D3D選中呈現的速度,通常等於刷新率。
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;
1.4 創建IDirect3Device9
填充D3DPRESENT_PARAMETERS后,IDirect3Device9就很簡單了
d3d9->CreateDevice(D3DADAPTER_DEFAULT,deviceType,hwnd,vp,&d3dpp,device);
2. 簡單框架 d3dUtility.h/cpp
在龍書的第二章提供了一個簡單框架,用於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 是窗口的消息處理函數。
2.1 亂碼處理
VS中的項目編碼默認的是Unicode,這樣在進行一些字符串處理(例如設置窗口標題)非常容易出現亂碼。這就需要加入頭文件tchar.h
#include <tchar.h>
在使用字符串的時候使用宏_T
轉換就可以避免亂碼,例如:
wc.lpszClassName = _T("Direct3D9App");
2.2 PeekMessage 和 GetMessage的區別
在該框架中,進入窗口循環的代碼如下:
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函數繪制圖形。
3 總結
本文是龍書第二章的學習筆記,總結了D3D初始化的4個步驟,並對龍書中提供的代碼框架作為簡單分析。