游戏开发基础(十)

 

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

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