第十章
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;
}