遊戲開發基礎(十)

 

第十章
ID3DXMesh接口繼承了其父接口ID3DXBaseMesh的大部分功能
ID3DXBaseMesh接口包含有一個頂點緩存(用於存儲網格頂點)和一個索引緩存(決定頂點應以何種組合方式構成網格的三角形單元)
獲取這些接口的指針
HRESULT ID3DXMesh::GetVertexBuffer(LPDIRECT3DVERTEXBUFFER9* ppVB);
HRESULT ID3DXMesh::GetIndexBuffer(LPDIRECT3DINDEXBUFFER9* ppIB);

例;
IDirect3DVertexBuffer9* vb = 0;
Mesh->GetVertexBuffer(&vb);

IDirect3DIndexBuffer9* ib  = 0 ;
Mesh->GetIndexBuffer(&ib);

ID3DXMesh接口僅支持將索引化的三角形單元列表作爲其基本類型

如果想鎖定這些緩存,進行讀寫操作,可使用如下函數,這些方法鎖定的是整個頂點緩存或索引緩存
HRESULT ID3DXMesh::LockVertexBuffer(DWORD Flags,BYTE** ppData);
HRESULT ID3DXMesh::LockIndexBuffer(DWORD Flags,BYTE** ppData);
#Flags 描述如何進行鎖定,
#ppData返回被鎖定的內存的指針的地址

當對被鎖定內存完成操作後,請務必調用相應的解鎖方法
HRESULT ID3DXMesh::UnlockVertexBuffer();
HRESULT ID3DXMesh::UnlockIndexBuffer();

ID3DXMesh接口獲取幾何信息的另外方法
DWORD GetFVF();//返回一個描述頂點格式的DWORD類型值
DWORD GetNumVertices();//返回頂點緩存中的頂點個數
DWORD GetNumBytesPerVertex();//返回每個頂點所佔的字節數
DWORD GetNumFaces();//返回網格中(三角形)面片的個數

一個網格由一個或多個子集組成,一個子集是網格中一組可用相同屬性進行繪製的三角形單元,這裏的屬性指材質,紋理,繪製狀態。

爲了區分不同的子集,需爲每個子集指定一個唯一的非負整數值,該值可爲DWORD類型所能容納的任何非負整數
網格中的每個三角形單元都被賦予了一個屬性ID,該ID指定了該三角形單元所屬的子集

這些三角形單元的屬性ID被存儲在網格的屬性緩存中,該屬性緩存實際上是一個DWORD類型的數組
由於每個面片(face,即三角形)在屬性緩存中都有對應項,所以屬性緩存中元素的個數與網格中面片的個數完全相等。而且屬性緩存中的那些項與在索引緩存中定義的三角形單元時一一對應的,即屬性緩存中的第i項對應於索引緩存中的第i個三角形


訪問屬性緩存,必須首先將其鎖定
DWORD* buffer = 0 ;
Mesh->LockAttributeBuffer(lockingFlags,&buffer);
//read or write to attribute buffer
Mesh->UnlockAttributeBuffer();

ID3DXMesh接口提供方法DrawSubset(DWORD Attribld)用於繪製由參數AttribId指定的子集中的三角形單元
例:
想要繪製子集0中的所有三角形單元
Mesh->DrawSubset(0);

若要繪製整個網格,必須繪製該網格的所有子集,比較方便的做法是,將各子集的屬性ID依次指定爲0,1,2,...,n-1,其中n爲子集的總數,每個子集都有一個對應的材質和紋理數組,這樣索引i就可找到與子集i相關的材質和紋理
for(int i = 0 ; i < numSubsets ; i++ )
{
 Device->SetMaterial(mtrls[i]);
 Device->SetTexture(0,texture[i]);
 Mesh->DrawSubset(0);
}

爲了更高效地繪製一個網格,可對該網格中的頂點和索引進行重組,即網格優化
可藉助該方法來完成優化
HRESULT ID3DXMesh::OptimizeInplace(
      DWORD Flags,
      CONST DWORD *pAdjacencyIn,
      DWORD *pAdjacencyOut,
      DWORD *pFaceRemap,
      LPD3DXBUFFER* ppVertexRemap);

#Flags 優化選項標記,
 D3DXMESHOPT_COMPACT從網格中移除那些無用頂點和索引
 D3DXMESHOPT_ATTSORT依據屬性對各個三角形單元進行排序,並生成一個屬性表,這樣可使DrawSubset獲得更高的繪製效率
 D3DXMESHOPT_VERTEXCACHE提高頂點高速緩存的命中率
 D3DXMESHOPT_STRIPORDER對索引進行重組,使三角形單元條帶儘可能地長
 D3DXMESHOPT_IGNOREVERTS僅對索引進行優化,忽略頂點
D3DXMESHOPT_VERTEXCACHE和D3DXMESHOPT_STRIPORDER不能同時使用
#pAdjacencyIn指向未經優化的網格的鄰接數組的指針
#pAdjacencyOut指向一個DWORD類型數組的指針,該數組被填充了經優化後的網格的鄰接信息,該數組的維數必須爲ID3DXMesh::GetNumFaces()*3,如果不需要該信息,可將參數設爲0
pFaceRemap指定了DWORD類型數組的指針,該數組填充了網格面片的重繪信息,該數組的維數應爲ID3DXMesh::GetNumFaces(),當對一個網格面實施優化後,其面片在索引緩存中可能發生了移動,該面片重繪信息表明了原始面片所被移動到新位置,即pFaceRemap中的第i項保存了表示第i個原始面片被移動到哪裏的面片索引,如果不需要改信息,可設0;

#ppVertexRemap指定ID3DXMesh對象指針的地址,該對象中保存了頂點的重繪信息,該緩存所包含的頂點數應爲ID3DXMesh::GetNumVertices(),當網格面經過優化之後,其頂點在索引緩存中的位置可能發生了變動,頂點重繪信息表明了原始頂點移動到新位置,即ppVertexRemap中的第i項保存了表示第i個原始頂點被移動到哪裏的頂點索引。如果不需要,設爲0

例:
//Get the adjacency info of the non-optimized mesh
DWORD adjacencyInfo[Mesh->GetNumFaces() * 3];
Mesh->GenerateAdjacency(0.0f,adjacencyInfo);

//Array to hold optimized adjacency info
DWORD optimizedAdjacencyInfo[Mesh->GetNumFaces() * 3];

Mesh->OptimizeInplace(
   D3DXMESHOPT_ATTSORT       
   |D3DXMESHOPT_COMPACT
                        |D3DXMESHOPT_VERTEXCACHE,
   adjacencyInfo,
   optimizeAdjacencyInfo,
   0, 
   0);

與該功能類似的另一個方法是Optimize,該方法將輸出調用了該方法的網格對象優化後的版本,但是調用該方法的那個網格對象本身不會發生變化
HRESULT ID3DXMesh::Optimize(
      DWORD Flags,
      CONST DWORD *pAdjacencyIn,
      DWORD * pAdjacencyOut,
      DWORD * pFaceRemap,
      LPD3DXBUFFER* ppVertexRemap,
      LPD3DXMESH*  ppOptMesh);
一個網格對象在優化處理時使用了D3DXMESHOPT_ATTRSORT標記,則構成該網格的三角形面片就會依據其屬性進行排序,這樣屬於特定子集的三角形面片就會被保存在頂點緩存或索引緩存中的一個連續存儲空間內

構成網格的面片和屬性緩存中的內容都依據屬性進行排序,這樣屬於特定子集的面片就會被保存在頂點緩存或索引緩存中的一個連續存儲空間內,現在我們就可以很容易地標出屬於某個子集的三角面片的始末位置
除了可對面片進行排序外,D3DXMESHOPT_ATTRSORT優化選項還將創建一個屬性表,該屬性表是一個D3DXATTRIBUTERANGE類型的結構數組,屬性表中的每一項都對應於網格的一個子集,並指定了該子集中面片的幾何信息被存儲在頂點緩存或索引緩存的哪一個存儲塊中
D3DXATTRIBUTERANGE結構:
typedef struct _D3DXATTRIUTERANGE
{
 DWORD AttribId;//子集ID
 DWORD FaceStart;//一個大小爲FaceStart*3的偏移量,表面了數據該子集的三角形單元在索引緩存中的起始位置。
 DWORD FaceCount;//子集中面片的總數
 DWORD VertexStart;//一個表面了與子集相關的頂點在頂點緩存中的起始位置的偏移量
 DWORD VertexCount;//子集中的頂點總數
}D3DXATTRIBUTERANGE;

屬性表創建後,只有通過一次快速查找,就能找到屬於該子集的全部面片,這樣就使子集的繪製可以高效地完成。沒有屬性表時,繪製某個子集,就只能對整個屬性緩存進行若干次線性查找以確定每個屬於該子集的面的位置

訪問一個網格面的屬性表
HRESULT ID3DXMesh::GetAttributeTable(
        D3DXATTRIBUTERANGE *pAttribTable,
        DWORD *pAttribTableSize);
該方法完成了兩項工作:返回屬性表中的屬性個數和用屬性數據填充D3DXATTRIBUTERANFGE類型的結構數組
要想獲取屬性表中的元素個數,將該方法的第一個參數取0
DWORD numSubsets = 0;
Mesh->GetAttributeTable(0,&numSubsets)s;
得到屬性表的元素個數,就可用屬性數據填充D3DXATTRIBUTERANGE類型的結構數組

D3DXATTRIBUTERANGE table = new D3DXATTRIBUTERANGE[numSubsets];
Mesh->GetAttributeTable(table,&numSubsets);

可直接用方法ID3DXMesh::SetAttributeTable對屬性表進行設置
例:設定屬性表有12個子集
D3DXATTRIBUTERANGE attributeTable[12];
Mesh->SetAttributeTable(attributeTable,12);

某些網格運算(如網格優化),需要知道對任意給定的三角形面片,哪些面片與其鄰接。這些鄰接信息(Adjacency Info)都存儲在網格的鄰接數組(adjacency array)中.

鄰接數組的類型爲DWORD,其每一項都包含了一個標識網格中某個三角形面片的索引。
例:第i項是指由下列頂點構成的三角形面片
 A = i*3
 B = i*3+1
 C = i*3+2
如果鄰接數組中某一項等於ULONG_MAX = 4294967295,則表明網格中某一特定邊沒有鄰接面片,我們也可將該項賦爲-1來表示此種情形,因爲將一個DWORD類型的變量賦值爲-1與賦值爲ULONG_MAX等效,之所以會出現這種溢出情況,是由於DWORD類型與無符號32位整數類型等價

每個面片最大可有3個鄰接三角形
所以,鄰接數組的維數必須爲 ID3DXBaseMesh::GetNumFaces()*3,網格中的每個三角形面片都有3個可能鄰接面片

許多D3DX網格創建函數都能夠輸出鄰接信息,也可用該方法:
HRESULT ID3DXMesh::GenerateAdjacency(
     FLOAT fEpsilon,
     DWORD *pAdjacency);


#fEpsilon 一個很小的正數值,指定了在某種距離度量下,兩個點接近到何種程度方可爲這兩點爲同一點,例,如果兩點間的距離小於該值,我們認爲這兩點爲同一點
#pAdjacency 一個數組指針,存儲了鄰接信息

例:
DWORD adjacencyInfo[Mesh->GetNumFaces() * 3];
Mesh->GenerateAdjacency(0.001f,adjacencyInfo);


生成網格數據的副本:
HRESULT ID3DXMesh::CloneMeshFVF(
    DWORD Options,
    DWORD FVF,
    LPDIRECT3DDEVICE9 pDevice,
    LPD3DXMESH* ppCloneMesh);

#Options 創建某網格副本時的創建標記或標記組合
 D3DXMESH_32BIT 網格將使用32爲索引
 D3DXMESH_MANAGED 網格數據將被存儲於託管內存池中
 D3DXMESH_WRITEONLY 指定網格數據位只讀
 D3DXMESH_DYNAMIC網格緩存將使用動態內存
#FVF 創建克隆網格的靈活頂點格式
#pDevice與所創建的克隆網格關聯的設備指針
#ppCloneMesh輸出所創建的克隆網格

該方法允許目標網格(destination mesh)採用與源網格(source mesh)不同的創建選項和靈活頂點格式
例:一個靈活頂點格式FVF_XYZ的網格,要創建該網格的一個副本,但想將其頂點格式指定爲D3DFVF_XYZ|D3DFVF_NORMAL

//assume mesh and device are valid
ID3DXMesh * clone = 0;
Mesh->CloneMeshFVF(
     Mesh->GetOptions(),
     D3DFVF_XYZ|D3DFVF_NORMAL,   
     Device,
     &clone);


使用D3DXCreate*系列函數創建了一些網格對象,也可用D3DXCreateMeshFVF函數創建一個"空"網格對象,這裏的"空"網格對象是指指定了網格的面片總數和頂點總數,然後由D3DXCreateMeshFVF函數爲頂點緩存,索引緩存和屬性緩存分配大小合適的內存,爲網格緩存分配好內存後,就可手工填入網格數據(即,必須將頂點,索引以及屬性分別寫入頂點緩存,索引緩存和屬性緩存中)
 
該函數創建一個空網格
HRESULT WINAPI D3DXCreateMeshFVF(
   DWORD NumFaces,
   DWORD NumVertices,
   DWORD Options,
   DWORD FVF,
   LPDIRECT3DDEVICE9 pDevice,
   LPD3DXMESH *ppMesh);
#NumFaces 網格將具有的面片總數,必須大於0
#NumVertices 網格將具有的頂點總數,必須大於0
#Options 創建網格時所使用的創建標記
 D3DXMESH_32BIT 網格將使用32爲索引
 D3DXMESH_MANAGED 網格數據將被存儲於託管內存池中
 D3DXMESH_WRITEONLY 指定網格數據位只讀
 D3DXMESH_DYNAMIC網格緩存將使用動態內存
#FVF存儲在該網格中的頂點的靈活頂點格式
#pDevice 與該網格相關的設備指針
#ppMesh所創建的網格對象指針

也可用D3DXCreateMesh函數來創建空網格
HRESULT D3DXCreateMesh(
   DWORD NumFaces,
   DWORD NumVertices,
   DWORD Options,
   CONST LPD3DVERTEXELEMENT9* pDeclaration,
   LPDIRECT3DDEVICE9 pDevice,
   LPD3DXMESH* ppMesh);

該函數參數與D3DXCreateMeshFVF方法類似,只是第4個參數不同,在該函數中,未指定FVF,而是用一個D3DVERTEXELEMENT9類型的結構數組來描述頂點數據的佈局方式
與D3DVERTEXELEMENT9相關的函數
HRESULT D3DXDeclaratorFromFVF(
    DWORD FVF//input
    D3DVERTEXELEMENT9 Declaration[MAX_FVF_DECL_SIZE]//output
    );
MAX_FVF_DECL_SIZE定義如下:
typedef enum{
 MAX_FVF_DECL_SIZE = 18
}MAX_FVF_DECL_SIZE;


例子講解:
初始化全局變量:
在本例中,每個子集都使用了一種不同紋理,這樣紋理數組中的第i個索引就與網格中的第i個子集建立關聯。
將指針Mesh進行了初始化,該指針將指向以後創建的網格對象
ID3DXMesh* Mesh = 0;
//定義該網格將具有的子集個數爲3個
const DWORD NumSubsets = 3;
IDirect3DTexture9* Textures[3] = {0,0,0};//texture for each subset

std::ofstream OutFile;//used to dump mesh data to file

主要工作在Setup函數中完成,首先創建一個空網格
bool Setup()
{
 HRESULT hr = 0;
 //創建一個可存儲12個面片和24個頂點的空網格,描述一個立方體
 hr = D3DXCreateMeshFVF(
    12,
    24,
    D3DXMESH_MANAGED,
    Vertex::FVF,
    Device,
    &Mesh);
 //將該立方體的頂點和索引信息分別寫入該網格對象的頂點緩存和索引緩存中,
 //鎖定頂點緩存和索引緩存,並手工寫入這些數據
 
 Vertex* v = 0;
 Mesh->LockVertexBuffer(0,(void**)&v);

 //fill in the front face vertex data
 v[0] = Vertex(-1.0f,-1.0f,-1.0f,0.0f,0.0f,-1.0f,0.0f,0.0f);
 v[1] = Vertex(-1.0f,-1.0f,-1.0f,0.0f,0.0f,-1.0f,0.0f,0.0f);
 ....
 v[23]= Vertex(-1.0f,-1.0f,-1.0f,0.0f,0.0f,-1.0f,0.0f,0.0f);

 Mesh->UnlockVertexBuffer();
 
 //Define the triangles of the box
 WORD * i = 0;
 Mesh->LockIndexBuffer(0,(void**)&i);
 
 //fill in the front face index data
 i[0] = 0;i[1] = 1 ;i[2] = 2;
 ....
 i[33] = 20;i[34] = 22 ;i[35] = 23;
 Mesh->UnlockIndexBuffer();

 //網格的面片數據寫入相應緩存後,必須指定每個面片所屬的子集
 //這裏,我們指定索引緩存中的前4個面片屬於子集0,接下來4個面片屬於子集1,最後4個  //面數據子集2
 DWORD *attributeBuffer = 0;
 Mesh->LockAttributeBuffer(0,&attributeBuffer);

 for(int a = 0 ;a < 4 ;a++) // trianges 1-4
  attributeBuffer[a] = 0;//subset 0

 for(int b = 4 ;b < 8 ;b++) // trianges 5-8
  attributeBuffer[b] = 1;//subset 1
 
 for(int c = 8 ;c < 12 ;c++) // trianges 9-12
  attributeBuffer[c] = 2;//subset 2
 
 Mesh->UnlockAttributeBuffer(); 

 //至此,已經創建了合法數據的網格,可以繪製網格了,但是我們可以進行優化
 //爲了對網格進行優化,首先需要計算該網格的鄰接信息
 
 std::vector<DWORD> adjacencyBuffer(Mesh->GetNumFaces() * 3);
 Mesh->GenerateAdjacency(0.0f,&adjancencyBuffer[0]);
 //網格優化
 hr = Mesh->OptimizeInplace(
    D3DXMESHSHOPT_ATTRSORT|
    D3DXMESHSHOPT_COMPACT|
    D3DXMESHSHOPT_VERTEXCACHE,
    &adjacencyBuffer[0],
    0,0,0);
 //至此,網格設置已全部完成
   
 return true;  

}


//網格繪製,僅是通過循環遍歷每個子集,爲每個子集設置相關紋理並進行繪製,當我們按照0,1,2...n-1的順序指定各子集時,子集遍歷很容易做到。


bool Display(float timeDelta)
{
 if(Device)
 {
  D3DXMATRIX xRot;
  D3DXMatrixRotationX(&xRot,D3DX_PI * 0.2f);

  static float y = 0.0f;
  
  D3DXMATRIX yRot ;
  D3DXMatrixRotationy(&yRot,y);
  
  y += timeDelta;

  if (y >= 6.28f)
   y = 0.0f;

  D3DXMATRIX World = xRot * yRot;
  Device->SetTransform(D3DTS_WORLD,&World);

  //Render
  Device->Clear(0,0,D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER,
    0x00000000,1.0f,0);

  Device->BeginScene();
   
  for(int i = 0 ;i<NumSubsets ;i++)
  {
   Device->SetTexture(0,Textures[i]);
   Mesh->DrawSubset(i);
  }
  
  Device->EndScene();
  Device->Present(0,0,0,0);
 }

 return true;
}

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