游戏开发基础(十一)

 

第十一章
ID3DXBuffer接口是一种泛型数据结构,该接口为D3DX库所使用,并可将数据存储在一个连续的内存块中
该接口只有两个方法
LPVOID GetBufferPointer();返回指向缓存中数据起始位置的指针
DWORD GetBufferSize();返回缓存的大小,单位为字节
为了保持该接口的通用性,该接口使用了void类型的指针。所以必须由我们来实现被存储的数据类型
例:
D3DXLoadMeshFromX函数用ID3DXBuffer类型的指针返回一个网格对象的邻接信息
由于邻接信息被存储在一个DWORD类型的数组中,所以当我们想要使用该接口的邻接信息时,我们必须对该缓存进行强制类型转换
例:
DWORD *info = (DWORD*)adjacencyInfo->GetBufferPointer();
D3DXMATERIAL* mtrls = (D3DXMATRIAL*)mtrlBuffer->GetBufferPointer();

由于ID3DXBuffer 是一个COM对象,该接口使用完毕之后必须将其释放,以防内存泄露
adjacencyInfo->Release();
mtrlBuffer->Release();

我们可用如下函数创建一个空的ID3DXBuffer对象
HRESULT D3DXCreateBuffer(DWORD NumBytes,LPD3DXBUFFER* ppBuffer);

创建一个能容纳4个数据的缓存
ID3DXBuffer* buffer = 0;
D3DXCreateBuffer(4*sizeof(int),&buffer);

.x格式是DirectX定义的格式,该格式得到了D3DX库的有力支持,D3DX库提供了对XFile格式的文件进行加载和保存的函数

该法创建一个ID3DXMesh对象并将XFile中的几何数据加载到该对象中

HRESULT D3DLoadMeshFromX(LPCSTR pFileName,
   DWORD Options,
   LPDIRECT3DDEVICE9 pDevice,
   LPD3DXBUFFER* ppAdjacency,
   LPD3DXBUFFER* ppMaterials,
   LPD3DXBUFFER* ppEffectInstances,
   PDWORD pNumMaterials,
   LPD3DXMESH *ppMesh);
#pFileName XFile文件名
#Options 创建网格时所使用的创建标记
 *D3DXMESH_32BIT 网格将使用32位索引
 *D3DXMESH_MANAGED 网格数据将被存储于托管内存池中
 *D3DXMESH_WRITEONLY 指定网格数据位只读
 *D3DXMESH_DYNAMIC 网格缓存将使用动态内存
#pDevice 与该网格对象相关的设备指针
#ppAdjacency返回一个ID3DXBuffer对象,该对象包含了一个描述了该网格对象的邻接信息的DWORD类型数组
#ppMaterials返回一个ID3DXBuffer对象,该对象包含了一个存储了该网格的材质数据的D3DXMATERIAL类型的结构数组
#ppEffectInstances返回一个ID3DXBuffer对象,该对象包含了一个D3DXEFFECTIN-STANCE结构
#pNumMaterials返回网格中的材质数目(即由ppMaterials输出的D3DXMATERIAL数组中的元素个数)
#ppMesh返回所创建的并已填充了XFile几何数据的ID3DXMesh对象

D3DXLoadMeshFromX函数中的第7个参数返回了该网格对象所含的材质数目,第5个参数返回了一个存储了材质数据的D3DXMATRIAL类型的结构数组
typedef struct D3DXMATERIAL{
 D3DMATERIAL9 MatD3D;
 LPSTR pTextureFileName;//指定了网格相关纹理的文件名
}D3DXMATERIAL;

XFile文件中并未存储纹理数据,只包含了纹理图像文件名,该文件名是对包含了实际纹理数据的纹理图像的引用,当用D3DXLoadMeshFromX函数加载了一个XFile文件后,必须根据指定的纹理文件名加载纹理数据。

D3DXLoadMeshFromX函数载入XFile数据后,返回的D3DXMATERIAL结构数组中的第i项与第i个子集想对应,所有我们将各子集按照0,1,2,...n-1的顺序进行标记,其中n是子集和材质的总数,这样可用一个简单的循环对全部子集进行遍历和绘制,从而完成整个网格的绘制


例:
ID3DXMesh* Mesh = 0;
std::vector<D3DMATERIAL9> Mtrls(0);
std::vector<IDirect3DTexture9*> Textures(0);

Mesh对象存储从XFile中加载的网格数据


Setup函数,记载XFile文件
bool Setup()
{
 HRESULT hr = 0;
 ID3DXBuffer* adjBuffer = 0;
 ID3DXBuffer* mtrlBuffer = 0;
 DWORD        numMtrls = 0;

 hr = D3DXLoadMeshFromX("bigship1.x",
    D3DXMESH_MANAGED,
    Device,
    &adjBuffer,
    &mtrlBuffer,
    0,
    &numMtrls,
    &Mesh);
 if(FAILED(hr))
 {
  ::MessageBox(0,"D3DXLoadMeshFromX()-FAILED",0,0);
  return false;
 }

 //加载XFile数据后,必须遍历D3DXMATERIAL数组中的元素,并加载该网格所引用的纹理数据

 if(mtrlBuffer != 0 && numMtrls != 0)
 {
  D3DXMATERIAL* mtrls = (D3DXMATERIAL*) mtrlBuffer->GetBufferPointer();
  for(int i = 0;i < numMtrls;i++)
  {
   //the MatD3D property does't have an ambient value set
   // when its loaded ,so set it now
   mtrls[i].MatD3D.Ambient = mtrls[i].MatD3D.Diffuse;

   //save the ith material
   Mtrls.push_back(mtrls[i].MatD3D);

   //check if the ith material has an associative texture
   if (mtrls[i].pTextureFileName != 0)
   {
    //yes load the texture for the ith subset
    IDirect3DTexture9* tex = 0;
    D3DXCreateTextureFromFile(Device,mtrls[i].pTextureFileName,&tex);
    //save the loaded texture
    Textures.push_back(tex);    
   }
   else
   {
    //no texture for the ith subset
    Textures.push_back(0);     
   }
  }
 }
 
 d3d::Release<ID3DXBuffer*>(mtrlBuffer);
 
 return true;
}

在Display函数中,在每一帧图像中对网格做了略微的旋转,以使其呈现旋转的动画效果,由于网格的个子集已按0,1,2.。。n-1的顺序标记(n为子集总数),整个网格的绘制通过一个简单的循环即可完成
bool Display(float timeDelta)
{
 if (Device)
 {

  static float y = 0.0f;
  D3DXMATRIX yRot;
  D3DXMatrixRotationY(&yRot,y);
 
  y += timeDelta;
  if (y >= 6.28f)
   y = 0.0f;
  D3DXMATRIX World = yRot;
  Device->SetTransform(D3DTS_WORLD,&World);

  Device->Clear(0,0,D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER,0xffffffff,1.0f,0);
  
  Device->BeginScene();
  for (int i = 0; i < Mtrls.size();i++)
  {
   Device->SetMaterial(&Mtrls[i]);
   Device->SetTexture(0,Textures[i]);
   Mesh->DrawSubset(i);
  }

  Device->EndScene();
  Device->Present(0,0,0,0);
 }

 return true;
}

我们可用如下方法来产生任意网格的顶点法向量

HRESULT D3DXComputeNormals(LPD3DXBASEMESH pMesh,//Mesh to compute the normals of
   CONST DWORD *pAdjacency //Input adjacency info
   );

该函数通过法向量平均的方法生成顶点的法向量,如果提供了邻接信息,重叠的顶点就会被剔除,如果没有提供邻接信息,则重叠顶点的法向量由该顶点所依附的各面在该点的局部法向量取平均而得到。很重要的一点是,传入的参数pMesh的顶点格式中必须包含标记D3DFVF_NORMAL

如果XFile文库不含顶点法线数据,则由函数D3DXLoadMeshFromX所创建的ID3DXMesh网格对象在其顶点格式中将不含D3DFVF_NORMAL标记,所以在使用函数D3DXComputeNormals之前,必须克隆该网格,并为其指定一个包含了D3DFVF_NORMAL标记的顶点格式
例:

 if(!(pMesh->GetFVF() & D3DFVF_NORMAL))
 {
  ID3DXMesh* pTempMesh = 0;
  pMesh->CloneMeshFVF(
        D3DXMESH_MANAGED,
        pMesh->GetFVF() | D3DFVF_NORMAL,// add it here
        Device,
               &pTempMesh);
  //compute the normals
  D3DXComputeNormals(pTempMesh,0);
  pMesh->Release();//get rid of the old mesh
  pMesh = pTempMesh;// save the new mesh with normals
 }

渐进网格用ID3DXPMesh接口来表示,它允许运用一系列的边折叠变换(Edge Collapse Transformations,ECT)对网格进行简化,每次ECT都移除一个顶点以及一个或两个面,由于每次ECT都是可逆的(其逆运算称为顶点分裂),可对简化过程进行逆转,从而将网格精确恢复到初始状态,当然,意味着我们无法获得比原始网格更丰富的细节,只能对网格进行简化及其逆运算。

渐进网格的思路与纹理中的多级渐进纹理类似

HRESULT D3DXGeneratePMesh(
   LPD3DXMESH pMesh,
   CONST DWORD *pAdjacency, 
   CONST D3DXATTRIBUTEWIGHTS* pVertexAttributeWeights,
   CONST FLOAT * pVertexWeights,
   DWORD MinValue,
   DWORD Options,
   LPD3DXPMESH *ppPMesh);
#pMesh 该输入变量包含了网格数据,渐进网格将根据此网格产生
#pAdjacency 指向包含了pMesh的邻接信息的DWORD类型的数组指针
#pVertexAttributeWeights 指向D3DXATTRIBUTEWEIGHTS 类型的结构数组指针,该数组的维数为pMesh->GetNumVertices(),该数组的第i项对应于pMesh中的第i个顶点,并指定了相应顶点的属性权值,顶点属性权值用于决定在简化过程中顶点被移除的概率,可以为参数传入NULL,则每个顶点将被赋予默认的属性权值。
#pVertexWeights指向一个float类型数组的指针,该数组的维数为pMesh->GetNumVertices(),该数组的第i项对应于pMesh中的第i个顶点,并指定了相应顶点的权值,一个顶点权值越高,在简化过程中被移除的概率就越小,可将该参数赋为NULL,这样每个顶点就会被赋予默认的顶点权值1.0
#MinValue 网格中的顶点数或面片数(有下一个参数Options决定)可被简化到的下限,该值只是一种期望值,实际还有依赖顶点权值或属性权值,所以简化结构可能与该值不一致
Options该参数实际上是D3DXMESHSIMP枚举类型的一个成员
*D3DXMESHSIMP_VERTEX 指定了前面的参数MinValue是指顶点数
*D3DXMESHSIMP_FACE指定了前面参数MinValue是指面片数
ppPMesh返回生成的渐进网格

顶点权值结构允许为顶点的每个可能的分量指定一个权值,如果某个分量的权值被赋为0.0,则表明该分量无权值,顶点分量的权值越高,在简化过程中该顶点被移除的概率越小
顶点权值结构:
typedef struct D3DXATTRIBUTEWEIGHTS{
 FLAOT Position;
 FLOAT Boundary;
 FLOAT Normal;
 FLOAT Diffuse;
 FLOAT Specular;
 FLOAT Texcoord[8];
 FLOAT Tangent;
 FLOAT Binormal;
}D3DXATTRIBUTEWEIGHTS,*LPD3DXATTRIBUTEWEIGHTS;

D3DXATTRIBUTEWEIGHTS AttributeWeights;
AttributeWeights.Position = 1.0;
AttributeWeights.Boundary = 1.0 ;
AttributeWeights.Normal   = 1.0;
AttributeWeights.Diffuse = 0.0;
AttributeWeights.Specular = 0.0;
AttributeWeights.Tex[8] = {0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0};
建议使用默认值


ID3DXPMesh接口:
DWORD GetMaxFaces(VOID);返回渐进网格面片数可被指定的上限
DWORD GetMaxVertices(VOID);返回渐进网格顶点数可被指定的上限
DWORD GetMinFaces(VOID);返回渐进网格面片数可被指定的下限
DWORD GetMinVertices(VOID);返回渐进网格顶点数可被指定的下限
HRESULT SetNumFaces(DWORD Faces);允许我们设置网格面片数可被简化或细化到的个数
例: 网格目前有50个面片,将其简化到30个
pMesh -> SetNumFaces(30);
调整后,网格面片数可能与期望的面片数不一致,如果Faces小于GetMinFaces(),将取GetMinFaces(),类似地,Faces大于GetMaxFaces,将取GetMaxFaces();
HRESULT SetNumVertices(DWORD Vertices);该方法允许我们设置网格的顶点数可被简化或细化到的个数
例 网格当前有20个顶点,将其增加细节,将顶点数增加到40个
pMesh->SetNumVertices(40)
调整后,网格顶点数可能与期望的顶点数不一致,规则类似SetNumFaces规则

HRESULT TrimByFaces(
     DWORD NewFacesMin,
     DWORD NewFacesMax,
     DWORD *rgiFaceRemap,//Face remap info
     DWORD *rgiVertRemap//Vertex remap info
     );
该方法允许重新设定面片数的最小值(NewFacesMin)和最大值(NewFacesMax),区间必须在[GetMinFaces(),GetMaxFaces()]内,该函数同时也返回了面片和顶点的重绘信息

HRESULT TrimByVertices(
   DWORD NewVerticesMin,
   DWORD NewVerticesMax, 
   DWORD *rgiFaceRemap,
   DWORD *rgiVertRemap);
该方法允许重新设定顶点数的最小值(NewVerticesMin)和最大值(NewVerticesMax),区间必须在[GetMinVertices(),GetMaxVertices()]内,该函数同时也返回了面片和顶点的重绘信息
 
SetNumFaces和SetNumVertices方法允许调整一个网格的LOD

下面该例是创建和绘制的网格是由ID3DXPMesh接口表示的渐进网格

ID3DXMesh* SourceMesh = 0;
ID3DXPMesh* pMesh = 0;
vector<D3DMATERIAL9> Mtrls(0);
vector<IDirect3DTexture9*> Textures(0);

生成渐进网格,必须传入一个"源"网格,该网格包含了所创建渐进网格将包含的数据,所以,首先将XFile数据加载到一个ID3DXMesh对象SourceMesh中,然后再生成渐进网格
bool Setup()
{
 HRESULT hr = 0;
 //Load the XFile data
 //Extracting materials and textures snipped
 
 // 生成渐进网格
 //Generate the progressive mesh
 
 hr = D3DXGeneratePMesh(
    SourceMesh,
    (DWORD*)adjBuffer->GetBufferPointer(),
    0,
    0,
    1,
    D3DXMESHSIMP_FACE,
    &PMesh);

 d3d::Release<ID3DXMesh*>(SourceMesh);
 d3d::Release<ID3DXBuffer*>(adjBuffer);

 if(FAILED(hr))
 {
  ::MessageBox(0,"D3DXGeneratePMesh()-FAILED",0,0);
  return false;
 }
}

如果想将网格面简化为只有一个面片,则由于顶点/属性权值的存在,这将不可能实现
但是,将期望的面片数指定为1可将网格简化至其所能达到的最低分辨率

由于首先以最大分辩率将该网格绘制出来,这样设置:
DWORD maxFaces = PMesh->GetMaxFaces();
PMesh->SetNumFaces(maxFaces);
bool Display(float timeDelta)
{
 if (Device)
 {
  // Update :Mesh resolution
  
  //Get the current number of faces the pmesh has
  int numFaces = PMesh->GetNumFaces();
  
  // add a face ,note the SetNumFaces() will automatically
  //clamp the specified value if it goes out of bounds
  if (::GetAsyncKeyState('A') & 0x8000f)
  {
   PMesh->SetNumFaces(numFaces + 1);
   if (PMesh ->GetNumFaces() == numFaces)
    PMesh->SetNumFaces(numFaces + 2);
  }

  // Remove a face ,note the SetNumFaces() will automatically
  // clamp the specified value if it goes out of bounds
  if (::GetAsyncKeyState('S') & 0x8000f)
   PMesh->SetNumFaces(numFaces -1);

  Device->Clear(0,0,D3DCLREAR_TARGET | D3DCLEAR_ZBUFFER,0xffffffff,1.0f,0);
  Device->BeginScene();
  
  for (int i = 0 ; i < Mtrls.size(); i++ )
  { 
   Device->SetMaterial(&Mtrls[i]);
   Device->SetTexture(0,Textures[i]);
   PMesh->DrawSubset(i);

   //draw wireframe outline
   Device->SetMaterial(&d3d::YELLOW_MTRL);
   Device->SetRenderState(D3DRS_FILLMODE,D3DFILL_WIREFRAME);
   PMesh->DrawSubset(i);
   Device->SetRenderState(D3DRS_FILLMODE,D3DFILL_SOLID);
  }
  
  Device->EndScene();
  Device->Present(0,0,0,0);
 }
 
 return true;
}

// 增加面片时,有时必须增加两个面片以保证ECT的逆变换能够顺利进行

 

计算一个网格的外界体,两种常见的外接体是外界球和外接体,其他的外接体还有圆柱体,椭球体,棱形体以及胶囊状容器

外接球或外接体常用于加速可见性检测和碰撞,如,一个网格的外接体或外接球不可见,就可认为该网格不可见。
检测外接体的可见性要比检测网格中每个面片的可见性的代价低得多,例,场景中有一个发射物,要确定该发射物是否会击中场景中的某一物体,由于物体是由三角形面片构成的,所以需要遍历每个物体的每个面片,并检测发射物(数学模型为射线)是否会击中某一面片,该方法需要进行大量的射线/三角形相交测试,场景中每个物体的每个三角形面片都需要进行一次,一种更高效的途径是计算出每个网格的(物体)的外接体,然后对每个物体进行射线/外接体相交测试,如果射线与某一物体的外接体相交,就认为该物体被击中,这是一种很好的近似,如果希望提高检测精度,可借助外接体快速排除那些显然不能被击中的物体,然后再对那些极有可能击中的物体使用更精确的方法来检测

D3DX库提供一些函数用来计算一个网格的外接球和外接体,这些函数接收一个数组,然后依据这些顶点计算出外接体或外接体。

HRESULT WINAPI D3DXComputeBoundingSphere(
     const D3DXVECTOR3 *pFirstPosition,
     DWORD NumVertices,
     DWORD dwStride,
     D3DXVECTOR3 *pCenter,
     FLOAT* pRadius);
#pFirstPosition指向顶点数组(该数组的每个元素都描述了对应顶点)中第一个顶点的位置向量的指针
#NumVertices该顶点数组中顶点的个数
#dwStride每个顶点的大小,单位为字节。该值很重要,因为一种顶点结构可能包含了许多该函数所不需要的附件信息
如法向量和纹理座标等,这样该函数就需要知道应该跳过多少字节才能找到下一个顶点的位置
#pCenter返回外接球的球心位置
#pRadius返回外接球的半径
HRESULT WINAPI D3DXComputeBoundingBox(
     const D3DXVECTOR3 *pFirstPosition,
     DWORD NumVertices,
     DWORD dwStride,
     D3DXVECTOR3 *pMin,//返回外接体的最小点
     D3DXVECTOR3 *pMax//返回外接体的最大点
     );

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