DirectX 9.0c遊戲開發手記之RPG編程自學日誌之16: Drawing with DirectX Graphics (用DirectX圖形繪圖)(第10-12節)


        本文由哈利_蜘蛛俠原創,轉載請註明出處!有問題請聯繫[email protected]

 

        這一次我們繼續來講述Jim Adams 老哥的RPG編程書籍第二版第二章的第10節:Particles (粒子),第11節:Depth Sorting and Z-Buffering (深度排序和Z-緩存),以及第12節:Working with Viewports (使用視口)。這兩節的內容都不多,所以就放在一期裏面講了。

 

 

原文翻譯:

 

===============================================================================

 

2.10 Particles (粒子)

 

        大爆炸、煙霧痕跡,甚至是追隨着一個hurdling(不知道咋個翻譯法)魔法飛彈的尾巴的小小火星都是一種叫做粒子(particles)的特殊的效果的傑作。粒子遵循着和廣告牌技術同樣的技術,用起來也同樣簡單。要使用粒子,你建立一些被煙霧、火苗、火星或任何你想要使用的圖形所紋理映射的多邊形。在適當的時候,你啓用alpha混合(可選)並這樣繪製粒子,使得它們面朝觀察點(使用廣告牌技術)。最後的結果就是一張由被混合後的物體組成的抽象畫,你可以用來產生一些酷炫的效果。

        關於粒子的一件很酷的事情就是它們實際上可以是任何尺寸的,因爲你可以創建一個放縮矩陣來與粒子多邊形的世界變換矩陣結合起來。這意味着你只需要使用一個多邊形來繪製你所有的粒子,除非粒子的紋理要變化,而在這種情況下多邊形的數目應該與紋理的數目相匹配。

        是時候來創建一個粒子圖像了。你可以從一個圓形開始,它在中心位置是實心的(不透明的),並且在靠近邊緣的時候變得越來越透明(如圖2.22所示)。



        現在,建立4個頂點來使用兩個多邊形(爲了優化,使用三角形帶)。頂點的座標代表一個粒子的默認尺寸,這個尺寸你將會在稍後進行放大。(感覺這句話的邏輯不對啊!PS:我的翻譯可沒有問題。)每一個粒子可以具有不同的性質,包括其顏色(通過使用材質)。

        然後你使用這個結構(哪個結構?),連同一個包含兩個多邊形(用於創建一個正方形)的頂點緩存,來將多邊形渲染到3-D設備中。在被繪製之前,每一個粒子都通過其自己的世界矩陣進行擺放(當然,使用廣告牌技術)。你將世界變換矩陣與每個粒子的縮放矩陣變換進行結合。然後你設定一個材質(使用IDirect3DDevice9::SetMaterial 函數)來改變粒子的顏色,組後你繪製出這個粒子。

        下面是一個創建一個粒子頂點緩存並將之繪製到設備中的例子:

 

// g_pD3DDevice = pre-initialized deviceobject
 
// define a custom vertex structure and descriptor
typedef struct {
  FLOATx, y, z;  // Local 3-D coordinates
  FLOATu, v;     // Texture coordinates
} sVertex;
#define VertexFVF (D3DFVF_XYZ |D3DFVF_TEX1)
 
// Particle vertex buffer and texture
IDirect3DVertexBuffer9 *g_pParticleVB =NULL;
IDirect3DTexture9      *g_pParticleTexture = NULL;
 
BOOL SetupParticle()
{
    BYTE*Ptr;
    sVertexVerts[4] = {
      {-1.0f,  1.0f, 0.0f, 0.0f, 0.0f },
      { 1.0f,  1.0f, 0.0f, 1.0f, 0.0f },
      {-1.0f, -1.0f, 0.0f, 0.0f, 1.0f },
      { 1.0f, -1.0f, 0.0f, 1.0f, 1.0f }
    };
 
    // Create particle vertex buffer and stuff in data
    if(FAILED(g_pD3DDevice->CreateVertexBuffer(                  \
                         sizeof(sVertex)*4,0, VertexFVF,         \
                         D3DPOOL_MANAGED,&g_pParticleVB, NULL)))
        return FALSE;
 
    if(FAILED(g_pParticleVB->Lock(0,0,(void**)&Ptr, 0)))
        return FALSE;
 
    memcpy(Ptr,Verts, sizeof(Verts));
    g_pParticleVB->Unlock();
    
    // Get particle texture
    D3DXCreateTextureFromFile(g_pD3DDevice,“particle.bmp”,      \
                                               &g_pParticleTexture);
    returnTRUE;
}
</pre><pre name="code" class="cpp">BOOL DrawParticle(float x, float y, floatz, float scale)
{
    D3DXMATRIX matWorld, matView, matTransposed;
    D3DXMATRIX matTrans, matScale;
    D3DMATERIAL9 d3dm;
 
    // Set render states (alpha blending and attributes)
    g_pD3DDevice->SetRenderState(D3DRS_ALPHABLENDENABLE,TRUE);
    g_pD3DDevice->SetRenderState(D3DRS_SRCBLEND,                 \
                                                D3DBLEND_SRCALPHA);
    g_pD3DDevice->SetRenderState(D3DRS_DESTBLEND,D3DBLEND_ONE);
 
    // Turn on ambient lighting
    g_pD3DDevice->SetRenderState(D3DRS_AMBIENT,0xffffffff);
 
    // Set stream source to particle vertex buffer
    g_pD3DDevice->SetStreamSource(0,g_pParticleVB, 0,              \
                                    sizeof(sVertex));
 
    // Set vertex shader to particle type
    g_pD3DDevice->SetFVF(VertexFVF);
 
    // Set texture
    g_pD3DDevice->SetTexture(0,g_pParticleTexture);
 
    // Set the particle color
    ZeroMemory(&d3dm,sizeof(D3DMATERIAL9));
    d3dm.Diffuse.r= d3dm.Ambient.r = 1.0f;
    d3dm.Diffuse.g= d3dm.Ambient.g = 1.0f;
    d3dm.Diffuse.b= d3dm.Ambient.b = 0.0f;
    d3dm.Diffuse.a= d3dm.Ambient.a = 1.0f;
    g_pD3DDevice->SetMaterial(&d3dm);
 
    // Build scaling matrix
    D3DXMatrixScaling(&matScale,scale, scale, scale);
 
    // Build translation matrix
    D3DXMatrixTranslation(&matTrans,x, y, z);
 
    // Build the billboard matrix
    g_pD3DDevice->GetTransform(D3DTS_VIEW,&matView);
    D3DXMatrixTranspose(&matTransposed,&matView);
 
    // Combine matrices to form world translation matrix
    D3DXMatrixMultiply(&matWorld,&matScale, &matTransposed);
    D3DXMatrixMultiply(&matWorld,&matWorld, &matTrans);
 
    // Set world transformation
    g_pD3DDevice->SetTransform(D3DTS_WORLD,&matWorld);
 
    // Draw particle
    g_pD3DDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP,0, 2);
 
    // Turn off alpha blending
    g_pD3DDevice->SetRenderState(D3DRS_ALPHABLENDENABLE,FALSE);
 
    return TRUE;
}

 

        這兩個函數展示瞭如何建立一個用於粒子的頂點緩存和紋理以及繪製實際的粒子。一個完整的粒子示例的代碼相當冗長。我只是想要給你看看如何操控單個的粒子。

如果想要一個展示在更高層面上使用粒子的完整的示例應用程序,請查閱書本的CD-ROM中的Particle項目(在\Bookode\Chap02\Particle目錄下)。(不用擔心,我會把源代碼和更新後的代碼發出來的。)

 

===============================================================================

 

        現在是時候給出代碼了!不過很悲哀的是,我並不是很懂這個程序的邏輯,看上去挺複雜的樣子。另外我實際上並沒有對此代碼做出本質性的改動。下面是程序運行時的截圖:



        下面是代碼下載的地址:

Particle代碼下載地址

 

===============================================================================

 

 

2.11 Depth Sorting and Z-Buffering (深度排序和Z-緩存)

 

        在你將多邊形網格物體渲染到場景中時,離觀察者遠些的物體需要被近處的物體所阻擋這件事很快就變得明顯了。這叫做深度排序(depth sorting),並且有兩種方法來實現。

        第一個方法叫做畫家的算法(painter’s algorithm)。這個方法將物體分解爲各自的多邊形,並將這些多邊形從後往前進行排序,然後按照這個順序對它們進行繪製(如圖2.23所示)。用這種方法進行繪製保證了一個多邊形總是被繪製在它後面的多邊形的前面。



        第二種深度排序的方法,也是圖形硬件設備使用最多的方法,叫做Z-緩存方法(Z-Buffer method)。這種方法是工作在像素基礎上的,而每個像素具有一個z-值(離觀察者的距離)(實際上,是離觀察者的沿着z軸的距離)

        在每個像素被繪製的時候,渲染器首先檢查是否一個具有更小的z-值的像素已經在那裏了。如果沒有,那麼這個像素就會被繪製;如果有的話,那麼這個像素就會被跳過。你可以在圖2.23中看到這個概念。

        大多數加速的3-D圖形卡具有一個內建的Z-緩存,所以Z-緩存方法是我們選擇的深度排序的方法。在你的應用程序中使用Z-緩存的最簡單的方法就是在你創建你的設備對象的時候對其初始化,並設定顯示方法(presentation methods)。

        你這樣做:首先使用適當的D3DFORMAT 設置來選擇Z-緩存的精度(16位,24位或32位)。然後你會發現用於Z-緩存的的很多設置,但是我考慮的只有D3DFMT_D16(16位)和D3DFMT_D32(32位)這兩種。

        你出於兩個理由來使用不同的精度——考慮存儲空間(storage)和考慮質量(quality)。就存儲空間而言,32位的Z-緩存佔用的空間比16位的Z-緩存要多得多,所以在可能的情況下,努力使用16位的Z-緩存吧。

        就質量而言,對於那些彼此捱得特別近的物體使用16位的Z-緩存的話,有時候會導致錯誤的像素被繪製,因爲精度不夠。改用32位的Z-緩存可以解決精度問題,但是付出的是佔用雙倍內存的代價。不過你不需要擔心:在快速反應(fast-action)的遊戲世界中,速度和優化是更重要的,所以始終使用16位的Z-緩存吧!

回到顯示(presentation)的建立上來,在你的應用程序中你可以將下列兩行加進來以啓用Z-緩存:

d3dp.EnableAutoDepthStencil = TRUE;
d3dp.AutoDepthStencilFormat = D3DFMT_D16;// or D3DFMT_D32

        現在你可以繼續你的初始化例程了。當你準備好利用Z-緩存進行渲染時(it doesn’t kick on automatically,我猜是“它不會自動起作用”的意思),你必須設定適當的渲染狀態:

// g_pD3DDevice = pre-initialized deviceobject
 
// To turn on Z-Buffer, use:
g_pD3DDevice->SetRenderState(D3DRS_ZENABLE,D3DZB_TRUE);
 
// To turn off Z-Buffer, use:
g_pD3DDevice->SetRenderState(D3DRS_ZENABLE,D3DZB_FALSE);


注意

===============================================================================

        雖然讓Z-緩存一直開着似乎是很合乎邏輯的,但是在你不需它的時候將它關閉可以提高一點速度。在你將按鈕等圖形繪製到屏幕上時,你需要完全控制在哪裏繪製什麼東西,所以在你覺得適當的時候就開啓或關閉Z-緩存吧。

===============================================================================

 

        如果你已經嘗試過了新的Z-緩存功能的話,你也許已經發現了一些不正常的事情。例如,屏幕在幾幀之後不更新了,或者太遠的物體被裁剪了。這是因爲你必須在每一幀之前清除Z-緩存,並且需要重調你的投影矩陣以便實現距離的設置。

要在每一幀前清除Z-緩存,將你的IDirect3DDevice9::Clear函數的調用改寫如下:

g_pD3DDevice->Clear(0, NULL,                                    \
                  D3DCLEAR_TARGET| D3DCLEAR_ZBUFFER,            \
                  D3DCOLOR_RGBA(0.0f,0.0f, 0.0f, 0.0f), 1.0f, 0);

        注意前面增加的D3DCLEAR_ZBUFFER 標記。這個標記告訴清除函數,它得將Z-緩存清除爲給定值(第五個參數,這裏是1.0f)。這個值可以從0.0(最小的Z-緩存深度)變動到1.0(最大的Z-緩存深度)。

        使用值1.0告訴清除函數將所有的深度值設爲它們的最大值。如果你想要將Z-緩存清除位最大值的一半,那麼使用0.5。唯一的問題就是最大值代表什麼意思?

        Z-緩存代表從觀察點的距離(實際上是縱向距離),所以當渲染進行時,比最大的觀察距離更遠的物體(還有那些太近的物體)不會被繪製。在你調用(通過D3DX)建立投影矩陣的函數的時候,你設定了最小和最大的觀察距離,如下列代碼所示:

// Set a min distance of 1.0 and a max of1000.0
D3DXMatrixPerspectiveFovLH(&MatrixProj,D3DX_PI/4,              \
                             1.0f,1.0f, 1000.0f);

        最後的兩個值是你想要調節的——分別是最小和最大的距離。對這些值進行試驗,看看哪些值最適合你;只要你記住,最小距離和最大距離之間的距離越大,Z-緩存的質量就越差。一般來說,將最小距離設爲1.0,而將最大距離設爲1,000,2,000或5,000。

 

===============================================================================

 

        本書的隨書光盤關於這個主題給了一個程序,叫做ZBuffer。不過我把它進行了比較大的改動,變成兩個貼着透明紋理的正方形繞着不同的軸旋轉(因此要用到前面所講的alpha test知識)。下面是程序運行時的截圖:



        這裏用到了兩幅部分透明的.png格式圖片。由於圖片扣得比較馬虎,所以看上去效果不太好,大家多多擔待哈!

 

        下面是代碼的下載地址,包含了原始代碼和本人的代碼:

ZBuffer代碼下載地址

 

===============================================================================

 

2.12 Working with Viewports (使用視口)

 

        有時候,你想將圖形渲染到顯示屏的某一部分上,就像渲染到主應用程序窗口的小窗口上。主視口(The main viewport)一般覆蓋了整個顯示屏,但是在你需要的時候,你可以改變視口的尺寸以讓它覆蓋屏幕的一塊子區域(就像渲染到一個雷達、汽車後視鏡或者其他任何“屏中屏”的情況)。

        爲了設置一個視口,你首先用你想使用的新視口的座標和維數來填充一個D3DVIEWPORT9結構體:

typedef struct _D3DVIEWPORT9 {
  DWORD X;      // left X coordinate of viewport
  DWORD Y;      // top Y coordinate of viewport
  DWORD Width;  // Width of viewport
  DWORD Height; // Height of viewport
  float MinZ;   // 0.0
  float MaxZ;   // 1.0
} D3DVIEWPORT9;

        在你用適當的數據設置結構體後,你通過調用函數ID3DDevice9::SetViewport函數來告訴Direct3D去使用它,如下列代碼所示:

// pD3DDevice = pre-initialized 3-D device
 
// Create a viewport
D3DVIEWPORT9 d3dvp = { 0,0, 100, 100, 0.0f,1.0f };
 
// Set the new viewport
pD3DDevice->SetViewport(&d3dvp);

        從這裏開始(在調用SetViewport後),所有的圖形都被渲染進了你定義的視口窗口中了。在你用完這個新的視口之後,你重建舊的視口。爲了獲得舊的視口的設置以便稍後重建它們,你調用ID3DDevice9::GetViewport函數,如下所示:

// pD3DDevice = pre-initialized 3-D device
 
D3DVIEWPORT9 OldViewport;
 
// Get old viewport settings
pD3DDevice->GetDevice(&OldViewport);// get old viewport
 
// ... change viewport settings as needed
 
// Restore old viewport
pD3DDevice->SetDevice(&OldViewport);

===============================================================================

 

        好啦,我們終於翻譯完了這三節內容了!這三節的內容都不難,不過最先講的粒子還是很值得花時間去研究的!

        現在我們已經完成了第2章的一多半內容了!剩下的基本上就是將網格模型了,而這是比較高級的技術了。你也許會問:怎麼這第2章這麼長呢?這要講到啥時候啊?後面的章節豈不是更難?彆着急,你要知道,其實Jim Adams老哥在這第2章裏講了人家Frank D. Luna花了好幾章內容來講述的東西呢!如果不談論HLSL語言的話,基本上這第2章的內容就涵蓋了“龍書”第一版的所有內容了!是不是很酷?!

        不過這裏講得有點急於求成了,所以有些地方看不懂是自然的,大家多多參考“龍書”第二版哈!

        另外,雖然後面的內容我沒有仔細看,但是可以很負責任地告訴你,後面的章節(至少是我會講述到的那些章節)的內容沒有這一章難!這不禁讓我想起了一句名言:開發遊戲是一件很困難的事,但是它會隨着你的開發進度而變得越來越容易!這是Allen Sherrod,《Ultimate Game Programming with DirectX》一書的作者說的話。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章