转:DirectX8.0初体验, 有点老,但写的挺好
有時候從一張白紙開始的感覺還是不錯的。微軟的上一個版本(DirectX7.0)是一個新的開始,畫圖元
(DrawPrimitive)開始形成了一種風格。
DirectX8(DX8)真正體現了其成熟之處。許多操作得到了改進和變得簡單,其高級功能更為易用。一個簡單的例
子,一個500行程序行的OpenGL程序,如果用DirectX8.0寫的話,大約在1000行左右。如今,DirectX8和OpenGL
看起來已經非常相似了。
DirectX8的變化實在是太大了,我不想花太多的時間去講述都發生了什么改變。取而代之的是,我將討論更多
的是現在的DirectX8.0 API更象什么,以及你如何立即取得更多的優勢。
DX8由6組API組成:DirectX圖形(包括Direct3D和D3DX函數庫),DirectX Audio(包括Direct Sound和Direct
Music),DirectInput, DirectPlay, DirectSetup, 和 DirectShow。DX8是非常龐大的,因此,我不想在這里
討論所有的DX8 API,本文會指導你使用DX8進行圖形和視頻編程。
我不知到是否有更新版本的DirectX Media會出現。DirectShow原來是DirectX Media的一部分,現在它已經是
DirectX8.0運行庫的一個基本部分了。對于開發者來講這是有利的,因為不用再分別安裝兩個運行庫。Direct
3D保留模式是Direct Media的一個組成部分,但D3DX函數庫更有取代它的可能。我想DirectX將會不斷地得來改
進,而Direct Media不再有改變,但舊的DirectX Media6.1仍然是可用的。
DirectX圖形
或許DirectX8.0中最令人耀眼改變就是DirectDraw了,DirectDraw完全地被Direct3D所取代了。
Direct3D作了重大的改動,引入了許多新的特性。你不再須要列舉任何的東西(設備)。Direct3D僅由12個接口
組成,它的遺傳圖表是非常單的:
(圖)
其中最COOL的一點特性就是加入了一種陰影語言(shader language)。和Renderman或Quake 3的陰影語言相比,
M$的的陰影語言更類似于匯編語言(assembly language)。然而它們的概念卻是一樣的。
D3DX是DirectX的一個高級函數庫。D3DX函數庫是非常靈活的,其包含的API可用于創建從精靈(sprites)、文字
到粘圖等所用的東西。使用D3DX將使你的取得跳躍性的進展。
DX8的矩陣操作是非常清析的(由其是在D3DX當中),并且工作起來更象OpenGL了。以下是其它一些與OpenGL相似
的地方:
Direct3D ?OpenGL
BeginScene?glBegin?
EndScene?glEnd
DrawPrimitive?glDrawElements
SetRenderState?glEnable
SetTexture?glBindTexture
Clear??glClear
許多的類似之處已經在早其版本中的DirectX中出現了,但新的DX8中明顯地體現到DX8與OpenGL有許多相似之處
。
2D編程并沒有完全被取代,DX8的D3DX庫中有一個精靈(scripte)接口。然而首選的2D編程方式應該是使用簡單
的粘圖。色彩鍵(chroma keying)被取消了,做透明效果的唯一方法是使用alpha混合(alpha blending)。
或許你會說我會很快成為這套API的狂熱者。或許你只習慣于OpenGL。但DX8作了許多改進,使用DX8開發你的游
戲已經不再成為問題了,我們將寫一些DX8的代碼來說明這一點。
DirectX圖形API是非常簡單而且功能強大的。當你使用DX8一段時日子后你會希望其它的DX8 API也象DirectX8
圖形API那樣簡單易用。
DirectShow
DirectShow是M$的視頻API,你可以在這里找到錄象機、數碼攝象機和DVD播放機等。游戲開發者可以很容易的
把視頻電影加進他門的游戲中。
有一些新的特性加入了DirectShow,但與游戲開發關系不大。例如歐洲的PAL制式,M$的ASF等。
播放Video是游戲開發者使用DirectShow的主要原因,一會兒我們將會寫一些這樣的代碼。
我們的程序主體
我會講解幾個演示程序,為了簡單起見,所有的演示程序都是基于同一程序主體的。
程序主體大約有90行,它是一個簡單的Win32程序。它的作用是建立一個Windows窗口并調用我定義的DirectX函
數。這些函數是InitDirect3D,ShutdownDirect3D,和DrawScene。
所有的Demo都使用同樣的ShutdownDirect3D函數和變量。
#define HELPER_RELEASE(x) { if(x) { (x)->Release(); (x) = NULL; }}
IDirect3D8 * pID3D?????????????????? = NULL;
IDirect3DDevice8 * pID3DDevice?????? = NULL;
IDirect3DVertexBuffer8 * pStreamData = NULL;
IDirect3DIndexBuffer8 * pIndexBuffer = NULL;
IDirect3DTexture8 * pTexture???????? = NULL;
void ShutdownDirect3D()
{
? HELPER_RELEASE(pTexture);
? HELPER_RELEASE(pIndexBuffer);
? HELPER_RELEASE(pStreamData);
? HELPER_RELEASE(pID3DDevice);
? HELPER_RELEASE(pID3D);
}
我首先定義我所用到的接口。注意:并不是所有的demo都會用到所有的接口的。只是為了程序主體的統一和簡
單化而已。
ShutdownDirect3D釋放所有的接口。將來你可能要加入額外的代碼來關閉Direct3D接口,但現在已經夠了。
現在讓我們開始我們的初始化代碼吧,請定位到InitDirect3D函數。IDirect3D是我們首先要用到的接口,你可
以這樣寫:
IDirect3D8 * pID3D = Direct3Dcreate8(D3D_SDK_VERSION);
在你使用pID3D以前,請檢查pID3D是否為非空。
你下一步通常是創建D3D設備,但在創建D3D設備之前你要調用GetAdapterDisplayMode方法取得必須的信息:
D3DDISPLAYMODE d3ddm;
pID3D->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &d3ddm);
接下來是取得當前顯示模式參數。下面的參數是Surface格式。你可以用這些參數來創建一個
D3DPRESENT_PARAMETERS結構:
D3DPRESENT_PARAMETERS present;
ZeroMemory(&present, sizeof(present));
present.SwapEffect????????????? = D3DSWAPEFFECT_COPY;
present.Windowed??????????????? = TRUE;
present.BackBufferFormat??????? = d3ddm.Format;
D3DPRESENT_PARAMETERS描述了顯示器Surface的信息,交換機制的類型,應用程序是窗口的還是全屏模式等信
息。
在本例中,Surface是以拷貝方法代替頁面翻轉的,因為它是一個窗口模式的應用程序。把后臺表面設置成與當
前顯示模式相匹配的格式,一個準備顯示的Surface可以Draw在后臺表面上。
現在你可以創建一個IDirect3DDevice8接口了:
pID3D->CreateDevice(D3DADAPTER_DEFAULT,
??????????????????? D3DDEVTYPE_HAL,
??????????????????? hwnd,
??????????????????? D3DCREATE_SOFTWARE_VERTEXPROCESSING,
??????????????????? &present,
??????????????????? &pID3DDevice);
這個函數有六個參數,幸運的是沒有一個是很復雜的。D3DADAPTER_DEFAULT告訴Direct3D使用主顯示器,只有
當你使用多顯示器時才是必須的。你可以使用一個數值來指定另外一個顯示器。調用IDirect3D的
GetAdapterCount將返回系統的適配器數目。
第二個參數,D3DDEVTYPE_HAL,告訴Direct3D使用硬件加速。其它選項包括D3DDEVTYPE_REF 和 D3DDEVTYPE_SW
,通常你都會希望使用硬件加速的,但有時侯你可能會使用軟件加速進行測試。
指定窗口取得焦點。如果是全屏應用程序,你需要一個最頂層窗口。
D3DCREATE_SOFTWARE_VERTEXPROCESSING指定頂點處理類型。你也可以使用硬件加速或是聯合類型,我不使用硬
件加速為的是廣泛的兼容性。如果你想支持T&L,則你必須使用硬件加速。
最后兩個參數很簡單,一個是你以前建立的,而pID3Ddevice是你現在要創建的IDirect3DDevice8接口。如果方
法返回D3DERR_NOTAVAILABLE,則你寫的參數是正確的,但你的設備不支持你指定的參數。
最完美的是這個方法自動為你創建后臺緩沖(back buffers)和深度緩沖(depth buffers)。剪裁(Clipping
)作為后臺表面(backface culling)被自動激活。燈光也被自動激活了,直到你定義頂點顏色之前,你可以
禁止使用燈光:
pID3DDevice->SetRenderState(D3DRS_LIGHTING, FALSE);
InitDirect3D函數已經完成,讓我們來看一看完整的InitDirect3D函數吧:
HRESULT InitDirect3D(HWND hwnd)
{
? pID3D = Direct3DCreate8(D3D_SDK_VERSION);
? HRESULT hr;
? do
? {
??? // we need the display mode so we can get
??? // the properties of our back buffer
??? D3DDISPLAYMODE d3ddm;
??? hr = pID3D->GetAdapterDisplayMode(
?????????????????????? D3DADAPTER_DEFAULT,
?????????????????????? &d3ddm);
??? if(FAILED(hr))
????? break;
??? D3DPRESENT_PARAMETERS present;
??? ZeroMemory(&present, sizeof(present));
??? present.SwapEffect?????? = D3DSWAPEFFECT_COPY;
??? present.Windowed???????? = TRUE;
??? present.BackBufferFormat = d3ddm.Format;
??? hr = pID3D->CreateDevice(D3DADAPTER_DEFAULT,
???????????????????????????? D3DDEVTYPE_HAL,
???????????????????????????? hwnd,
???????????????????????????? D3DCREATE_SOFTWARE_VERTEXPROCESSING,
???????????????????????????? &present,
???????????????????????????? &pID3DDevice);
??? if(FAILED(hr))
????? break;
??? hr = pID3DDevice->SetRenderState(D3DRS_LIGHTING,
???????????????????????????????????? FALSE);
? } while(0);
? return hr;
}
現在讓我們把注意力集中到DrawScene函數吧,在我們的第一個練習中我只想放一些簡單的東西在場景中。當你
達到這一點后,在這上面加些東西是很簡單的事。
下面是DrawScene函數:
HRESULT DrawScene()
{
? HRESULT hr;
? do
? {
??? // clear back buffer
??? hr = pID3DDevice->Clear(0,
??????????????????????????? NULL,
??????????????????????????? D3DCLEAR_TARGET,
??????????????????????????? D3DCOLOR_RGBA(0,63,0,0),
??????????????????????????? 0,
??????????????????????????? 0);
??? if(FAILED(hr))
????? break;
??? // start drawing
??? hr = pID3DDevice->BeginScene();
??? if(FAILED(hr))
????? break;
??? // Put all drawing code here
??? hr = pID3DDevice->EndScene();
??? if(FAILED(hr))
????? break;
??? // flip back buffer to front
??? hr = pID3DDevice->Present(NULL, NULL, NULL, NULL);
? } while(0);
? return hr;
}
這段代碼是很簡潔的。Clear會填充你指定的緩沖區。你可以填充Z緩沖區、后臺緩沖區或摸板緩沖區(stencil
buffer)。在這個例子中你將用綠色填充后臺緩沖區。所以,我們設定D3DCLEAR_TARGET標志和綠色。
在本例中BeginScene和EndScene并沒有做什么,但在以后的例子中我們會用到它的。這兩個函數是畫圖元時的
例行公事代碼,
這個函數不斷的翻轉后臺表面。我們可以不斷的在后臺表面畫一些東西,然后把后臺表面翻轉到前臺表面。
如果你現在就運行程序,你將得到一個綠色背景的窗口。如果一切都正常,你就可以編寫代碼去畫一個三角形
了,要知道畫圖元乃是游戲編程的核心。
畫三角形(d3d2.cpp)
三角形有幾個有趣的特性,是它們在三維編程中吸引人的地方。它們總是平面,三角形的組合可以成為任何的
幾何體。在后面的例子中我們會用三角形去建立一個立方體。通常我會在我的第一個三維引擎中做一個立方體
旋轉的例子。如果它適合于一個程序員,那么它也適合于另外一個程序員。
在它的最簡單形式當中,是一個由三個頂點組成的三角形。程序員是怎樣定義這些頂點的呢。一個二維的三角
形你可以用x和y軸坐標系來定義每一個點。一個完善的三維程序可能會定義頂點的坐標系,轉換坐標系,顏色
,幾個紋理坐標系和其它一些信息。
從嚴格的語義學來講,如何運用這些信息在OpenGL和Direct3D之間只有些微的區別。畫一些不連續的三角形時
,你可以分別的定義它們。當你畫一個模型的時候許多的頂點在三角形之間是共享的,所以,為每一個三角形
保存三個頂點是低效率的。在OpenGL和Direct3D中你都可以在一個大的陣列中為一個模型指定所有的頂點。三
角形的個數被定義成三的整數倍。你可以把這個陣列傳給一個函數,例如,DrawIndexedPrimitive,使用它你
可以立即畫出一個三維模型。
定義你的頂點格式,Direct3D引入了一種可變形頂點格式(flexible vertex format)(FVF)的概念。在FVF
中,你定義一個結構其中包括所需要的頂點組成部分。這個結構會隨著你的程序而改變,但在這里你將初步把
它定義成這個樣子:
struct MYVERTEX
{
? FLOAT x, y, z; // The transformed position
? FLOAT rhw;???? // 1.0 (reciprocal of homogeneous w)
? DWORD color;?? // The vertex color
};
示例的開始定義了一個頂點結構,頂點的名稱,和每一個三角形的頂點。在你的InitDirect3D函數中,你必須
創建一個頂點緩沖區:
int num_elems = sizeof(vertices) / sizeof(vertices[0]);
pID3DDevice->CreateVertexBuffer(sizeof(MYVERTEX) *
??????????????????????????????? num_elems,
??????????????????????????????? D3DUSAGE_WRITEONLY,
??????????????????????????????? D3DFVF_XYZRHW|D3DFVF_DIFFUSE,
??????????????????????????????? D3DPOOL_DEFAULT,
??????????????????????????????? &pStreamData);
函數的第一個參數是頂點結構的字節大小。在應用程序還不能讀取頂點之前,傳一個D3DUSAGE_WRITEONLY標記
給它。這里可以有不同的標記來指定如何處理你的頂點,但現在你可以確信Direct3D已經能正確的工作了。
下一步,指定我們用的是什么FVF格式。當你還沒有使用坐標系預轉換之前,指定為D3DFVF_XYZRHW標記。以后
你使用自己的矩陣坐標系轉換時,把它改成D3DFVF_XYZ。D3DFVF_DIFFUSE告訴Direct3D,我們將為每一個頂點
指定顏色。D3DPOOL_DEFAULT指定內存的管理模式。
最后一個參數是頂點緩沖區的指針,在例子1中你已經定義了它,但并沒有用上。
如果你不向頂點緩沖區填入有用數據的話,頂點緩沖區是沒有用的:
MYVERTEX *v;
pStreamData->Lock(0, 0, (BYTE**)&v, 0);
for(int ii = 0; ii < num_elems; ii++)
{
? v[ii].x???? = vertices[ii].x;
? v[ii].y???? = vertices[ii].y;
? v[ii].z???? = vertices[ii].z;
? v[ii].rhw?? = vertices[ii].rhw;
? v[ii].color = vertices[ii].color;
}
pStreamData->Unlock();
這是不難理解的,Lock返回一個你想寫入頂點數據的指針。下一步你從你的頂點陣列中拷貝數據。然后,反還
這個指針。
這一對的調用可以告訴Direct3D你的FVF格式,并設定頂點陣列為當前的活動頂點陣列。(你可以有多個頂點陣
列)。
pID3DDevice->SetVertexShader(D3DFVF_XYZRHW | D3DFVF_DIFFUSE);
pID3DDevice->SetStreamSource(0, pStreamData, sizeof(MYVERTEX));
SetVertexShader告訴Direct3D使用與CreateVertexBuffer同樣的格式。
SetStreamSource告訴Direct3D使用pStreamData作為當前頂點陣列,并取得所有元素的大小。
你現在可以加入畫三角形的代碼了。在BeginScene和EndScene之間加入如下代碼:
int num_elems = sizeof(vertices) / sizeof(vertices[0]);
pID3DDevice->DrawPrimitive(D3DPT_TRIANGLELIST,
?????????????????????????? 0,
?????????????????????????? num_elems / 3);
D3DPT_TRIANGLELIST標記將命令Direct3D畫不連續的三角形。你指定從索引的第0個頂點開始,指定所要畫的三
角形數目。
如果正確的話,你會看到一個三角形畫在先前的綠色背景窗口上。
聲明:
本文的英文原作者是:Toby "Ace" Jones 作者電子郵件:tjones@hot-shot.com
我首先看到英文原作的網站是:http://gamedev.net
本文的SourceCode和英文原作都可以從http://gamedev.net得到
歡迎您光臨我的主頁:http://gamedev.363.net
陳偉凡
E-mail: laical@21cn.com
2000/12/19
總結
以上是生活随笔為你收集整理的转:DirectX8.0初体验, 有点老,但写的挺好的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 家庭记账本开发记录(6)
- 下一篇: 一辆车到底需要多少芯片?