【3D技术宅公社】XR数字艺术论坛  XR技术讨论 XR互动电影 定格动画

 找回密码
 立即注册

QQ登录

只需一步,快速开始

调查问卷
论坛即将给大家带来全新的技术服务,面向三围图形学、游戏、动画的全新服务论坛升级为UTF8版本后,中文用户名和用户密码中有中文的都无法登陆,请发邮件到324007255(at)QQ.com联系手动修改密码

3D技术论坛将以计算机图形学为核心,面向教育 推出国内的三维教育引擎该项目在持续研发当中,感谢大家的关注。

查看: 3054|回复: 6

在DirectX9.0中使用Mesh(全)

[复制链接]
发表于 2009-4-12 10:23:31 | 显示全部楼层 |阅读模式
本文译自《Introduction to 3D Game Programming with DirectX 9.0》第十章“Mesh Part One”,敬请斧正。
 
在D3DX中,有很多函数都使用了ID3DXMesh接口,如D3DXCreate*之类。ID3DXMesh接口的主要功能继承自ID3DXBaseMesh父类接口,还有其他的Mesh接口也是从ID3DXBaseMesh接口继承的,如ID3DXPMesh接口,这个接口用于Progressive Mesh,把它翻译成“渐进Mesh”,不知是否合适。
本节要达到的目标:
l          学习ID3DXMesh对象的内部数据组织
l          学习创建一个ID3DXMesh对象
l          学习优化ID3DXMesh
l          学习渲染ID3DXMesh
10.1.        几何结构信息接口ID3DXBaseMesh具有顶点缓冲和顶点索引缓冲,分别用于存储Mesh的顶点数据和顶点的索引数据,二者结合在一起才能够渲染出组成Mesh的三角形。使用下面的两个方法可得到指向两个缓冲区的指针:
HRESULT ID3DXMesh::GetVertexBuffer(LPDIRECT3DVERTEXBUFFER9* ppVB);
HRESULT ID3DXMesh::GetIndexBuffer(LPDIRECT3DINDEXBUFFER9* ppIB);

下面是一个关于上述两个方法用法的例子:
IDirect3DVertexBuffer9* pVB=NULL;
Mesh->GetVertexBuffer(&pVB);
 
IDirect3DIndexBuffer9* pIB=NULL;
Mesh->GetIndexBuffer(&pIB);

另外,如果想修改顶点缓冲和顶点索引缓冲,需要先使用下面的两个方法加锁:
HRESULT ID3DXMesh:ockVertexBuffer(DWORD Flags,LPVOID* ppData);
HRESULT ID3DXMesh:ockIndexBuffer(DWORD Flags,LPVOID* ppData);

参数Flags用于说明加锁的方式,参数ppData返回被锁定的内存的地址。记住,如果加锁成功还需要调用与之配对的解锁函数:
HRESULT ID3DXMesh::UnlockVertexBuffer();
HRESULT ID3DXMesh::UnlockIndexBuffer();

下面是另外一些与Mesh的几何结构有关的ID3DXMesh接口的方法:
l          DWORD GetFVF(); --返回顶点的格式
l          DWORD GetNumVertices(); --返回顶点缓冲中的顶点数
l          DWORD GetNumBytesPerVertex(); --返回一个顶点所占的字节数
l          DWORD GetNumFaces(); --返回Mesh的面数,也就是三角形数
10.2.        子集和属性缓冲一个Mesh由数个子集组成。子集是Mesh中的一组使用相同属性渲染的三角形。这里的属性指的是材质、纹理、渲染状态。每一个子集用一个唯一的非负整数表示其ID,如0,1,2,3等。
Mesh中的每一个三角形都与一个属性ID相关联,表示该三角形属于该子集。例如,在一个表示房子的Mesh中,组成地板的三角形具有属性ID 0,这就表示这些三角形属于子集0;同样的,组成墙的三角形的属性ID为1,他们属于子集1。


三角形的属性ID存储在Mesh的属性缓冲中,这是一个DWORD数组。因为每个面对应属性缓冲中的一项,所以属性缓冲中的项目数等于Mesh中的面的个数。属性缓冲中的项目和索引缓冲定义的三角形一一对应;也就是说,属性缓冲的第I项和索引缓冲中定义的第I个三角形相对应。三角形I有下面三个索引缓冲中的索引项定义:
A=I*3
B=I*3 + 1
C=I*3 + 2


可以使用下面的方法访问属性缓冲:
DWORD* buffer=NULL;
Mesh->LockAttributeBuffer(lockingFlags,&buffer);
// do something...
Mesh->UnlockAttributeBuffer();

10.3.        渲染接口ID3DXMesh提供了DrawSubset(DWORD AttribID)方法渲染参数AttribID指示的子集中的各个三角形。例如,如果渲染子集0中的所有三角形,可以使用如下方法:
Mesh->DrawSubset(0);

如果要渲染整个Mesh,需要分别渲染Mesh的各个子集。因为子集序列与Mesh使用的材质、纹理的序列相对应,即子集I和材质、纹理数组的第I项对应,所以可以使用一个简单的循环渲染Mesh:
for (int i=0;i<numSubsets;i++)
{
    Device->SetMaterial(mtrls);
    Device->SetTexture(0,textures);
    Mesh->DrawSubset(i);
}

10.4.        优化为了更加有效的渲染Mesh,可以重新组织其中的顶点和索引,也就是优化Mesh。可以使用如下方法进行优化:
HRESULT ID3DXMesh::OptimizeInplace(
    DWORD Flags,
    CONST DWORD *pAdjacencyIn,
    DWORD *pAdjacencyOut,
    DWORD *pFaceRemap,
    LPD3DXBUFFER *ppVertexRemap
);

l          Flags –优化选项,告诉该方法执行什么类型的优化。可以区下面的一个或几个值:
n          D3DXMESHOPT_COMPACT –删除没有用的顶点和索引项
n          D3DXMESHOPT_ATTRSORT –根据属性给三角形排序并调整属性表,这将使DrawSubset方法更有效的执行
n          D3DXMESHOPT_VERTEXCACHE –增加顶点缓冲的命中率
n          D3DXMESHOPT_STRIPREORDER –重组顶点索引使三角形条带(Triangle Strip)尽量长
n          D3DXMESHOPT_IGNOREVERTS –只优化索引,忽略顶点
l          pAdijacencyIn –没有优化的Mesh的邻接数组
l          pAdjacencyOut –输出优化的Mesh的邻接信息的数组。这个DWORD数组必须有ID3DXMesh::GetNumFaces() * 3个元素。如果不需要该信息,可以传递NULL。
l          pFaceRemap –一个DWORD数组,用于接收面重影射信息。这个数组应不小于ID3DXMesh::GetNumFaces()。当Mesh被优化时,由索引缓冲定义的面可能被移动,也就是说,如果pFaceRemap的第I项表示第I个原始面被移到的面索引值。如果不需要该信息,可以使用NULL。
l          ppVertexRemap –指向ID3DXBuffer的指针的地址,返回顶点重影射信息。该缓冲区应包含ID3DXMesh::GetNumVertices()个顶点。当Mesh被优化时,顶点可能被移动,该重影射信息用于说明原来的顶点被移动到新位置,也就是说,ppVertexRemap的第I项指示原来的第I个顶点的新位置。如果不需要该信息,可以使用NULL。
// 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_ATTRSORT |
    D3DXMESHOPT_COMPACT |
    D3DXMESHOPT_VERTEXCACHE,
    adjacencyInfo,
    optimizedAdjacencyInfo,
    0,
    0);

另一个相似的方法是Optimize(),它输出一个优化的Mesh,而不是在原来Mesh的基础上进行优化:
HRESULT ID3DXMesh::Optimize(
    DWORD Flags,
    CONST DWORD *pAdjacencyIn,
    DWORD *pAdjacencyOut,
    DWORD *pFaceRemap,
    LPD3DXBUFFER *ppVertexRemap,
    LPD3DXMESH *ppOptMesh
);

10.5.        属性表如果一个Mesh使用D3DXMESHOPT_ATTRSORT标志进行优化,Mesh的结构信息将按属性进行排序,这样各个子集的顶点/顶点索引将组成连续的块。


除了进行几何信息的排序外,D3DXMESHOPT_ATTRSORT优化选项还将创建一个属性表。该表是D3DXATTRIBUTERANGE结构的一个数组,其中的每一项对应Mesh的一个子集并指示顶点/顶点索引的一个连续块,这个子集的几何信息就包含在这个块里。结构D3DXATTRIBUTERANGE的定义如下:
typedef struct _D3DXATTRIBUTERANGE
{
    DWORD AttribId;
    DWORD FaceStart;
    DWORD FaceCount;
    DWORD VertexStart;
    DWORD VertexCount;
} D3DXATTRIBUTERANGE;

l          AttribId –子集的ID
l          FaceStart –该子集的面的起始值,FaceStart*3就是起始三角形在索引缓冲的索引偏移
l          FaceCount –子集中的面数,也就是三角形数
l          VertexStart –该子集的起始顶点在顶点缓冲中的偏移
l          VertexCount –该子集包含的定点数
很容易看出该结构与上面图中表示的信息之间的联系。上图中Mesh的属性表中的每一项对应一个子集。
建立属性表后,渲染一个子集就很容易了,仅仅查一下属性表找出该自己的几何信息。如果没有属性表,每渲染一个子集就需要对属性缓冲区进行一次线性搜索来找出该子集包含的几何信息。
可以使用如下方法访问Mesh的属性表:
HRESULT ID3DXMesh::GetAttributeTable(
    D3DXATTRIBUTERANGE *pAttribTable,
    DWORD *pAttribTableSize
);

该方法可以完成两个功能:可以返回属性表的属性数,也可以将返回完整的属性表。
要得到属性表的元素个数,可以给第一个参数传NULL,如:
DWORD numSubsets = 0;
Mesh->GetAttributeTable(0, &numSubsets);

然后,就可以取得属性表了:
D3DXATTRIBUTERANGE table = new D3DXATTRIBUTERANGE [numSubsets];
Mesh->GetAttributeTable( table, &numSubsets );

还可以使用ID3DXMesh::SetAttributeTable方法直接修改属性表。
D3DXATTRIBUTERANGE attributeTable[12];
// ...fill attributeTable array with data
Mesh->SetAttributeTable( attributeTable, 12);

[此贴子已经被Admin于2009-6-3 10:24:48编辑过]
 楼主| 发表于 2009-4-12 10:23:53 | 显示全部楼层
10.6.        邻接信息对于Mesh的某些操作,如优化,需要知道三角形间的邻接信息,而Mesh的邻接数组就存储这样的信息。
邻接数组是DWORD类型数组,其中的每一项对应Mesh中的一个三角形。例如,邻接数组的第I项对应的三角形有以下三个索引值定义:
A=I*3
B=I*3 + 1
C=I*3 +2
这里,使用ULONG_MAX=4294967295表示该边没有邻接三角形。其实,这个数就是-1。
由于每个三角形有三条边,所以,他有三个邻接三角形。


因此,每个三角形可能有三个邻接三角形,邻接数组必须有(ID3DXMesh::GetNumFaces() * 3)个元素。
在D3DX中,有很多函数可以输出Mesh的邻接信息,如:
HRESULT ID3DXMesh::GenerateAdjacency(
    FLOAT fEpsilon,
    DWORD* pAdjacency
);

l          fEpsilon –指示当两点距离有多近时,可以认为是一个点。当两点间的距离小于epsilon时,可认为他们是同一个点
l          pAdjacency –用于存储邻接信息的邻接数组
例如:
DWORD adjacencyInfo[Mesh->GetNumFaces() * 3];
Mesh->GenerateAdjacency(0.001f, adjacencyInfo);

10.7.        克隆有时,需要将Mesh的数据另外复制一份,可以使用ID3DXMesh::CloneMeshFVF方法:
HRESULT ID3DXMesh::CloneMeshFVF(
    DWORD Options,
    DWORD FVF,
    LPDIRECT3DDEVICE9 pDevice,
    LPD3DXMESH *ppCloneMesh
);

l          Options –创建Mesh的标志。完整信息可参考SDK文档。下面的几个很常用:
n          D3DXMESH_32BIT –使用32位顶点索引
n          D3DXMESH_MANAGED –Mesh数据将被放在受控的内存缓冲池中
n          D3DXMESH_WRITEONLY –Mesh数据只能执行写操作,不能执行读操作
n          D3DXMESH_DYNAMIC –Mesh缓冲将是动态的
l          FVF –创建新Mesh的灵活定点格式
l          pDevice –与克隆Mesh相关联的D3D设备
l          ppCloneMesh –输出新Mesh
这个方法允许指定与原Mesh不同的Options和FVF。例如,现在有一个定点格式为D3DXFVF_XYZ的Mesh,我们想复制一个顶点格式为D3DXFVF_XYZ|D3DXFVF_NORMAL的Mesh,可以这样做:
// assume _mesh and device are valid
ID3DXMesh* clone = 0;
Mesh->CloneMeshFVF(
    Mesh->GetOptions(), // use same options as source mesh
    D3DFVF_XYZ | D3DFVF_NORMAL,// specify clones FVF
    Device,
    &clone);

10.8.        创建Mesh(D3DXCreateMeshFVF)我们可以使用D3DXCreateMeshFVF函数创建一个空的Mesh对象。所谓空,是指我们已经指定了顶点数和面数(也就是三角形数),函数D3DXCreateMeshFVF也分配了适当大小的内存给顶点、定点索引、属性缓冲区。有了这些缓冲区后,就可以手动填写上下文数据了(需要分别向定点缓冲区、索引缓冲区、属性缓冲区提供定点、索引、属性数据)。
HRESULT WINAPI D3DXCreateMeshFVF(
    DWORD NumFaces,
    DWORD NumVertices,
    DWORD Options,
    DWORD FVF,
    LPDIRECT3DDEVICE9 pD3DDevice,
    LPD3DXMESH *ppMesh
);

l          NumFaces –Mesh中的三角形数,必须指定一个大于0的数
l          NumVertices –定点数,也必须是一个大于0的数
l          Options –创建Mesh的选项标志,常用的标志如下:
n          D3DXMESH_32BIT –Mesh使用32位顶点索引
n          D3DXMESH_MANAGED –使用受控内存池
n          D3DXMESH_WRITEONLY –Mesh的数据只能被写,不能被读
n          D3DXMESH_DYNAMIC –使用动态缓冲
l          FVF –Mesh的顶点格式
l          pD3DDevice –与Mesh相关联的D3D设备
l          ppMesh –输出的Mesh指针
在下一节,将举例说明该函数的用法,到时将手动填充Mesh对象的数据。
另外,还可以使用D3DXCreateMesh函数创建空的Mesh对象。其原形如下:
HRESULT WINAPI D3DXCreateMesh(
    DWORD NumFaces,
    DWORD NumVertices,
    DWORD Options,
    const LPD3DVERTEXELEMENT9 *pDeclaration,
    LPDIRECT3DDEVICE9 pD3DDevice,
    LPD3DXMESH *ppMesh
);

其中的参数的含义就不需要再解释了,他们与D3DXCreateMeshFVF的参数相似。但是,第四个参数是D3DVERTEXELEMENT9的数组,而不是先前的FVF。
10.9.        应用举例:创建并渲染Mesh 我们手动创建一个立方体,它将使用本章讨论的大部分知识点:
l          创建一个空的Mesh
l          向Mesh中填写立方体的几何信息
l          划分子集
l          生成Mesh的邻接信息
l          优化
l          渲染
这里,给出代码的框架,这个例子使用D3DXCreateMeshFVF函数创建Mesh对象。
在代码中,定义如下的全局变量:
ID3DXMesh* Mesh = 0;
const DWORD NumSubsets = 3;
IDirect3DTexture9* Textures[3] = {0, 0, 0};// texture for each subset

第一个是Mesh对象的指针;Mesh对象中有三个子集。每个子集用不同的纹理渲染,数组Textures中存储每个子集的纹理。
主要的代码在Setup函数中,他首先创建一个空的Mesh对象:
bool Setup()
{
    HRESULT hr = 0;
    hr = D3DXCreateMeshFVF(
        12,
        24,
        D3DXMESH_MANAGED,
        Vertex::FVF,
        Device,
        &Mesh);

这里,创建的Mesh具有12个面(三角形),24个顶点,使用他们来描述立方体。
现在,Mesh对象还是空的,需要为其填充适当的顶点/索引数据。
// Fill in vertices of a box
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, 1.0f);
.
.
.
v[22] = Vertex( 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f);
v[23] = Vertex( 1.0f, -1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.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[3] = 0; i[4] = 2; i[5] = 3;
.
.
.
// fill in the right face index data
i[30] = 20; i[31] = 21; i[32] = 22;
i[33] = 20; i[34] = 22; i[35] = 23;
Mesh->UnlockIndexBuffer();

立方体的几何信息输入完毕后,还需要为各个三角形划分子集。可以通过修改Mesh对象的属性缓冲区,指定每个三角形所属的子集。在这个例子中,由索引缓冲定义的12个三角形中,前四个属于子集0,再下面四个属于子集1,最后四个属于子集2。
DWORD* attributeBuffer = 0;
Mesh->LockAttributeBuffer(0, &attributeBuffer);
for(int a = 0; a < 4; a++) // triangles 1-4
    attributeBuffer[a] = 0; // subset 0
for(int b = 4; b < 8; b++) // triangles 5-8
    attributeBuffer = 1; // subset 1
for(int c = 8; c < 12; c++) // triangles 9-12
    attributeBuffer[c] = 2; // subset 2
Mesh->UnlockAttributeBuffer();

到这里,这个Mesh对象已经创建完毕了,可以渲染了。但是,它还没有经过优化。当然,对于这个微不足道的立方体,优化得不到任何好处,但是,我们还是用它演示ID3DXMesh接口方法的用法。在优化之前,需要计算Mesh对象的邻接信息:
std::vector<DWORD> adjacencyBuffer(Mesh->GetNumFaces() * 3);
Mesh->GenerateAdjacency(0.0f, &adjacencyBuffer[0]);

然后,执行优化:
hr = Mesh->OptimizeInplace(
    D3DXMESHOPT_ATTRSORT|D3DXMESHOPT_COMPACT|D3DXMESHOPT_VERTEXCACHE,
    &adjacencyBuffer[0],
    0, 0, 0);

到这里,创建Mesh对象的工作已经完成了。最后,如果一切顺利,函数Setup应该返回true:
    return true;
} // end Setup()

上面说过,经过优化,Mesh对象将建立属性表。使用下面的代码验证一下。
// number of entries in the attribute table
DWORD numEntries = 0;
mesh->GetAttributeTable(0, &numEntries);
vector<D3DXATTRIBUTERANGE> table(numEntries);
mesh->GetAttributeTable(&table[0], &numEntries);
for(int i = 0; i < numEntries; i++)
{
    cout << "Entry " << i << endl;
    cout << "------" << endl;
    cout << "Subset ID: " << table.AttribId << endl;
    cout << "Face Start: " << table.FaceStart << endl;
    cout << "Face Count: " << table.FaceCount << endl;
    cout << "Vertex Start: " << table.VertexStart << endl;
    cout << "Vertex Count: " << table.VertexCount << endl;
    cout <<endl;
}

下面是部分输出信息:
Entry 0
------------
Subset ID: 0
Face Start: 0
Face Count: 4
Vertex Start: 0
Vertex Count: 8
Entry 1
------------
Subset ID: 1
Face Start: 4
Face Count: 4
Vertex Start: 8
Vertex Count: 8
Entry 2
------------
Subset ID: 2
Face Start: 8
Face Count: 4
Vertex Start: 16
Vertex Count: 8

与前面的设计相符。最后,渲染Mesh对象:
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 );
    Mesh->DrawSubset( i );
}
Device->EndScene();
Device->resent(0, 0, 0, 0);

10.10.   总结 l          Mesh对象包含顶点、索引、属性缓冲区。顶点和索引缓冲区包含Mesh对象的几何信息。属性缓冲区的每一项对应一个Mesh对象的一个三角形,指示该三角形所属的子集。
l          Mesh对象可以使用OptimizeInplace和Optimize方法优化。为了更加有效的渲染,优化将重组Mesh的几何信息。使用D3DXMESHOPT_ATTRSORT标志进行优化,会产生属性表,据此,访问一次属性表,就可以完整的渲染一个子集。
l          Mesh对象的邻接信息是一个DWORD数组,每个三角形有三个邻接项,代表与该三角形相邻接的三角形。
l          可以使用D3DXCreateMeshFVF函数创建空的Mesh对象。然后使用相应的Lock*方法填写适当的数据组成Mesh的几何信息和属性信息。
 楼主| 发表于 2009-4-12 10:24:21 | 显示全部楼层
11.1.    关于ID3DXBuffer 这个接口贯穿整个D3DX库,需要对该接口有大体上的认识。ID3DXBuffer是D3DX用来管理连续内存块的结构,他只有两个方法:
l          LPVOID GetBufferPointer(); --返回数据块的首地址
l          DWORD GetBufferSize(); --返回缓冲区的大小,以字节为单位
例如,D3DXLoadMeshFromX函数就使用ID3DXBuffer返回Mesh对象的邻接信息。因邻接信息是DWORD数组,所以需要进行类型转换。如:
DWORD* info =(DWORD*)adjacencyInfo->GetBufferPointer();
D3DXMATERIAL* mtrls = (D3DXMATERIAL*)mtrlBuffer->GetBufferPointer();
又因为ID3DXBuffer是一个COM对象,所以,用完后,需要进行释放。
adjacencyInfo->Release();
mtrlBuffer->Release();
也可以使用如下函数创建一个空的ID3DXBuffer对象:
HRESULT WINAPI D3DXCreateBuffer(
    DWORD NumBytes,
    LPD3DXBUFFER *ppBuffer
);
其中参数的含义显而易见。例如,创建一个包含四个整型数的缓冲区:
ID3DXBuffer* buffer = 0;
D3DXCreateBuffer( 4 * sizeof(int), &buffer );
11.2.    X文件使用D3DXCreate*函数,可以创建一些简单的几何体,如球、圆柱、立方体等。如果想通过手动设定顶点的方式创建较复杂的3D对象,你会发现这太麻烦了,简直无法做到!现在,可以使用很多种3D建模工具软件来完成这项枯燥工作,如3DS MAX,LightWave 3D,Maya等。使用这样的建模工具,可以在可视化的、交互的环境中设计复杂、逼真的模型,而且还有丰富的工具可用,使整个建模过程相当简单。这里的简单是相对于在“程序中手动设定顶点的方式建模”,实际上,这些建模工具还是相当复杂的,想得心应手的使用,可不是一朝一夕之功。
这些建模工具可以将所建立的模型的数据(几何信息,材质,动画等)保存到文件。我们需要从文件中分析提取需要的数据,然后应用到自己的3D程序中。有一种常用的文件格式,XFile,其扩展名为.x,较为简单,是Direct3D定义的文件格式,D3DX库提供了完整的支持,可满足一般的需要。
11.2.1. 加载一个.x文件使用下面的函数加载存储在.x文件中的Mesh数据。它创建一个ID3DXMesh对象,然后从.x文件中读取Mesh的几何信息。
HRESULT WINAPI D3DXLoadMeshFromX(
    LPCTSTR pFilename,
    DWORD Options,
    LPDIRECT3DDEVICE9 pD3DDevice,
    LPD3DXBUFFER *ppAdjacency,
    LPD3DXBUFFER *ppMaterials,
    LPD3DXBUFFER *ppEffectInstances,
    DWORD *pNumMaterials,
    LPD3DXMESH *ppMesh
);
l          pFileName –.x文件的文件名
l          Options –创建Mesh的标志。详情可参考SDK文档中的D3DXMESH枚举类型。常用的几个标志如下:
n          D3DXMESH_32BIT –使用32位的顶点索引,默认为16位
n          D3DXMESH_MANAGED –使用受控的内存缓冲池
n          D3DXMESH_WRITEONLY –缓冲区只可执行写操作
n          D3DXMESH_DYNAMIC –使用动态内存缓冲池
l          pD3DDevice –D3D设备指针
l          ppAdjacency –使用ID3DXBuffer返回Mesh的邻接信息,这是一个DWORD数组
l          ppMaterials –使用ID3DXBuffer返回Mesh的材质数据,这是一个D3DXMATERIAL类型数组
l          ppEffectInstances –使用ID3DXBuffer返回一个D3DXEFFECTINSTANCE结构数组
l          pNumMaterials –返回Mesh对象的材质数量,也就是通过ppMaterials返回的D3DXMATERIAL数组的元素数
l          ppMesh –返回ID3DXMesh对象
11.2.2. XFile材质函数D3DXLoadMeshFromX的第七个参数返回Mesh对象的材质数量,第五个参数是D3DXMATERIAL的数组,包含Mesh的材质数据。D3DXMATERIAL结构的定义如下:
typedef struct D3DXMATERIAL {
    D3DMATERIAL9 MatD3D;
    LPSTR pTextureFilename;
} D3DXMATERIAL;
这个结构很简单,包含一个D3DMATERIAL9结构和一个以0字符结束的字符串的指针,表示相关联的纹理文件。. x文件并不包含纹理数据,只包含纹理文件的文件名。使用该函数加载.x文件后,还需要根据纹理文件的文件名手动加载纹理。
函数D3DXLoadMeshFromX返回的D3DXMATERIAL数组正好与Mesh对象的子集相对应。也就是说,第I个子集的材质纹理信息就存储在ppMaterials[I]中。
11.2.3. X文件的应用实例这个例子相当的简单,它加载bigship1.x文件,这是DirectX SDK中的一个文件。这里只列出代码的主要框架。
ID3DXMesh* Mesh = 0;
vector<D3DMATERIAL9> Mtrls(0);
vector<IDirect3DTexture9*> Textures(0);

bool Setup()
{
    HRESULT hr = 0;
    //
    // Load the XFile data.
    //
    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;
    }
    //
    // Extract the materials, load textures.
    //
    if( mtrlBuffer != 0 && numMtrls != 0 )
    {
        D3DXMATERIAL* mtrls=(D3DXMATERIAL*)mtrlBuffer->GetBufferPointer();
        for(int i = 0; i < numMtrls; i++)
        {
            // the MatD3D property doesn't have an ambient value
            // set when it’s loaded, so set it now:
            mtrls.MatD3D.Ambient = mtrls.MatD3D.Diffuse;
            // save the ith material
            Mtrls.push_back( mtrls.MatD3D );
            // check if the ith material has an associative
            // texture
            if( mtrls.pTextureFilename != 0 )
            {
                // yes, load the texture for the ith subset
                IDirect3DTexture9* tex = 0;
                D3DXCreateTextureFromFile(
                    Device,
                    mtrls.pTextureFilename,
                    &tex);
                // save the loaded texture
                Textures.push_back( tex );
            }
            else
            {
                // no texture for the ith subset
                Textures.push_back( 0 );
            }
        }
    }
    Release<ID3DXBuffer*>(mtrlBuffer); // done w/ buffer
    .
    . // Snipped irrelevant code to this chapter (e.g., setting up lights,
    . // view and projection matrices, etc.)
    .
    return true;
}
最后,渲染Mesh对象:
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 );
    Device->SetTexture(0, Textures);
    Mesh->DrawSubset(i);
}
Device->EndScene();
Device->resent(0, 0, 0, 0);
 楼主| 发表于 2009-4-12 10:24:56 | 显示全部楼层
11.2.4. 创建顶点的法向量有时.x文件不包含顶点的法向量,这时,如果使用光照,则需要手动计算顶点的法向量。对于接口ID3DXMesh和其父接口ID3DXBaseMesh,可以使用如下函数计算顶点的法向量:
HRESULT WINAPI D3DXComputeNormals(
    LPD3DXBASEMESH pMesh,
    const DWORD *pAdjacency
);
该函数将使用法向量的平均值作为顶点的法向量。如果提供了Mesh对象的邻接信息,则重复的顶点会被忽略;如果没有邻接信息,重复的顶点也会被重复计算。另外一点更加重要,需要计算法向量的Mesh对象的顶点格式必须包含D3DFVF_NORMAL标志。
如果.x文件中没有法向量数据,通过D3DXLoadMeshFromX函数创建的ID3DXMesh对象的顶点格式就不包含D3DFVF_NORMAL标志。因此,在计算法向量之前,必须使用D3DFVF_NORMAL标志复制Mesh对象。
// does the mesh have a D3DFVF_NORMAL in its vertex format?
if ( !(pMesh->GetFVF() & D3DFVF_NORMAL) )
{
    // no, so clone a new mesh and add D3DFVF_NORMAL to its format:
    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
}
11.3.    渐进模型(Progressive Mesh)渐进Mesh是ID3DXPMesh接口的对象,可以简化边缩减转换(Edge Collapse Transformations (ECT))。每次ECT都回减少一个顶点和一两个面。由于ECT过程是可逆的(他的逆过程叫顶点分裂),所以,可以通过逆过程将Mesh恢复到原始状态。当然,我们也无法得到比原始状态更精细的Mesh对象,最多只能将其恢复到原始状态。
Progressive Mesh和纹理中的mipmap十分相似。在较小的和远距离的对象上使用高分辨率的纹理纯粹是浪费,因为纹理的细节根本就表现不出来。对于Mesh对象也是一样,较小的距离较远的Mesh不需要太多的三角形,多了纯粹是浪费。所以,在渲染时,实在没有必要在这些根本表现不出来的地方浪费时间。
一种方法是,根据Mesh对象距离视点的距离调整其精细水准(LOD,Level Of Detail)。当距离增加时,可降低LOD;反之,则增加LOD。
这里只讨论ID3DXPMesh接口的用法,不讨论其实现细节。如果你感兴趣,可参考其它资料。
11.3.1. 生成一个渐进Mesh 使用下面的函数创建ID3DXPMesh对象:
HRESULT WINAPI D3DXGeneratePMesh(
    LPD3DXMESH pMesh,
    const DWORD *pAdjacency,
    const D3DXATTRIBUTEWEIGHTS *pVertexAttributeWeights,
    const FLOAT *pVertexWeights,
    DWORD MinValue,
    DWORD Options,
    LPD3DXPMESH *ppPMesh
);
l          pMesh –输入的普通的Mesh对象
l          pAdjacency –Mesh对象的邻接信息,这是一个DWORD数组
l          pVertexAttributeWeights –结构D3DXATTRIBUTEWEIGHTS的数组,元素个数为pMesh->GetNumVertices(),表示顶点的属性的权。在简化Mesh对象时,权值决定一个顶点被删除的可能性大小。该参数可以设为NULL,这时顶点使用默认的权值。
l          pVertexWeights –顶点的权,是float数组,元素个数是pMesh->GetNumVertices(),用于决定顶点在简化时被删除的可能性的大小。该参数也可设为NULL,这时,顶点默认的权值为1.0f。
l          MinValue –在简化Mesh时,顶点或者三角形数的最小个数。该参数是必要的,而且与顶点权值和顶点属性权值有关系,最终也许达不到该数值。
l          Options –只能取D3DXMESHSIMP枚举类型中的一个值:
n          D3DXMESHSIMP_VERTEX –上一个参数MinValue指顶点数
n          D3DXMESHSIMP_FACE –上一个参数MinValue指三角形数
l          ppPMesh –返回生成的渐进Mesh
11.3.2. 顶点的属性权 typedef struct _D3DXATTRIBUTEWEIGHTS {
    FLOAT Position;
    FLOAT Boundary;
    FLOAT Normal;
    FLOAT Diffuse;
    FLOAT Specular;
    FLOAT Texcoord[8];
    FLOAT Tangent;
    FLOAT Binormal;
} D3DXATTRIBUTEWEIGHTS, *LPD3DXATTRIBUTEWEIGHTS;
通过这个结构,可以为顶点的每个属性指定一个权值,0.0表示属性没有权。权值越高,在简化时,越不易被删除。默认的权值如下:
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};
一般情况下,推荐使用默认的权值,除非你认为非常有必要使用不同的权值。
11.3.3. ID3DXPMesh的方法接口ID3DXPMesh继承自ID3DXBaseMesh,下面介绍一些常用的方法。
l          DWORD GetMaxFaces(VOID); --返回Mesh的最大三角形数
l          DWORD GetMaxVertices(VOID); --返回Mesh的最大顶点数
l          DWORD GetMinFaces(VOID); --返回Mesh的最少三角形数
l          DWORD GetMinVertices(VOID); --返回Mesh的最少顶点数
l          HRESULT SetNumFaces(DWORD Faces); --设置Mesh的三角形数。例如,假定Mesh现在有50个三角形,而想将其简化为30个三角形,则调用pmesh->SetNumFaces(30)。调整后的三角形数可能并不是我们设定的个数,因为PMesh的三角形数还有最大和最少的限制。
l          HRESULT SetNumVertices(DWORD Vertices); --设置PMesh的顶点个数。例如,假设现在PMesh有20个顶点,而为了增加其精细程度将顶点数增为40个,则只需调用pmesh->SetNumVertices(40)。与三角形数一样,最终的结果可能不是我们指定的数值,同样有最大最少个数的限制。
l          HRESULT TrimByFaces(
    DWORD NewFacesMin,
    DWORD NewFacesMax,
    DWORD *rgiFaceRemap,
    DWORD *rgiVertRemap
); --该方法设定PMesh三角形数的最大最小值。新的最大最小值必须在当前的最大最小值之间,即必须在[GetMinFaces(),GetMaxFaces()]内。同时,该方法还将返回三角形和顶点的重影射信息。
l          HRESULT TrimByVertices(
    DWORD NewVerticesMin,
    DWORD NewVerticessMax,
    DWORD *rgiFaceRemap,
    DWORD *rgiVertRemap
); --该方法与上面的方法相似。
11.3.4. 应用举例:Progressive Mesh 这个例子与前面的XFile例子相似,只是其中使用ID3DXPMesh接口。
与前例相似,我们使用如下的全局变量:
ID3DXMesh* SourceMesh = 0;
ID3DXPMesh* PMesh = 0; // progressive mesh
vector<D3DMATERIAL9> Mtrls(0);
vector<IDirect3DTexture9*> Textures(0);
在创建Progressive Mesh之前,需要使用ID3DXMesh接口加载.x文件:
HRESULT hr = 0;
// ...Load XFile data into SourceMesh snipped.
//
// ...Extracting materials and textures snipped.

//
// Generate the progressive mesh.
//
hr = D3DXGeneratePMesh(
    SourceMesh,
    (DWORD*)adjBuffer->GetBufferPointer(), // adjacency
    0, // default vertex attribute weights
    0, // default vertex weights
    1, // simplify as low as possible
    D3DXMESHSIMP_FACE, // simplify by face count
    &Mesh);
Release<ID3DXMesh*>(SourceMesh); // done w/ source mesh
Release<ID3DXBuffer*>(adjBuffer); // done w/ buffer
if(FAILED(hr))
{
    ::MessageBox(0, "D3DXGeneratePMesh() - FAILED", 0, 0);
    return false;
}
通常,因为顶点和顶点属性权值的缘故,很难将Mesh简化到只有一个三角形的程度,但是,如果指定将Mesh简化到一个三角形的程度,则可以将Mesh简化到解析度最低的程度。
现在,渐进Mesh已经生成了,但是,如果直接渲染,则Mesh的解析度此时最低。如果想渲染全解析度的PMesh,首先需要设置其三角形数:
// set to original (full) detail
DWORD maxFaces = PMesh->GetMaxFaces();
PMesh->SetNumFaces(maxFaces);
在渲染PMesh时,我们使用键盘输入控制其解析度:A键将增加解析度,S键减小解析度。
// 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 )
{
    // Sometimes we must add more than one face to invert
    // an edge collapse transformation because of the internal
    // implementation details of the ID3DXPMesh interface. In
    // other words, adding one face may possibly result in a
    // mesh with the same number of faces as before. Thus to
    // increase the face count we may sometimes have to add
    // two faces at once.
    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);
 楼主| 发表于 2009-4-12 10:25:07 | 显示全部楼层
上面的方法直截了当,只是增加三角形数时,有时需要增加两个来满足ECT的需要。
最后,使用和渲染ID3DXMesh同样的方法渲染ID3DXPMesh。另外,为了更加直观的观察PMesh的三角形数的变化情况,使用黄色材质在线框模式(Wireframe Mode)下渲染Mesh的三角形。
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 );
    Device->SetTexture(0, Textures);
    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->resent(0, 0, 0, 0);
11.4.    对象的边界范围有时,需要计算Mesh对象的边界范围,常用的有两种类型:立方体和球。也有使用其它方法的,如圆柱体、椭球体、菱形体、太空舱形等。这里,我们只讨论立方体和球体两种边界形式。
边界盒或边界球常用来加速多物体间的可视范围测试、碰撞检测等。如果一个Mesh的边界盒/球不可见,就可认为Mesh也不可见。检测边界盒/球是否可见比检测Mesh中所有的三角形是否可见要方便得多。在碰撞检测中,如果一枚导弹点火起飞,我们需要检测他是否撞到了同一个场景中的目标。由于这些对象全是大量的三角形构成,我们可以依次检测每个对象的每个三角形,检测导弹(可以使用数学模型中的射线)是否撞到了这些三角形。这个方法需要进行多次的射线/三角形交点的运算。较好的方法是使用边界盒或边界球,计算射线与场景中的每个对象的边界盒/边界球的交点。如果射线与对象的边界范围相交,可以认为该对象被击中了。这是一个公平的近似方法,如果需要更高的精度,可以用边界范围法先去除那些明显不会相撞的对象,然后用更精确地方法检测很可能相撞的对象。如果边界范围检测发现相撞,则该对象就很有可能相撞。
D3DX库提供了计算Mesh对象边界盒/球的函数。这些函数使用顶点数组作为输入计算边界盒/球,可以使用各种顶点格式:
HRESULT WINAPI D3DXComputeBoundingSphere(
    const D3DXVECTOR3 *pFirstPosition,
    DWORD NumVertices,
    DWORD dwStride,
    D3DXVECTOR3 *pCenter,
    FLOAT *pRadius
);
l          pFirstPosition –顶点数组的地址,顶点的第一个向量需要是顶点的位置坐标
l          NumVertices –顶点的数目
l          dwStride –顶点大小,以字节为单位。因顶点中有很多附加数据,如法向量、纹理坐标等,计算边界范围不需要这些数据,所以,需要知道跳过多少数据才能找到下一个顶点的坐标。
l          pCenter –返回边界范围的中心
l          pRadius –返回边界球的半径
HRESULT WINAPI D3DXComputeBoundingBox(
    const D3DXVECTOR3 *pFirstPosition,
    DWORD NumVertices,
    DWORD dwStride,
    D3DXVECTOR3 *pMin,
    D3DXVECTOR3 *pMax
);
前三个参数与计算边界球的函数相同;后两个参数返回边界盒的最小和最大点。
11.4.1. 边界检测类型为了使边界检测易于使用,我们实现几个辅助的数据结构:
struct BoundingBox
{
    BoundingBox();
    bool isPointInside(D3DXVECTOR3& p);
    D3DXVECTOR3 _min;
    D3DXVECTOR3 _max;
};
struct BoundingSphere
{
    BoundingSphere();
    D3DXVECTOR3 _center;
    float _radius;
};

BoundingBox::BoundingBox()
{
    // infinite small bounding box
    _min.x = FLT_MAX;
    _min.y = FLT_MAX;
    _min.z = FLT_MAX;
    _max.x = -FLT_MAX;
    _max.y = -FLT_MAX;
    _max.z = -FLT_MAX;
}
bool BoundingBox::isPointInside(D3DXVECTOR3& p)
{
    // is the point inside the bounding box?
    if (p.x >= _min.x && p.y >= _min.y && p.z >= _min.z &&
        p.x <= _max.x && p.y <= _max.y && p.z <= _max.z)
    {
        return true;
    }
    else
    {
        return false;
    }
}
BoundingSphere::BoundingSphere()
{
    _radius = 0.0f;
}
11.4.2. 边界范围应用举例该例子演示D3DXComputeBoundingSphere和D3DXComputeBoundingBox函数的用法。程序首先加载一个.x文件,然后计算Mesh的边界盒/球。代码中创建两个ID3DXMesh对象,分别使用边界盒和边界球。最后,分别渲染他们。
这个例子很简单,这里只给出有关边界范围的代码:
bool ComputeBoundingSphere(
    ID3DXMesh* mesh, // mesh to compute bounding sphere for
    BoundingSphere* sphere) // return bounding sphere
{
    HRESULT hr = 0;
    BYTE* v = 0;
    mesh->LockVertexBuffer(0, (void**)&v);
    hr = D3DXComputeBoundingSphere(
        (D3DXVECTOR3*)v,
        mesh->GetNumVertices(),
        D3DXGetFVFVertexSize(mesh->GetFVF()),
        &sphere->_center,
        &sphere->_radius);
    mesh->UnlockVertexBuffer();
    if( FAILED(hr) )
        return false;
    return true;
}
bool ComputeBoundingBox(
    ID3DXMesh* mesh, // mesh to compute bounding box for
    BoundingBox* box) // return bounding box
{
    HRESULT hr = 0;
    BYTE* v = 0;
    mesh->LockVertexBuffer(0, (void**)&v);
    hr = D3DXComputeBoundingBox(
        (D3DXVECTOR3*)v,
        mesh->GetNumVertices(),
        D3DXGetFVFVertexSize(mesh->GetFVF()),
        &box->_min,
        &box->_max);
    mesh->UnlockVertexBuffer();
    if( FAILED(hr) )
        return false;
    return true;
}
类型转换(D3DXVECTOR3*)v假定顶点坐标在顶点结构的开头位置,一般都是如此。
11.5.    总结 l          现在,我们可以用3D建模软件导出的.x文件构建复杂的Mesh对象。使用D3DXLoadMeshFromX函数取得ID3DXMesh对象,就可以在自己的应用程序中自由使用了。
l          使用ID3DXPMesh接口表示的渐进Mesh,可以控制其精细程度。可以根据对象在场景中的突出程度调整PMesh的精细程度。
l          我们可以使用D3DXComputeBoundingSphere和D3DXComputeBoundingBox函数计算Mesh对象的边界。边界范围很有用,其接近对象真实的边界,可加速碰撞检测等的计算。
发表于 2009-4-14 20:08:28 | 显示全部楼层
图片失效了。LZ查看下http://blog.csdn.net里的图片是否还在。
发表于 2009-7-17 22:28:04 | 显示全部楼层
看的好痛苦啊~~
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|小黑屋|3D数字艺术论坛 ( 沪ICP备14023054号 )

GMT+8, 2025-2-6 11:47

Powered by Discuz! X3.4

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表