MFC架構下的DirectX8 (轉)

gugu99發表於2008-03-02
MFC架構下的DirectX8 (轉)[@more@]MFC架構下的8
第一章 MFC
(DX8MFC)
這裡的MFC框架指的是一個符合遊戲開發應用的框架,當然你也可以寫一個符合你要求的MFC框架。如果你對MFC比較熟悉的話可以直接從第二章開始閱讀。本框架是以後幾個例子的基礎,如果你對MFC不是很瞭解的話,就要認真閱讀本章,以求對這個MFC框架有一個深入的瞭解。
框架中包括兩個類:
CDX8MFCApp類和CFrameWin類,CDX8MFCApp類是應用類,CFrameWin類是框架的主類,以後我們的大部分程式碼都是從這裡擴充套件的。首先來看一看CDX8MFCApp類,它包括CDX8MFCApp()、ExitInstance()、InitInstance()、OnIdle(LONG lCount)等成員和一個Game。
InitInstance()成員函式在程式初始化時就被,在這裡我建立了一個視窗:
BOOL CDX8MFCApp::InitInstance()
{
// The one and only window has been initialized, so show and update it.
m_pMainWnd = new CFrameWin();
  m_pMainWnd->ShowWindow(m_nCmdShow);
  m_pMainWnd->UpdateWindow();
Game = (CFrameWin*) m_pMainWnd;
Game->Init();

return TRUE;
}

ExitInstance()成員函式在程式終止時被呼叫,在這裡我們釋放一些物件和指標:
int CDX8MFCApp::ExitInstance()
{
// TODO: Add your specialized code here and/or call the base class
Game->End();
delete Game;

return CWinApp::ExitInstance();
}

OnIdle(LONG lCount)成員函式會在沒有訊息要處理的時候被呼叫,也就是說OnIdle()成員函式會不斷的被呼叫,這正好被我們用作遊戲迴圈。
BOOL CDX8MFCApp::OnIdle(LONG lCount)
{
// TODO: Add your specialized code here and/or call the base class
if(Game->window_active==TRUE)
{
Game->Active();
Game->window_active=FALSE;
}

Game->Go();

return TRUE;
}

Game物件是一個CFrameWin類指標,我們在InitInstance()成員函式中建立了一個CFrameWin物件並把CFrameWin物件的指標值賦給Game。
下面我們來看一看CFrameWin類,它包括Active()、End()、Go()、Init()、Update()等成員函式。
Init()成員函式,你可以在這裡做一些自己的初始化。回顧CDX8MFCApp類的InitInstance()成員函式可知,在完成視窗初始化後InitInstance()成員函式里就呼叫了Game->Init(),也就是說Init()在視窗初始化後被呼叫。
void CFrameWin::Init()
{
AfxMessageBox("Init");
}

Go()成員函式會不斷的被迴圈呼叫,它又呼叫了Update()和DestroyWindow()。Update()用於視窗,呼叫DestroyWindow()則會結束應用程式。如果你把DestroyWindow()語句刪除掉,程式會不斷的迴圈。
void CFrameWin::Go() //Game迴圈
{
AfxMessageBox("Go");
Update();
DestroyWindow();
}

Active()成員函式會在應用程式被擊活的時候被呼叫。
void CFrameWin::Active() //視窗被啟用
{
TRACE("Activen");
AfxMessageBox("Active");
}

這個程式並不做任何事,只是一個MFC框架。你可以從http://gamedev.363 例子的源程式,或透過E-: laical@21cn.向本文作者索取。

第二章 初始化DirectX8
(DX8MFC1)
本例將以第一章的MFC框架為基礎對CFrameWin類進行擴充套件。主要加入了DrawScene()、InitDirect3D(HWND hwnd)和ShutdownDirect3D()三個函式。
InitDirect3D(HWND hwnd)函式對Direct3D進行初始化:
HRESULT CFrameWin::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.SEffect  = 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;

  // we do our own coloring, so disable lighting
  hr = pID3DDevice->SetRenderState(D3DRS_LIGHTING,
  FALSE);

  } while(0);

  return hr;
}

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)。剪裁(Clip)作為後臺表面(backface culling)被自動啟用。燈光也被自動啟用了,直到你定義頂點顏色之前,你可以禁止使用燈光:
pID3DDevice->SetRenderState(D3DRS_LIGHTING, FALSE);

DrawScene()函式:
HRESULT CFrameWin::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;

  // flback buffer to front
  hr = pID3DDevice->Present(NULL, NULL, NULL, NULL);
  } while(0);

  return hr;
}

Clear會填充你指定的緩衝區。你可以填充Z緩衝區、後臺緩衝區或摸板緩衝區(stencil buffer)。在這個例子中你將用綠色填充後臺緩衝區。所以,我們設定D3DCLEAR_TARGET標誌和綠色。
在本例中BeginScene和EndScene並沒有做什麼,但在以後的例子中我們會用到它的。這兩個函式是畫圖元時的例行公事程式碼,
這個函式不斷的翻轉後臺表面。我們可以不斷的在後臺表面畫一些東西,然後把後臺表面翻轉到前臺表面。

ShutdownDirect3D()函式
void CFrameWin::ShutdownDirect3D()
{
  HELPER_RELEASE(pTexture);
  HELPER_RELEASE(pIndexBuffer);
  HELPER_RELEASE(pStreata);
  HELPER_RELEASE(pID3DDevice);
  HELPER_RELEASE(pID3D);
}
ShutdownDirect3D釋放所有的介面。將來你可能要加入額外的程式碼來關閉Direct3D介面,但現在已經夠了。如果你執行程式,你將得到一個綠色背景的視窗。你可以按“ESC”鍵來退出應用程式。

第三章 畫三角形
(DX8MFC2)
定義你的頂點格式,Direct3D引入了一種可變形頂點格式(flexible vertex format)(FVF)的概念。在FVF中,你定義一個結構其中包括所需要的頂點組成部分。這個結構會隨著你的程式而改變,但在這裡你將初步把它定義成這個樣子:
struct MYVERTEX
{
  FLOAT x, y, z; // The tranormed position
  FLOAT rhw;  // 1.0 (reciprocal of homogeneous w)
  D 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->SetStream(0, pStreamData, sizeof(MYVERTEX));
SetVertexShader告訴Direct3D使用與CreateVertexBuffer同樣的格式。
SetStreamSource告訴Direct3D使用pStreamData作為當前頂點陣列,並取得所有元素的大小。
你現在可以加入畫三角形的程式碼了。在DrawScene()的BeginScene和EndScene之間加入如下程式碼:
int num_elems = sizeof(vertices) / sizeof(vertices[0]);
pID3DDevice->DrawPrimitive(D3DPT_TRIANGLELIST,
  0,
  num_elems / 3);
D3DPT_TRIANGLELIST標記將命令Direct3D畫不連續的三角形。你指定從的第0個頂點開始,指定所要畫的三角形數目。
如果正確的話,你會看到一個三角形畫在先前的綠色背景視窗上。

第四章 畫索引三角形
(DX8MFC3)
上一章的畫三角形方式執行是較低的,而實際上我們都會使用DrawIndexedPrimitive()而不是DrawPrimitive()。想一想,如果要畫兩個相連的三角形,共有四個頂點。用DrawIndexedPrimitive()畫要畫四個頂點,而用DrawPrimitive()畫則要畫六個頂點。
如果你可以頂點建立索引,你就可以用DrawIndexedPrimitive()畫三角形了。我們可以為一個三角形建立這樣的索引:
WORD indices[] = { 0, 1, 2 };
它表示三角形中,第一個頂點對應於頂點陣列的第0個頂點;三角形中,第二個頂點對應於頂點陣列的第1個頂點;三角形中,第三個頂點對應於頂點陣列的第2個頂點;
要畫索引三角形,首先要建立索引緩衝:
num_elems = sizeof(indices) / sizeof(indices[0]);
pID3DDevice->CreateIndexBuffer(sizeof(WORD) * num_elems,
  D3DUSAGE_WRITEONLY,
  D3DFMT_INDEX16,
  D3DPOOL_DEFAULT,
  &pIndexBuffer);
第二步是用頂點填充這個索引緩衝:
WORD *pIndex;
pIndexBuffer->Lock(0, 0, (BYTE **)&pIndex, 0);
for(ii = 0; ii < num_elems; ii++)
{
  pIndex[ii] = indices[ii];
}
pIndexBuffer->Unlock();
設定索引緩衝:
pID3DDevice->SetIndices(pIndexBuffer, 0);
把DrawScene()的相應的pID3DDevice->DrawPrimitive(...)換成:
pID3DDevice->DrawIndexedPrimitive(
  D3DPT_TRIANGLELIST,
  0,
  sizeof(indices) / sizeof(indices[0]),
  0,
  sizeof(indices) / sizeof(indices[0]) / 3);
執行程式的到的還是一個三角形。

第五章 加入帖圖
(DX8MFC4)
首先,在MYVERTEX結構中加入帖圖座標系tu和tv,並給頂點陣列賦以適當的值。
下一步,設定你的帖圖:
D3DXCreateTextureFromFile(pID3DDevice,
  "dx5_logo.bmp",
  &pTexture);
pID3DDevice->SetTexture(0, pTexture);
其中的"dx5_logo.bmp"指的是帖圖,你可以用其他的檔案代替它,執行程式你會看到一個帶帖圖的三角形。

第六章 帖圖立方體
(DX8MFC5)
現在樣我們來進入三維的世界吧!這裡你要啟用Z緩衝(z-buffer),設定立方體的材質,世界座標系和投影座標系。
啟用Z緩衝(z-buffer),你要在D3DPRESENT_PARAMETERS結構中加入:
present.EnableAutoDepthStencil  = TRUE;
present.AutoDepthStencilFormat  = D3DFMT_D16;
這裡告訴DirectX8使用16位的Z緩衝,下一步:
pID3DDevice->SetRenderState(D3DRS_ZENABLE, TRUE);
到這裡Z緩衝已經設定完成。最後你還要在DrawScene()中呼叫清除Z緩衝內容的程式碼:
pID3DDevice->Clear(0,
  NULL,
  D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER,
  D3DCOLOR_RGBA(0,63,0,0),
  1.0,
  0);
MYVERTEX結構改成:
struct MYVERTEX
{
  FLOAT x, y, z; // The transformed position
  DWORD color;  // The vertex color
  FLOAT tu, tv;  // Texture coordinates
};
在InitDirect3D()也作了相應改動,具體可見。
Direct3D中有多種矩陣,在這裡只使用其中的三個:世界、檢視和投影矩陣。世界矩陣變換會把正方體放在世界座標系中,檢視矩陣變換把正方體放在可視空間內,投影矩陣使正方體看起來有深度感。
BuildMatrices()函式將建立這三個矩陣:
void CFrameWin::BuildMatrices()
{
  D3DXMATRIX matrix;
  D3DXMatrixRotationY(&matrix, timeGetTime() / 1000.0f);
  pID3DDevice->SetTransform(D3DTS_WORLD, &matrix);

  D3DXMatrixLookAtLH(&matrix, &D3DXVECTOR3(0.0f, 3.0f, -5.0f), // 攝像機的空間位置
  &D3DXVECTOR3(0.0f, 0.0f, 0.0f),  // 攝象機觀察點
  &D3DXVECTOR3(0.0f, 1.0f, 0.0f)); // 攝象機向上方向向量
  pID3DDevice->SetTransform(D3DTS_VIEW, &matrix);

  // 設定我們的平截面為45度角
  D3DXMatrixPerspectiveFovLH(&matrix, D3DX_PI / 4, 4.0f / 3.0f, 1.0f, 100.0f);
  pID3DDevice->SetTransform(D3DTS_PROJECTION, &matrix);
}
執行本章的例子你將看到一個旋轉的正方體。

宣告:
本文的SourceCode可以從http://gamedev.363.net得到
歡迎您光臨我的主頁:http://gamedev.363.net
作者:陳偉凡
: laical@21cn.com
2001/1/12


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10748419/viewspace-1000292/,如需轉載,請註明出處,否則將追究法律責任。

相關文章