DirectX 9 是由微軟開發的一組多媒體應用程式介面API,用於建立和執行基於Windows平臺的多媒體應用程式,尤其是遊戲。它是DirectX系列中的一個版本,於2002年釋出,是DirectX系列中的一個重要版本,DirectX 9在其釋出時引入了許多新的功能和效能最佳化,成為當時PC遊戲開發的主要標準,許多經典的PC遊戲使用了DX9作為其圖形和音訊渲染引擎。雖然後續出現了更多強大的引擎,但本質上都是可以相容Dx9的。
在使用Dx9引擎之前讀者需要自行下載該繪製庫,當然在課件中筆者已經為大家準備了綠色版,讀者可自行解壓到指定目錄下,在目錄下有一個Developer Runtime
其內部是引擎執行時所需要的執行環境,讀者可根據不同的需求安裝對應位數的執行庫,安裝成功後則可配置開發目錄,一般而言我們只需要關注Include
引入目錄,以及Lib
庫目錄即可。
讀者可自行開啟屬性頁面,並選中VC++目錄自行配置,如下圖所示;
13.1.1 初始化變數
在開始使用繪製庫之前我們需要一個可被自由繪製的畫布程式,該程式必須使用D3Dx9引擎生成以便於後續文章的測試工作,一般而言,使用DirectX 9
繪製圖形的流程包括初始化、建立資源、設定渲染狀態和頂點格式、更新資料、繪製圖形、渲染和清理資源構成,在使用之前讀者需要引入Dx9的標頭檔案以及所需定義部分,如下所示;
#include <windows.h>
#include <tchar.h>
#include <d3d9.h>
#pragma comment( lib, "d3d9.lib")
#define null NULL
#define RETURN return
#define FVF ( D3DFVF_XYZRHW | D3DFVF_DIFFUSE )
LPDIRECT3D9 g_pD3D = NULL;
LPDIRECT3DDEVICE9 g_pd3dDevice = NULL;
LPDIRECT3DVERTEXBUFFER9 g_pVB = NULL;
struct CUSTOMVERTEX
{
float x, y, z, rhw;
DWORD color;
};
13.1.2 LPDIRECT3D9
其中定義的全域性指標LPDIRECT3D9
是DX9
中的一個指標型別,表示一個Direct3D 9
的頂層物件。頂層物件是Direct3D
物件模型的頂級結構,它為應用程式提供了一組方法來進行3D
圖形渲染。LPDIRECT3D9介面可以用來建立和操作Direct3D 9
裝置物件IDirect3DDevice9
以及其他與圖形渲染相關的物件。在使用DX9進行圖形渲染之前,必須透過呼叫Direct3DCreate9
函式來建立一個IDirect3D9
介面的例項,並透過LPDIRECT3D9
型別的指標進行訪問和操作。例如,使用下面的程式碼可以建立一個LPDIRECT3D9物件:
LPDIRECT3D9 d3d9 = Direct3DCreate9(D3D_SDK_VERSION);
這將建立一個指向Direct3D 9
的頂層物件的指標,並將其分配給變數d3d9
。透過這個LPDIRECT3D9
物件,應用程式就可以執行各種與圖形渲染相關的操作,如建立頂點快取、紋理物件等。在程式結束時,應用程式必須透過呼叫LPDIRECT3D9
物件的Release
方法來釋放所有建立的Direct3D
物件,以防止記憶體洩漏。
d3d9->Release();
13.1.3 LPDIRECT3DDEVICE9
第二個全域性變數LPDIRECT3DDEVICE9
是DirectX 9
中表示3D裝置的指標型別,它是使用Direct3D
進行3D
渲染的關鍵物件。LPDIRECT3DDEVICE9物件表示著本次渲染中的3D物件在硬體上的運算環境,透過它可以對3D物件進行變換、光照和紋理等操作。透過LPDIRECT3D9
物件建立的步驟通常包括以下幾個步驟:
1.建立一個LPDIRECT3D9
物件,透過Direct3DCreate9
函式建立,如下所示:
LPDIRECT3D9 d3d9 = Direct3DCreate9(D3D_SDK_VERSION);
2.使用LPDIRECT3D9
物件建立一個IDirect3DDevice9
物件,可以透過呼叫LPDIRECT3D9
物件的CreateDevice
方法來建立,如下所示:
LPDIRECT3DDEVICE9 d3dDevice;
D3DPRESENT_PARAMETERS presentParams = {0};
presentParams.Windowed = TRUE;
presentParams.SwapEffect = D3DSWAPEFFECT_DISCARD;
d3d9->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd, D3DCREATE_HARDWARE_VERTEXPROCESSING, &presentParams, &d3dDevice);
這個程式碼片段建立了一個視窗化的3D裝置並將其儲存到變數d3dDevice
中。其中D3DADAPTER_DEFAULT
和D3DDEVTYPE_HAL
參數列示選擇預設顯示介面卡和硬體抽象層,hWnd
引數為視窗控制程式碼,D3DCREATE_HARDWARE_VERTEXPROCESSING
表示使用硬體進行頂點計算,&presentParams
為一個D3DPRESENT_PARAMETERS
結構體指標,用於配置呈現引數。
3.初始化3D裝置物件,可以設定一些統一的裝置狀態,如渲染狀態、混合模式等,它將禁用光照計算。如下所示:
d3dDevice->SetRenderState(D3DRS_LIGHTING, FALSE);
4.在進行有效的渲染之前,必須在每一幀開始時呼叫BeginScene
方法,以告知Direct3D
例項開始渲染,如下所示:
d3dDevice->BeginScene();
5.渲染3D物件,透過LPDIRECT3DDEVICE9
物件進行繪製,並進行相應的3D資料應用操作,如下所示:
d3dDevice->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 1);
渲染結束後,可以呼叫EndScene
方法通知Direct3D
例項結束渲染並顯示影像,如下所示:
d3dDevice->EndScene();
最後,使用SwapChain顯示影像,如下所示:
d3dDevice->Present(NULL, NULL, NULL, NULL);
在程式退出時,藉助於LPDIRECT3DDEVICE9
物件的Release
方法釋放所有建立的Direct3D
物件,以避免記憶體洩漏。
d3dDevice->Release();
13.1.4 LPDIRECT3DVERTEXBUFFER9
LPDIRECT3DVERTEXBUFFER9是DirectX 9
中表示頂點緩衝區的指標型別,它被用來儲存3D網格
的頂點資料,是Direct3D
遊戲開發中的一個重要概念之一。頂點緩衝區是一個可以包含頂點資料的記憶體塊,它可以儲存可繪製的幾何體(三角形、四邊形等)的頂點資料。
使用LPDIRECT3DVERTEXBUFFER9
物件儲存頂點資料,可以充分利用硬體加速能力,提高渲染效率和圖形效能,最佳化遊戲效能。
頂點緩衝區由頂點格式和頂點資料兩部分組成:
- 頂點格式(Vertex Format): 表示頂點資料的結構和排列方式。使用
D3DVertexElement9
和D3DVertexDeclaration9
等API進行建立、宣告以及管理。 - 頂點資料(Vertex Data): 包含了網格的所有頂點資料,如座標、法線、顏色、紋理座標等。可以使用
LPDIRECT3DVERTEXBUFFER9
物件儲存,同時還可以使用其他緩衝區型別如索引緩衝區(LPDIRECT3DINDEXBUFFER9)來儲存索引資料,方便後續渲染處理。
建立LPDIRECT3DVERTEXBUFFER9
物件的步驟通常如下:
首先,宣告並建立一個頂點緩衝區物件。在建立LPDIRECT3DVERTEXBUFFER9
物件時,需要指定緩衝區大小、緩衝區用法等引數。
LPDIRECT3DVERTEXBUFFER9 pVertexBuffer = NULL;
device->CreateVertexBuffer(vertexBufferSize, D3DUSAGE_WRITEONLY, FVF, D3DPOOL_MANAGED, &pVertexBuffer, NULL);
寫入頂點資料到頂點緩衝區,使用Lock方法可以將頂點緩衝區鎖定,返回已鎖定的頂點緩衝區指標,並且允許應用程式與鎖定的資料進行讀寫操作,然後使用Unlock
方法來解鎖。
float* pBuffer = NULL;
pVertexBuffer->Lock(0, 0, (void**)&pBuffer, 0);
memcpy(pBuffer, vertices, vertexBufferSize);
pVertexBuffer->Unlock();
繪製幾何體時,可以使用SetStreamSource
方法指定頂點緩衝區、頂點格式以及偏移量。最後呼叫DrawPrimitive
方法進行繪製。
device->SetFVF(FVF);
device->SetStreamSource(0, pVertexBuffer, 0, sizeof(Vertex));
device->DrawPrimitive(D3DPT_TRIANGLELIST, 0, numTriangles);
13.1.5 初始化繪圖引擎
接著我們來看一下我們是如何初始化一個D3D引擎的,InitD3D
函式會在遊戲程式啟動時被呼叫,以初始化3D裝置和相關環境,為後續的3D圖形渲染操作做好準備。初始化部分答題可總結為三步,首先呼叫Direct3DCreate9
用於建立一個Dx9引擎畫布,接著填充D3DPRESENT_PARAMETERS
結構,最後透過使用CreateDevice
實現對裝置的建立,當建立成功則會將指標儲存在LPDIRECT3D9
這個全域性結構指標內。
HRESULT InitD3D(HWND hWnd)
{
g_pD3D = Direct3DCreate9(D3D_SDK_VERSION);
D3DPRESENT_PARAMETERS d3dpp;
ZeroMemory(&d3dpp, sizeof(d3dpp));
d3dpp.Windowed = TRUE;
d3dpp.BackBufferFormat = D3DFMT_UNKNOWN;
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
g_pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd, D3DCREATE_HARDWARE_VERTEXPROCESSING, &d3dpp, &g_pd3dDevice);
return S_OK;
}
上述流程具體分析,步驟如下:
使用Direct3DCreate9
函式建立一個LPDIRECT3D9
物件,該物件表示頂層Direct3D
物件,負責管理和控制DX操作。
g_pD3D = Direct3DCreate9(D3D_SDK_VERSION);
建立並配置D3DPRESENT_PARAMETERS
結構體,該結構體用於描述渲染裝置的一些基本屬性,如視窗模式、後臺緩衝區格式、交換模式等。
D3DPRESENT_PARAMETERS d3dpp;
ZeroMemory(&d3dpp, sizeof(d3dpp));
d3dpp.Windowed = TRUE;
d3dpp.BackBufferFormat = D3DFMT_UNKNOWN;
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
在上述程式碼中,使用ZeroMemory()
函式將d3dpp
物件中除第1個成員外所有成員的值都重置為0。還設定了視窗模式(Windowed = TRUE,表示視窗化模式),後臺緩衝區格式(BackBufferFormat = D3DFMT_UNKNOWN,表示使用預設格式),以及交換模式(SwapEffect = D3DSWAPEFFECT_DISCARD,表示丟棄當前幀並替換為下一幀)。
使用CreateDevice
函式建立一個IDirect3DDevice9
物件,並儲存在變數g_pd3dDevice
中。
g_pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd, D3DCREATE_HARDWARE_VERTEXPROCESSING, &d3dpp, &g_pd3dDevice);
在這裡,第1個引數(D3DADAPTER_DEFAULT)表示使用預設顯示介面卡;第2個引數(D3DDEVTYPE_HAL)指定使用硬體抽象層,表示硬體加速;第3個引數(hWnd)是視窗控制程式碼;第4個引數(D3DCREATE_HARDWARE_VERTEXPROCESSING)表示使用硬體進行頂點處理。最後,使用&d3dpp、&g_pd3dDevice引數傳遞裝置建立資訊。最後,返回S_OK
表示函式執行成功。
初始化部分的第二步則是呼叫InitVB
這個函式,該函式用於建立頂點緩衝區,可以用於儲存3D網格的頂點資料,方便後續的渲染處理;
HRESULT InitVB()
{
CUSTOMVERTEX v[] =
{
100, 000, 0, 1, 0xffff0000,
300, 50, 0, 1, 0xff00ff00,
500, 400, 0, 1, 0xff0000ff
};
g_pd3dDevice->CreateVertexBuffer(3 * sizeof(v), 0, FVF, D3DPOOL_DEFAULT, &g_pVB, 0);
void* vb;
g_pVB->Lock(0, 0, (void**)&vb, 0);
memcpy(vb, v, sizeof(v));
g_pVB->Unlock();
return S_OK;
}
上述程式碼中,首先宣告一個CUSTOMVERTEX
型別的陣列v
,並將其作為輸入引數,其中每一個元素表示一個自定義的頂點,包括位置座標和顏色。
CUSTOMVERTEX v[] =
{
100, 000, 0, 1, 0xffff0000,
300, 50, 0, 1, 0xff00ff00,
500, 400, 0, 1, 0xff0000ff
};
在程式碼中,每個元素都包含了頂點的x、y、z
座標、齊次座標w,以及頂點的顏色。
呼叫CreateVertexBuffer
函式,建立一個頂點緩衝區物件,並將其儲存在變數g_pVB
中。該函式的第1個參數列示緩衝區大小,即儲存頂點資料的位元組數,這裡是3個頂點乘以每個頂點40個位元組(即一個CUSTOMVERTEX型別的大小);第2個引數是填充位元組的數值,設為0表示不填充;第3個引數是頂點格式,表示每個頂點包含的資訊,和CUSTOMVERTEX
資料結構一致;第4個引數是緩衝區型別,表示緩衝區的使用方式,D3DPOOL_DEFAULT表示快取區將用於GPU讀寫操作。最後,&g_pVB
是返回的頂點緩衝區物件。
g_pd3dDevice->CreateVertexBuffer(3 * sizeof(v), 0, FVF, D3DPOOL_DEFAULT, &g_pVB, 0);
對頂點緩衝區進行鎖定,使用Lock函式
使緩衝區可讀寫,並將頂點資料寫入緩衝區中。這裡使用void*
型別的指標vb指向頂點緩衝區中的第一個元素,並使用memcpy()
函式將頂點陣列的資料複製到頂點緩衝區中。並使用Unlock()
函式解除頂點緩衝區的鎖定。最後返回S_OK
,作為函式執行成功的標誌。
void* vb;
g_pVB->Lock(0, 0, (void**)&vb, 0);
memcpy(vb, v, sizeof(v));
g_pVB->Unlock();
接著對視窗中的圖形進行著色及初始化,
void Render()
{
g_pd3dDevice->Clear(0, 0, D3DCLEAR_TARGET, D3DCOLOR_XRGB(176, 196, 222), 1, 0);
// 設定背景色 黑色
// g_pd3dDevice->Clear(0, 0, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, 0), 1, 0);
g_pd3dDevice->BeginScene();
g_pd3dDevice->SetStreamSource(0, g_pVB, 0, sizeof(CUSTOMVERTEX));
g_pd3dDevice->SetFVF(FVF);
//g_pd3dDevice->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 10);
g_pd3dDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, 0, 4, 0, 4);
g_pd3dDevice->EndScene();
g_pd3dDevice->Present(0, 0, 0, 0);
}
使用Clear
函式清除背景,並設定新的背景色。這裡使用D3DCOLOR_XRGB(176, 196, 222)
,表示顏色值為R:176, G:196, B:222
的淺藍色。
g_pd3dDevice->Clear(0, 0, D3DCLEAR_TARGET, D3DCOLOR_XRGB(176, 196, 222), 1, 0);
使用BeginScene
函式開始渲染場景。
g_pd3dDevice->BeginScene();
設定頂點著色器的輸入資料來源。使用SetStreamSource
函式設定使用的頂點緩衝區,其中第1個引數是流編號,第2個引數是頂點緩衝區物件,第3個引數是緩衝區內頂點資料的起始點,第4個引數是頂點結構體的大小。
g_pd3dDevice->SetStreamSource(0, g_pVB, 0, sizeof(CUSTOMVERTEX));
設定頂點格式。使用SetFVF
函式描述頂點的結構,這裡的FVF常量是一個結構體標記符號,用於描述頂點的型別和結構。
g_pd3dDevice->SetFVF(FVF);
使用DrawPrimitive
函式或DrawIndexedPrimitive
函式繪製圖形,這裡使用的是後者。該函式繪製在緩衝區中的三角形列表,根據輸入的位置在緩衝區中查詢三角形點,再連線相鄰的三角形點,形成3D圖形。第1個引數(D3DPT_TRIANGLELIST)表示三角形列表,第2個引數是起始頂點索引,第3個引數是最小頂點索引,第4個引數是被繪製的總頂點數,第5個引數(0)表示要跳過的資料數量,第6個引數(4)表示每個圖元的頂點數。
g_pd3dDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, 0, 4, 0, 4);
使用EndScene
函式結束本次渲染。
g_pd3dDevice->EndScene();
使用Present函式展示渲染結果到視窗中。
g_pd3dDevice->Present(0, 0, 0, 0);
當有了上述初始化函式的封裝後,接著我們就可以在主函式內透過CreateWindow
函式建立一個窗體,並在初始化流程內透過呼叫InitD3D(hWnd)
以及InitVB()
對D3D引擎初始化,初始化後進入到該程式的訊息迴圈內,在訊息迴圈內除了透過TranslateMessage
捕獲訊息外,還需要不間斷的呼叫Render()
用於動態重新整理D3D窗體顯示,這樣則可實現動態繪製一個完整窗體並載入繪圖引擎的目的;
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
message == WM_CLOSE ? PostQuitMessage(0) : (void)0;
return DefWindowProc(hWnd, message, wParam, lParam);
}
int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE, PTSTR, int)
{
wchar_t cn[] = L"LySharkGame";
WNDCLASS wc;
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wc.lpszMenuName = NULL;
wc.lpszClassName = cn;
RegisterClass(&wc);
DWORD cxScreen = GetSystemMetrics(SM_CXSCREEN);
DWORD cyScreen = GetSystemMetrics(SM_CYSCREEN);
HWND hWnd = CreateWindow(cn, TEXT("LySharkGame"), WS_OVERLAPPEDWINDOW, (cxScreen - 1024) / 2, (cyScreen - 768) / 2, 1024, 768, NULL, NULL, hInstance, NULL);
ShowWindow(hWnd, SW_SHOW);
InitD3D(hWnd);
InitVB();
MSG msg;
ZeroMemory(&msg, sizeof(msg));
while (msg.message != WM_QUIT)
{
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else
{
Render();
}
}
return 0;
}
至此我們就得到了一個具有D3D
功能的窗體,當讀者開啟該窗體時即可看到一個標題為LySharkGame
的窗體,該窗體大小為標準的1024x768
這個窗體輸出效果如下圖所示;
本文作者: 王瑞
本文連結: https://www.lyshark.com/post/c0fa8f9c.html
版權宣告: 本部落格所有文章除特別宣告外,均採用 BY-NC-SA 許可協議。轉載請註明出處!