网格模型

这个章节我们主要对ID3DXMesh接口的数据和方法进行复习。

首先查看SDK可知ID3DXMesh接口继承自ID3DXBaseMesh接口的,其他一些接口如ID3DXPMash也是继承BashMesh接口的。所以我们学习ID3DXMesh接口也是适用于其他Mesh类型的。但是值得注意的是:基于D3DX的都是高层接口,使用它并非最好的选择。可能的话还是使用D3D的基层接口实现我们的渲染工作。

Mesh接口包含一个存储网格顶点缓存和一个定义这些顶点的索引缓存,我们可以通过一些方法来获得其中的缓存指针。

HRESULT GetVertexBuffer( LPDIRECT3DVERTEXBUFFER9* ppVB );
HRESULT GetIndexBuffer( LPDIRECT3DINDEXBUFFER9* ppIB );

我们定义一个该类型的变量,再传其地址到函数中,我们将会获得一个顶点缓存和索引缓存所在的地址。 当我们获得对应的缓存地址后,为方便在其中进行相应的读写操作,我们需要保证该区域不受其他操作而发生改变,它应当仅受我们的控制,所以,此时我们将该区域锁定,保证不被其他程序访问。 HRESULT LockVertexBuffer( DWORD Flags, BYTE** ppData ); HRESULT LockIndexBuffer( DWORD Flags, BYTE** ppData ); 其中第一个参数依旧是锁定时使用的锁定方法,第二个参数是函数返回的指向锁定内存指针的地址。

当我们对该内存进行写入任务完毕后,记得UnlockVertexBuffer/UnlockIndexBuffer解锁释放。

在这里我需要插一句,在游戏中,我们经常可以见到换装系统,此时其实上可以是我们更换了一组顶点或者纹理。我们此时就不能一个模型仅仅一个纹理了,我们尽量将衣服和人物皮肤的纹理分开,这样我们在换装时就无需将人物皮肤纹理也更新一次了,这就涉及到了一套模型对应多个纹理,又假设我们的人物穿着的是金属铠甲,那么它的材质反光度必然和人物皮肤是不能相同的,在材质,渲染状态上,一个模型也是可以对应多种的,所以,我们的mesh网格是拥有subset(子集)的。

我们可以假设有一辆车,那么我们可以根据纹理,材质,渲染模式的不同,将整个车给分割成多个子集,玻璃是反光的透明的,做为子集0,车身是铁制的,做为子集1,轮胎是橡胶的,粗糙的,做为子集2。。实际上我们完全可以做一个链表来存放各个子集,再做一个对应的链表来存放对应ID序号子集的各种渲染状态信息。

而Mesh本身支持了对每个子集的控制,Mash在属性缓存中用一个个DWORD数组存放着它的一个个子集。我们可以象对付顶点缓存和索引缓存一样对付他们,LockAttributeBuffer锁定后进行读写,在使用完毕后再Unlock解锁。

当一些条件都准备好了之后,我们可以对每个子集进行绘制。当然用个循环来实现每个Mesh子集的轮流绘制

for( int i = 0; i < numSubsets; ++i )
{
   mpDevice->SetMeterial( Mtrls[i] );        // 每个子集分别设置材质
   mpDevice->SetTexture( 0, Textures[i] );   // 每个子集分别设置纹理
   Mesh->DraSubset(i);                       // 每个子集进行分别的绘制
}

但是我们需要明白Mesh中的顶点和索引都是非常之多的,我们可以对其进行一些优化,ID3DXMesh接口给我们提供了现有的方法:OptimizeInplace,在SDK中它的声明如下:

HRESULT OptimizeInplace(
  DWORD Flags,
  CONST DWORD * pAdjacencyIn,
  DWORD * pAdjacencyOut,
  DWORD * pFaceRemap,
  LPD3DXBUFFER * ppVertexRemap
);
  • 其中第一个参数是进行优化的方法,其可选项建议自行从SDK中查找。这里仅强调一个选项D3DXMESHOPT_ATTRSORT,它是将mesh中的几何信息按照属性进行排序,简单来解释就是,按照顶点和索引将子集中的每个信息顺序的排列起来,在我们渲染时则更容易获得连续的排列,会使我们DrawSubset的速度大大加快。
  • 第二个参数是源mesh数组,也就是我们需要优化的数组。
  • 第三个参数是目标mesh数组,也就是优化后,我们需要得到的数组。
  • 第四个参数是面重影信息相关数组,建议设为0不使用该信息。
  • 第五个参数是面重影信息相关数组指针,建议设置为0不使用。