從 John Carmack 拒用 DirectX 3.0 ,到 DirectX 5.0 勉強堪用,DirectX 6.0/7.0 持續改良,Microsoft 累積了這麼多經驗,終於推出了DirectX 8.0,不但架構上做了許多大幅度的改進 (Direct3D/DirectDraw融為一體,DirectShow變成基本元件...),API 也簡化許多,以前光是 Initialize 出一個 D3DDevice,可能就得寫個數十行,現在是數行就解決了。總算是 Windows Game Programmer 的一大福音。
不囉唆,筆者照舊寫個 Windowed Mode 畫 TLVERTEX的程式,算是介紹初始化的過程,下次的教學再加強為需要 Transform 的 LVERTEX。(DirectX 8.0 已經取消 TLVERTEX, LVERTEX等觀念,Vertex 結構都得自訂,不過觀念類似,筆者還是沿用以前的說法)。
完整程式列表 //----------------------------------------------------------------------------- // File: d3d8im.cpp // // Desc: This is the first tutorial for using Direct3D 8.0. // // http://latte.fanmesh.com //----------------------------------------------------------------------------- #include <d3d8.h>
//----------------------------------------------------------------------------- // Global variables //----------------------------------------------------------------------------- LPDIRECT3D8 pD3D = NULL; // Used to create the D3DDevice LPDIRECT3DDEVICE8 pDev = NULL; // Our RenderD3Ding device LPDIRECT3DVERTEXBUFFER8 pVB = NULL;
//----------------------------------------------------------------------------- // Name: InitD3D() // Desc: Initializes Direct3D //----------------------------------------------------------------------------- HRESULT InitD3D( HWND hWnd ) { // Create the D3D object, which is needed to create the D3DDevice. if( NULL == ( pD3D = Direct3DCreate8( D3D_SDK_VERSION ) ) ) return E_FAIL;
// Get the current desktop display mode D3DDISPLAYMODE d3ddm; if( FAILED( pD3D->GetAdapterDisplayMode( D3DADAPTER_DEFAULT, &d3ddm ) ) ) return E_FAIL;
D3DPRESENT_PARAMETERS d3dpp; ZeroMemory( &d3dpp, sizeof(d3dpp) );
d3dpp.Windowed = TRUE; d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; d3dpp.BackBufferFormat = d3ddm.Format;
if( FAILED( pD3D->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &pDev ) ) ) { return E_FAIL; }
// Device state would normally be set here return S_OK; }
//----------------------------------------------------------------------------- // Name: CloseD3D() // Desc: Releases all previously initialized objects //----------------------------------------------------------------------------- void CloseD3D(void) { if( pVB != NULL) pVB->Release();
if( pDev != NULL) pDev->Release();
if( pD3D != NULL) pD3D->Release(); }
//----------------------------------------------------------------------------- // Name: RenderD3D() // Desc: Draws the scene //----------------------------------------------------------------------------- struct MYVERTEX { FLOAT x,y,z; FLOAT rhw; DWORD color; };
#define NUM_VERT 3
struct MYVERTEX vert[NUM_VERT]= { 140, 50, 0, 1.0f, 0x00FF0000, 240,200, 0, 1.0f, 0x0000FF00, 40,200, 0, 1.0f, 0x000000FF };
BOOL NeedCreateVBFlag=true;
void RenderD3D(void) { if( NULL == pDev ) return;
// Clear the backbuffer to a blue color pDev->Clear( 0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0,0,0), 1.0f, 0 );
// Begin the scene pDev->BeginScene();
//----------------------------------------------------------------------------- // RenderD3Ding of scene objects can happen here //----------------------------------------------------------------------------- if(NeedCreateVBFlag) { pDev->CreateVertexBuffer(sizeof(MYVERTEX)*NUM_VERT, D3DUSAGE_WRITEONLY, D3DFVF_XYZRHW|D3DFVF_DIFFUSE, D3DPOOL_DEFAULT, &pVB); NeedCreateVBFlag=false; }
MYVERTEX *v; pVB->Lock(0,0,(BYTE **)&v,0); memcpy(v,vert,sizeof(MYVERTEX)*NUM_VERT); pVB->Unlock();
pDev->SetVertexShader(D3DFVF_XYZRHW|D3DFVF_DIFFUSE); pDev->SetStreamSource(0,pVB,sizeof(MYVERTEX)); pDev->DrawPrimitive(D3DPT_TRIANGLELIST,0,NUM_VERT/3); //-----------------------------------------------------------------------------
// End the scene pDev->EndScene();
// Present the backbuffer contents to the display pDev->Present( NULL, NULL, NULL, NULL ); }
//----------------------------------------------------------------------------- // Name: MsgProc() // Desc: The window's message handler //----------------------------------------------------------------------------- LRESULT WINAPI MsgProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam ) { switch( msg ) { case WM_DESTROY: PostQuitMessage( 0 ); return 0; }
return DefWindowProc( hWnd, msg, wParam, lParam ); }
//----------------------------------------------------------------------------- // Name: WinMain() // Desc: The application's entry point //----------------------------------------------------------------------------- INT WINAPI WinMain( HINSTANCE hInst, HINSTANCE, LPSTR, INT ) { MSG msg;
// Register the window class WNDCLASSEX wc = { sizeof(WNDCLASSEX), CS_CLASSDC, MsgProc, 0L, 0L, GetModuleHandle(NULL), NULL, NULL, NULL, NULL, "D3D Tutorial", NULL }; RegisterClassEx( &wc );
// Create the application's window HWND hWnd = CreateWindow( "D3D Tutorial", "D3D8 Tutorial 01", WS_OVERLAPPEDWINDOW, 100, 100, 300, 300, GetDesktopWindow(), NULL, wc.hInstance, NULL );
// Initialize Direct3D if( SUCCEEDED( InitD3D( hWnd ) ) ) { // Show the window ShowWindow( hWnd, SW_SHOWDEFAULT ); UpdateWindow( hWnd );
while(1) { if(PeekMessage( &msg, NULL, 0, 0, PM_NOREMOVE )) { if( GetMessage( &msg, NULL, 0, 0 ) ) { TranslateMessage(&msg); DispatchMessage(&msg); } else { // This msg is WM_QUIT. break; } } else { RenderD3D(); } } }
// Clean up everything and exit the app CloseD3D(); UnregisterClass( "D3D Tutorial", wc.hInstance ); return 0; }
現在逐段解釋原始程式:
1.#include 只要帶入 d3d8.h , Direct3D 8.0 的宣告就都有了,夠簡單了吧! //----------------------------------------------------------------------------- // File: d3d8im.cpp // // Desc: This is the first tutorial for using Direct3D 8.0. // // http://latte.fanmesh.com //----------------------------------------------------------------------------- #include <d3d8.h>
2.此範例只需要3個全域變數 pD3D、pDev、pVB,其中 pD3D 就是最新的 Direct3D/DirectDraw 綜合體,或者該稱為 Direct Graphics, 得先產生就對了。 pDev是用來通知3D 硬體畫三角形。pVB 是 Vertex Buffer,現在通知 pDev畫三角形,三角形的資訊一定要放在 Vertex Buffer 了。 //----------------------------------------------------------------------------- // Global variables //----------------------------------------------------------------------------- LPDIRECT3D8 pD3D = NULL; // Used to create the D3DDevice LPDIRECT3DDEVICE8 pDev = NULL; // Our RenderD3Ding device LPDIRECT3DVERTEXBUFFER8 pVB = NULL;
3.先看看 WinMain與 Message Procedure 的架構,WinMain 先呼叫 InitD3D(),將顯示模式準備好,再來就是利用 PeekMessage() 沒得到任何事件的時機( 也就是 OnIdle() ),呼叫 RenderD3D 畫三角形。萬一 GetMessage() 得到 WM_QUIT 訊息,傳回值就會是 False,就利用 break 脫離 while(1) 這個無窮迴圈,自然而然地執行 CloseD3D()。 MsgProc() 幾乎沒做任何事, 只有碰到 WM_DESTROY 時, 就利用 PostQuitMessage()送出 WM_QUIT 訊息,通知目前迴圈 break。 //----------------------------------------------------------------------------- // Name: MsgProc() // Desc: The window's message handler //----------------------------------------------------------------------------- LRESULT WINAPI MsgProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam ) { switch( msg ) { case WM_DESTROY: PostQuitMessage( 0 ); return 0; }
return DefWindowProc( hWnd, msg, wParam, lParam ); }
//----------------------------------------------------------------------------- // Name: WinMain() // Desc: The application's entry point //----------------------------------------------------------------------------- INT WINAPI WinMain( HINSTANCE hInst, HINSTANCE, LPSTR, INT ) { MSG msg;
// Register the window class WNDCLASSEX wc = { sizeof(WNDCLASSEX), CS_CLASSDC, MsgProc, 0L, 0L, GetModuleHandle(NULL), NULL, NULL, NULL, NULL, "D3D Tutorial", NULL }; RegisterClassEx( &wc );
// Create the application's window HWND hWnd = CreateWindow( "D3D Tutorial", "D3D8 Tutorial 01", WS_OVERLAPPEDWINDOW, 100, 100, 300, 300, GetDesktopWindow(), NULL, wc.hInstance, NULL );
// Initialize Direct3D if( SUCCEEDED( InitD3D( hWnd ) ) ) { // Show the window ShowWindow( hWnd, SW_SHOWDEFAULT ); UpdateWindow( hWnd );
while(1) { if(PeekMessage( &msg, NULL, 0, 0, PM_NOREMOVE )) { if( GetMessage( &msg, NULL, 0, 0 ) ) { TranslateMessage(&msg); DispatchMessage(&msg); } else { // This msg is WM_QUIT. break; } } else { RenderD3D(); } } }
// Clean up everything and exit the app CloseD3D(); UnregisterClass( "D3D Tutorial", wc.hInstance ); return 0; }
4.看完了 WinMain() ,就知道架構是 InitD3D(), RenderD3D(), CloseD3D()了。先看看 InitD3D() 與 CloseD3D()。 InitD3D() 第一步就是利用 Direct3DCreate8(D3D_SDK_VERSION) 產生我們要的 Direct3D8 這個 COM object。 再來就是利用 GetAdapterDisplayMode() 獲得目前的顯示模式的硬體資訊。 利用上述資訊(d3dpp.BackBufferFormat),另外加上換頁模式(d3dpp.SwapEffect)、是否為視窗模式(d3dpp.Windowed) ,才能用 CreateDevice 產生符合目前顯示模式的 pDev變數。 至於 CloseD3D(),就是單純地釋放 pD3D, pDev, pVB 那三個全域變數。 //----------------------------------------------------------------------------- // Name: InitD3D() // Desc: Initializes Direct3D //----------------------------------------------------------------------------- HRESULT InitD3D( HWND hWnd ) { // Create the D3D object, which is needed to create the D3DDevice. if( NULL == ( pD3D = Direct3DCreate8( D3D_SDK_VERSION ) ) ) return E_FAIL;
// Get the current desktop display mode D3DDISPLAYMODE d3ddm; if( FAILED( pD3D->GetAdapterDisplayMode( D3DADAPTER_DEFAULT, &d3ddm ) ) ) return E_FAIL;
D3DPRESENT_PARAMETERS d3dpp; ZeroMemory( &d3dpp, sizeof(d3dpp) );
d3dpp.Windowed = TRUE; d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; d3dpp.BackBufferFormat = d3ddm.Format;
if( FAILED( pD3D->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &pDev ) ) ) { return E_FAIL; }
// Device state would normally be set here return S_OK; }
//----------------------------------------------------------------------------- // Name: CloseD3D() // Desc: Releases all previously initialized objects //----------------------------------------------------------------------------- void CloseD3D(void) { if( pVB != NULL) pVB->Release();
if( pDev != NULL) pDev->Release();
if( pD3D != NULL) pD3D->Release(); }
5.RenderD3D() 就是畫三角形的部分:
- DX8 已經沒有 D3DTLVERTEX的宣告了,Vertex 格式都得自行設計( Flexible Vertex Format,彈性端點格式),在此範例,只是要在螢幕上畫個 Gouraud Shaded的2D 三角形,所以只需要 (x,y,z,rhw) 與 端點顏色,所以 MYVERTEX 結構,就只有這兩大類資訊就夠了。
- 這個 2D 三角形三點座標分別是 ( 140, 50), (240,200), (40,200) ,z軸都沒用到,都填0 ,表示是離鏡頭最近的狀況,rhw 意義上是世界座標轉換(transform)成螢幕座標時,轉換結果其實是 (x,y,z,w), rhw 就是 w的倒數,為什麼是 (x,y,z,w) 牽涉到 3D圖學的座標轉換過程,這裡先填 1.0f 就對了,不是 1.0f 會不正常。
- VertexBuffer 因為只需產生一次,因此設計個 bool 變數 - NeedCreateVBFlag,預設為 true ,在產生好 Vertex Buffer 後,設為 false ,就不會一直產生 Vertex Buffer。 這是為了解說方便,才這樣設計,其實在 InitD3D() 裡面產生,就可以省掉這個 bool 變數與檢查此變數的時間。
- RenderD3D() 第一步,就是用 Clear() 清螢幕背景與 zbuffer 。接下來畫三角形才有意義!
- 再來就是 BeginScene() 與 EndScene() 包起來的區域,通知 3D硬體做事的之前,一定要呼叫 BeginScene(),結束時要呼叫 EndScene()。
- CreateVertexBuffer() 之後,鎖住(Lock()) 該 Vertex Buffer ,才能用 memcpy() 拷貝事先準備好的資料進去,用完之後解鎖(Unlock)。
- 用SetVertexShader()指示 D3DDEVICE8 著色方式。D3DFVF_XYZRHW表示座標已經是轉換(Transformed)好的,直接就是螢幕座標。D3DFVF_DIFFUSE表示使用 Vertex 提供的彩色值。
- 用SetStreamSource() 指示 D3DDEVICE8 用哪一個 Vertex Buffer,在本範例當然就是 pVB了。
- 許多資訊都事先指定了,DrawPrimitive() 就只剩下3個參數了,第一個是 D3DPT_TRIANGLELIST,表示 pVB 裡面的端點資訊要三個、三個地形成一個個三角形,其他情況,例如D3DPT_LINELIST ,就是兩個、兩個地畫線出來,接下來的兩個參數就是端點起點與所畫物件的個數。
- Present() 就是進行換頁了,因為在 InitD3D() 都指定好換頁模式了,這裡就顯的很簡單了,都給 NULL就行了。兩頁交換是電腦繪圖的常用技巧。
|
//----------------------------------------------------------------------------- // Name: RenderD3D() // Desc: Draws the scene //----------------------------------------------------------------------------- struct MYVERTEX { FLOAT x,y,z; FLOAT rhw; DWORD color; };
#define NUM_VERT 3
struct MYVERTEX vert[NUM_VERT]= { 140, 50, 0, 1.0f, 0x00FF0000, 240,200, 0, 1.0f, 0x0000FF00, 40,200, 0, 1.0f, 0x000000FF };
BOOL NeedCreateVBFlag=true;
void RenderD3D(void) { if( NULL == pDev ) return;
// Clear the backbuffer to a blue color pDev->Clear( 0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0,0,0), 1.0f, 0 );
// Begin the scene pDev->BeginScene();
//----------------------------------------------------------------------------- // RenderD3Ding of scene objects can happen here //----------------------------------------------------------------------------- if(NeedCreateVBFlag) { pDev->CreateVertexBuffer(sizeof(MYVERTEX)*NUM_VERT, D3DUSAGE_WRITEONLY, D3DFVF_XYZRHW|D3DFVF_DIFFUSE, D3DPOOL_DEFAULT, &pVB); NeedCreateVBFlag=false; }
MYVERTEX *v; pVB->Lock(0,0,(BYTE **)&v,0); memcpy(v,vert,sizeof(MYVERTEX)*NUM_VERT); pVB->Unlock();
pDev->SetVertexShader(D3DFVF_XYZRHW|D3DFVF_DIFFUSE); pDev->SetStreamSource(0,pVB,sizeof(MYVERTEX)); pDev->DrawPrimitive(D3DPT_TRIANGLELIST,0,NUM_VERT/3); //-----------------------------------------------------------------------------
// End the scene pDev->EndScene();
// Present the backbuffer contents to the display pDev->Present( NULL, NULL, NULL, NULL ); } 結論:
 跟筆者 DirectX 7.0 的教學文件比起來,DirectX 8.0 簡化到不用微軟規劃好的 Framework ,也能寫點東西出來。如果讀者從來沒學過 DirectX , DirectX 8.0 真是一個很好的切入點。 這次介紹是基本觀念為主,所以還是畫 TLVERTEX ,下次就著重在畫世界座標的東西,真正進入3D。
|