会员: 密码:  免费注册 | 忘记密码 | 会员登录 网页功能: 加入收藏 设为首页 网站搜索  
游戏开发 > 程序设计 > 3D图形
Direct3D 8.0 Immediate Mode 教學 I
发表日期:2007-01-16 17:04:28作者: 出处:  


從 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() 就是畫三角形的部分:

  1. DX8 已經沒有 D3DTLVERTEX的宣告了,Vertex 格式都得自行設計( Flexible Vertex Format,彈性端點格式),在此範例,只是要在螢幕上畫個 Gouraud Shaded的2D 三角形,所以只需要  (x,y,z,rhw) 與 端點顏色,所以 MYVERTEX 結構,就只有這兩大類資訊就夠了。
  2. 這個 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 會不正常。
  3. VertexBuffer 因為只需產生一次,因此設計個 bool 變數 - NeedCreateVBFlag,預設為 true ,在產生好 Vertex Buffer 後,設為 false ,就不會一直產生 Vertex Buffer。  這是為了解說方便,才這樣設計,其實在 InitD3D() 裡面產生,就可以省掉這個 bool 變數與檢查此變數的時間。
  4. RenderD3D() 第一步,就是用 Clear() 清螢幕背景與 zbuffer 。接下來畫三角形才有意義!
  5. 再來就是 BeginScene() 與 EndScene() 包起來的區域,通知 3D硬體做事的之前,一定要呼叫 BeginScene(),結束時要呼叫 EndScene()。
  6. CreateVertexBuffer() 之後,鎖住(Lock()) 該 Vertex Buffer ,才能用 memcpy() 拷貝事先準備好的資料進去,用完之後解鎖(Unlock)。
  7. 用SetVertexShader()指示 D3DDEVICE8 著色方式。D3DFVF_XYZRHW表示座標已經是轉換(Transformed)好的,直接就是螢幕座標。D3DFVF_DIFFUSE表示使用 Vertex 提供的彩色值。
  8. 用SetStreamSource() 指示 D3DDEVICE8 用哪一個 Vertex Buffer,在本範例當然就是 pVB了。
  9. 許多資訊都事先指定了,DrawPrimitive() 就只剩下3個參數了,第一個是 D3DPT_TRIANGLELIST,表示 pVB 裡面的端點資訊要三個、三個地形成一個個三角形,其他情況,例如D3DPT_LINELIST ,就是兩個、兩個地畫線出來,接下來的兩個參數就是端點起點與所畫物件的個數。
  10. 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。

返回顶部】 【打印本页】 【关闭窗口

关于我们 / 给我留言 / 版权举报 / 意见建议 / 网站编程QQ群   
Copyright ©2003- 2024 Lihuasoft.net webmaster(at)lihuasoft.net 加载时间 0.00388