1. 程式人生 > >DirectX 3D程式設計及其最簡單例子

DirectX 3D程式設計及其最簡單例子

介面程式設計一直是程式設計師的一個比較高的門檻,Windows上最強大的3DDirectX和開源的OpenGL庫雖然強大,但是資料寥寥無幾。再加上其複雜的變數型別和結構體,很多人望而卻步。

大名鼎鼎的魔獸爭霸是基於DirectX庫上執行的,而反恐精英則使用了OpenGL技術。在圖形介面的執行效率上,原來Windows自帶的底層GDI庫我認為已經比較高效,但DirectX卻更神奇。DirectX有強大的圖形庫,直接和顯示卡的GPU打交道,而且封裝了非常複雜繁瑣的操作指令,我個人比較喜研究神奇的技術。

我也是DirectX的一名初學者,假如你對DirectX已經很熟,本文沒有什麼參考價值。由於水平有限,所以可能會有說錯的地方。

一年前在網上搜了一下,DirectX的資料實在少得可憐。英文版的實在難看,於是在網上直接下載了個Microsoft DirectX SDK (April 2006),期待在幫助中找到例子。

Microsoft DirectX SDK (April 2006)的幫助有C++.Net的例子,C++的例子都能編譯通過, 但是VS2008DirectX的相容性好像不是很好。使用VS2008開啟.net的例子需要版本轉換,轉換後編譯不通過,在他的例子裡修改了一下,好不容易通過了,但是下一個例子又要改。

最近重拾對DirectX的興趣,到微軟網站下載了最新的SDKMicrosoft DirectX SDK (August 2009)

。這個版本的例子和幫助中,直接去掉了.Net的例子。可能是由於我水平問題沒有下載對的版本,也可能是.netDirectX的呼叫還不是很方便,於是看來學DirectX,沒有什麼捷徑好走了,還得去面對複雜而高效的C++

我在微軟的官方網站上下載DirectXSDK是不用收費的,大家大可不用第一時間考慮有沒有試用版,直接到官方網站下載。,下載最新版本。

學習DirectX比較好的教程應該是Andy Pike寫的教程,但那是針對老版本的DirectX 8,而且是英文的。網上有Aman JIANG將它的教程譯成中文的版本,大家可以去搜搜。

安裝了SDK後,有一些簡單的例子。但是微軟認為簡單的例子對於

DirectX的初學者來說,一點也不簡單,因為它們為20多個例子寫了一個通用框架,20多個例子都去呼叫這些公共類,這個通用框架很多東西是對於最簡單的例子是沒有必要的,影響了程式碼的閱讀性。這個我和Aman JIANG一樣的認為。所以對於DirectX初學者來說,SDK的幫助其實幫助不大。假如你的水平非常高,可以閱讀使用它自帶的例子,它的例子有VS2005VS2008兩個版本,大家可以用VS2005VS2008這兩個編輯工具直接開啟對應的例子去閱讀,假如你用慣了VS6.0,那麼也可以用VS6.0去編寫,標頭檔案和庫檔案在安裝目錄的includelib目錄,記得要附加編譯才能通過。

於是我還是回到最原始,使用Andy Pike的最原始例子,建立最原始的win32程式,使用DirectX的最簡單的函式,最後是編譯通過了,但是要注意,Andy Pike的例子僅僅是DirectX8的,我們下載的是最新的DirectX9 10 11SDK,已經沒有DirectX8的庫了,所以我們能要做相應的修改。

下面我以一個不斷旋轉的正方體做一個例子,附上全部程式碼:(對Andy PikeDX8的例子作了相應版本轉換的修改

閱讀本例子需要一定的win32的程式設計基礎,假如你不熟悉,很難看懂此程式碼,可以到我的上一篇文章去把win32的程式設計基礎看懂。

#pragma comment(lib,"d3d9.lib")

#pragma comment(lib,"d3dx9.lib")

#include <d3dx9.h>

//d3dx8.lib d3d8.lib

LPDIRECT3D9 g_pD3D = NULL;

LPDIRECT3DDEVICE9 g_pD3DDevice = NULL;

LPDIRECT3DVERTEXBUFFER9 g_pVertexBuffer = NULL; // Buffer to hold vertices

struct CUSTOMVERTEX

{

FLOAT x, y, z;

DWORD colour;

};

#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ|D3DFVF_DIFFUSE)

#define SafeRelease(pObject) if(pObject != NULL) {pObject->Release(); pObject=NULL;}

HRESULT InitialiseD3D(HWND hWnd)

{

//First of all, create the main D3D object. If it is created successfully we

//should get a pointer to an IDirect3D8 interface.

g_pD3D = Direct3DCreate9(D3D_SDK_VERSION);

if(g_pD3D == NULL)

{

return E_FAIL;

}

//Get the current display mode

D3DDISPLAYMODE d3ddm;

if(FAILED(g_pD3D->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &d3ddm)))

{

return E_FAIL;

}

//Create a structure to hold the settings for our device

D3DPRESENT_PARAMETERS d3dpp;

ZeroMemory(&d3dpp, sizeof(d3dpp));

//Fill the structure.

//We want our program to be windowed, and set the back buffer to a format

//that matches our current display mode

d3dpp.Windowed = TRUE;

//d3dpp.SwapEffect = D3DSWAPEFFECT_COPY_VSYNC;

d3dpp.SwapEffect=D3DSWAPEFFECT_DISCARD;

d3dpp.BackBufferFormat = d3ddm.Format;

//Create a Direct3D device.

if(FAILED(g_pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd,

D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &g_pD3DDevice)))

{

return E_FAIL;

}

//Turn on back face culling. This is becuase we want to hide the back of our polygons

g_pD3DDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_CCW);

//Turn off lighting becuase we are specifying that our vertices have colour

g_pD3DDevice->SetRenderState(D3DRS_LIGHTING, FALSE);

return S_OK;

}

HRESULT InitialiseVertexBuffer()

{

VOID* pVertices;

//Store each point of the cube together with it's colour

//Make sure that the points of a polygon are specified in a clockwise direction,

//this is because anti-clockwise faces will be culled

//We will use a three triangle strips to render these polygons (Top, Sides, Bottom).

CUSTOMVERTEX cvVertices[] =

{

//Top Face

{-5.0f, 5.0f, -5.0f, D3DCOLOR_XRGB(0, 0, 255),},//Vertex 0 - Blue

{-5.0f, 5.0f, 5.0f, D3DCOLOR_XRGB(255, 0, 0),},//Vertex 1 - Red

{5.0f, 5.0f, -5.0f, D3DCOLOR_XRGB(255, 0, 0),},//Vertex 2 - Red

{5.0f, 5.0f, 5.0f, D3DCOLOR_XRGB(0, 255, 0),},//Vertex 3 - Green

//Face 1

{-5.0f, -5.0f, -5.0f, D3DCOLOR_XRGB(255, 0, 0),},//Vertex 4 - Red

{-5.0f, 5.0f, -5.0f, D3DCOLOR_XRGB(0, 0, 255),},//Vertex 5 - Blue

{5.0f, -5.0f, -5.0f, D3DCOLOR_XRGB(0, 255, 0),},//Vertex 6 - Green

{5.0f, 5.0f, -5.0f, D3DCOLOR_XRGB(255, 0, 0),},//Vertex 7 - Red

//Face 2

{5.0f, -5.0f, 5.0f, D3DCOLOR_XRGB(0, 0, 255),},//Vertex 8 - Blue

{5.0f, 5.0f, 5.0f, D3DCOLOR_XRGB(0, 255, 0),},//Vertex 9 - Green

//Face 3

{-5.0f, -5.0f, 5.0f, D3DCOLOR_XRGB(0, 255, 0),},//Vertex 10 - Green

{-5.0f, 5.0f, 5.0f, D3DCOLOR_XRGB(255, 0, 0),},//Vertex 11 - Red

//Face 4

{-5.0f, -5.0f, -5.0f, D3DCOLOR_XRGB(255, 0, 0),},//Vertex 12 - Red

{-5.0f, 5.0f, -5.0f, D3DCOLOR_XRGB(0, 0, 255),},//Vertex 13 - Blue

//Bottom Face

{5.0f, -5.0f, -5.0f, D3DCOLOR_XRGB(0, 255, 0),},//Vertex 14 - Green

{5.0f, -5.0f, 5.0f, D3DCOLOR_XRGB(0, 0, 255),},//Vertex 15 - Blue

{-5.0f, -5.0f, -5.0f, D3DCOLOR_XRGB(255, 0, 0),},//Vertex 16 - Red

{-5.0f, -5.0f, 5.0f, D3DCOLOR_XRGB(0, 255, 0),},//Vertex 17 - Green

};

//Create the vertex buffer from our device.

if(FAILED(g_pD3DDevice->CreateVertexBuffer(18 * sizeof(CUSTOMVERTEX),

0, D3DFVF_CUSTOMVERTEX,

D3DPOOL_DEFAULT, &g_pVertexBuffer,NULL)))

{

return E_FAIL;

}

//Get a pointer to the vertex buffer vertices and lock the vertex buffer

//if(FAILED(g_pVertexBuffer->Lock(0, sizeof(cvVertices), (BYTE**)&pVertices, 0)))

if(FAILED(g_pVertexBuffer->Lock(0, sizeof(cvVertices), (void**)&pVertices, 0)))

{

return E_FAIL;

}

//Copy our stored vertices values into the vertex buffer

memcpy(pVertices, cvVertices, sizeof(cvVertices));

//Unlock the vertex buffer

g_pVertexBuffer->Unlock();

return S_OK;

}

void SetupRotation()

{

//Here we will rotate our world around the x, y and z axis.

D3DXMATRIX matWorld, matWorldX, matWorldY, matWorldZ;

//Create the transformation matrices

D3DXMatrixRotationX(&matWorldX, timeGetTime()/400.0f);

D3DXMatrixRotationY(&matWorldY, timeGetTime()/400.0f);

D3DXMatrixRotationZ(&matWorldZ, timeGetTime()/400.0f);

//Combine the transformations by multiplying them together

D3DXMatrixMultiply(&matWorld, &matWorldX, &matWorldY);

D3DXMatrixMultiply(&matWorld, &matWorld, &matWorldZ);

//Apply the tansformation

g_pD3DDevice->SetTransform(D3DTS_WORLD, &matWorld);

}

void SetupCamera()

{

//Here we will setup the camera.

//The camera has three settings: "Camera Position", "Look at Position" and "Up Direction"

//We have set the following:

//Camera Position:(0, 0, -30)

//Look at Position: (0, 0, 0)

//Up direction:Y-Axis.

D3DXMATRIX matView;

D3DXMatrixLookAtLH(&matView, &D3DXVECTOR3(0.0f, 0.0f,-30.0f),//Camera Position

&D3DXVECTOR3(0.0f, 0.0f, 0.0f),//Look At Position

&D3DXVECTOR3(0.0f, 1.0f, 0.0f));//Up Direction

g_pD3DDevice->SetTransform(D3DTS_VIEW, &matView);

}

void SetupPerspective()

{

//Here we specify the field of view, aspect ration and near and far clipping planes.

D3DXMATRIX matProj;

D3DXMatrixPerspectiveFovLH(&matProj, D3DX_PI/4, 1.0f, 1.0f, 500.0f);

g_pD3DDevice->SetTransform(D3DTS_PROJECTION, &matProj);

}

void Render()

{

if(g_pD3DDevice == NULL)

{

return;

}

//Clear the backbuffer to black

g_pD3DDevice->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);

//Begin the scene

g_pD3DDevice->BeginScene();

//Setup the rotation, camera, and perspective matrices

SetupRotation();

SetupCamera();

SetupPerspective();

//Rendering our objects

//STDMETHOD(SetStreamSource)(THIS_ UINT StreamNumber,IDirect3DVertexBuffer9* pStreamData,UINT OffsetInBytes,UINT Stride) PURE;

g_pD3DDevice->SetStreamSource(0, g_pVertexBuffer,0, sizeof(CUSTOMVERTEX));

//g_pD3DDevice->SetVertexShader((IDirect3DVertexShader9*)D3DFVF_CUSTOMVERTEX);

g_pD3DDevice->SetFVF(D3DFVF_CUSTOMVERTEX);

g_pD3DDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);//Top

g_pD3DDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP, 4, 8);//Sides

g_pD3DDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP, 14, 2);//Bottom

//End the scene

g_pD3DDevice->EndScene();

//Filp the back and front buffers so that whatever has been rendered on the back buffer

//will now be visible on screen (front buffer).

g_pD3DDevice->Present(NULL, NULL, NULL, NULL);

}

void CleanUp()

{

SafeRelease(g_pVertexBuffer);

SafeRelease(g_pD3DDevice);

SafeRelease(g_pD3D);

}

void GameLoop()

{

//Enter the game loop

MSG msg;

BOOL fMessage;

PeekMessage(&msg, NULL, 0U, 0U, PM_NOREMOVE);

while(msg.message != WM_QUIT)

{

fMessage = PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE);

if(fMessage)

{

//Process message

TranslateMessage(&msg);

DispatchMessage(&msg);

}

else

{

//No message to process, so render the current scene

Render();

}

}

}

//The windows message handler

LRESULT WINAPI WinProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)

{

switch(msg)

{

case WM_DESTROY:

PostQuitMessage(0);

return 0;

break;

case WM_KEYUP:

switch (wParam)

{

case VK_ESCAPE:

//User has pressed the escape key, so quit

DestroyWindow(hWnd);

return 0;

break;

}

break;

}

return DefWindowProc(hWnd, msg, wParam, lParam);

}

//Application entry point

INT WINAPI WinMain(HINSTANCE hInst, HINSTANCE, LPSTR, INT)

{

//Register the window class

WNDCLASSEX wc = {sizeof(WNDCLASSEX), CS_CLASSDC, WinProc, 0L, 0L,

GetModuleHandle(NULL), NULL, NULL, NULL, NULL,

"DX Project 3", NULL};

RegisterClassEx(&wc);

//Create the application's window

HWND hWnd = CreateWindow("DX Project 3", "www.andypike.com: Tutorial 3",

WS_OVERLAPPEDWINDOW, 50, 50, 500, 500,

GetDesktopWindow(), NULL, wc.hInstance, NULL);

//Initialize Direct3D

if(SUCCEEDED(InitialiseD3D(hWnd)))

{

//Show our window

ShowWindow(hWnd, SW_SHOWDEFAULT);

UpdateWindow(hWnd);

//Initialize Vertex Buffer

if(SUCCEEDED(InitialiseVertexBuffer()))

{

//Start game running: Enter the game loop

GameLoop();

}

}

CleanUp();

UnregisterClass("DX Project 3", wc.hInstance);

return 0;

}

遊戲動起來是怎樣實現的呢?為什麼我們設計的模型能像電影那樣動起來呢?

我們在main函式的最後,呼叫了一個自定義叫GameLoop的函式,通過死迴圈的方式不斷去呼叫自定義的Render函式名的函式,去重新整理螢幕的顯示。

其實遊戲跟電影一樣,也是這樣將畫面快速地更換,加上DirectX繪畫的高效性,我們感覺不到畫面其實是一卡一卡的。

DX介面的一個很重要的概念是裝置(deviceg_pD3DDevice,其實跟我們平時用到的GDIDC類似,裝置就是一個要繪製的物件,我們將畫面繪製到裝置上。

好了,這幾乎是最簡單的例子了,在SDK上所謂最簡單的例子,其實比這個要複雜很多。初學者可以用這個作為最基本框架,作為參考去讀它自帶的更接近應用的框架。

值得注意的是,DX 3D的概念中,所有的3D圖形都是由無數的三角形組成,一個圓球其實也是用無數個三角形構成,三角形越多,效果越平滑,跟數學上曲線是由無數折線構成的原理是一樣的,當然,三角形越多消耗的GPU的處理時間當然也越多。假如你的立體幾何的邏輯建模能力非常優秀,不用任何3D 建模工具都能通過無數個三角形和顏色組合構建出一個物件或者人物,但我想,應該沒有這麼牛比的人吧。