1、2,第12章 网格(2),主要目标: 学习怎样把X文件里的数据加载D3DXMesh对象中。 了解使用渐进式网格的好处,以及学习使用渐进式网格的接口ID3DXPMesh。 学习包围体相关的知识,如包围体的用途及怎样用D3DX函数创建包围体。,3,12.1 ID3DXBuffer接口,ID3DXBuffer接口是一个通用的数据结构,D3DX用它来保存位于一块连续的内存区域中的数据,它只包含两个方法: LPVOID GetBufferPointer(); /返回数据区首位置的指针 DWORD GetBufferSize(); /返回数据区的大小 为了保持这个结构的通用性, ID3DXBuffer接口
2、使用的是void型的指针。,4,12.1 ID3DXBuffer接口,由于ID3DXBuffer是一个COM对象,因此为了避免内存泄漏,使用之后必须调用Release方法释放内存: adjacencyInfo-Release(); mtrlBuffer-Release(); 用下面的方法可以创建一个空的ID3DXBuffer接口对象: HRESULT D3DXCreateBuffer(DWORD NumBytes, / 缓冲区大小,以字节为单位LPD3DXBUFFER *ppBuffer / 返回创建好的缓冲区 ); 下面的例子创建一个可以保存4个整型数的缓冲区: ID3DXBuffer *b
3、uffer = 0; D3DXCreateBuffer(4 * sizeof(int), ,5,12.2 .X文件,3D建模软件就是专门被开发以用来减少创建3D物体过程中这种工作的复杂度的。 游戏开发里面常用的建模工具包括3DS Max()、LightWave3D()以及Maya()。 现在就有一套独特的网格文件格式,被称为X文件(扩展名.x)。许多3D建模工具都可以导出这种文件格式,而且也有现成的转换器来把许多别的流行的文件格式转化为.x格式。X文件的方便之处就在于它是DirectX自带的官方格式,也就是说D3DX库提供了对X文件进行专门读取和保存的函数。因此,如果使用这种文件格式,就可以避
4、免编写自己的文件读取和保存程序。,6,12.2.1 读取.X文件,注意这个方法创建了一个ID3DXMESH对象,然后把X文件中的网格数据加载到这个对象中: HRESULT D3DXLoadMeshFromX(LPCSTR pFilename,DWORD Options,LPDIRECT3DDEVICE9 pDevice,LPD3DXBUFFER *ppAdjacency,LPD3DXBUFFER *ppMaterials,LPD3DXBUFFER *ppEffectInstances,PDWORD pNumMaterials,LPD3DXMESH *ppMesh );,7,12.2.2 X文件
5、中的材质,D3DXMATERIAL结构声明如下: typedef struct D3DXMATERIAL D3DMATERIAL9 MatD3D; LPSTR pTextureFilename; D3DXMATERIAL; 这是一个简单的结构,包括了一个基本的D3DMATERIAL9结构以及一个描述相关纹理文件名的字符串指针。 .x文件本身并不存放纹理数据,而仅仅是保留纹理图片的文件名,在需要的时候通过文件名来引用存放在图片文件的真正的纹理数据。 使用D3DXLoadMeshFromX函数载入.x文件数据后,在D3DXMATERIAL数组中的元素将和网格中的网格子集一一对应。,8,12.2.3
6、 X文件实例,图12.1是本示例运行时的屏幕截图。 本示例用到了如下的全局变量: ID3DXMesh *Mesh = 0; std:vector Mtrls(0); std:vector Textures(0);,9,12.2.3 X文件实例,首先实现Setup函数,用于装载X文件: 参见教材P179 读取了X文件数据后,应遍历D3DXMATERIAL数组来加载所有的网格中使用的纹理: 参见教材P180 在Display中,在每一帧中让网格有个小的旋转量,这样看起来网格模型就像在不停地旋转。由于子集被标识为0、1、2、.、n-1(n是子集的数量),因此用一个简单的循环就可以渲染整个网格了: 参
7、见教材P181,10,12.2.4 生成顶点法线,可以用以下的方法来生成顶点法线: HRESULT D3DXComputeNormals( LPD3DXBASEMESH pMesh, / 需要计算法线的网格 const DWORD *pAdjacency / 需要传入的邻接信息 ); 该函数通过计算平均值来获得顶点法线如果提供了邻接信息,则重复顶点会被忽略掉;如果没有提供邻接信息,重复的顶点的法线是那些引用这个顶点的面法线的平均值。,11,12.3 渐进式网格,图12.2展示了具有三种不同细节层次(Level Of Detail,LOD)的同一个网格,细节层次从左到右依次为高、中、低。 渐进式
8、网格的概念类似于纹理中的纹理细节(MIPMAP)的概念。 在少量三角形就能胜任的情况下,应该减少三角形的数量来节省渲染时间。 利用渐进式网格的一种方法是根据网格距视点的距离调整网格的细节层次。,12,12.3.1 生成渐进式网格,用下面的方法可以获得一个ID3DXPMesh接口对象: HRESULT D3DXGeneratePMesh(LPD3DXMESH pMesh,CONST DWORD *pAdjacency,CONST LPD3DXATTRIBUTEWEIGHTS pVertexAttributeWeights,CONST FLOAT *pVertexWeights,DWORD Min
9、Value,DWORD Options,LPD3DXPMESH *ppPMesh );,13,12.3.2 顶点属性权重,代码如下: typedef struct _D3DXATTRIBUTEWEIGHTS FLOAT Position; FLOAT Boundary; FLOAT Normal; FLOAT Diffuse; FLOAT Specular; FLOAT Texcoord8; FLOAT Tangent; FLOAT Binormal; D3DXATTRIBUTEWEIGHTS; 通过D3DXATTRIBUTEWEIGHTS结构,可以指定影响某个顶点的所有可能的组件的权重。,1
10、4,12.3.2 顶点属性权重,权重的默认取值如下: D3DXATTRIBUTEWEIGHTS AttributeWeights; AttributeWeights.Position = 1.0; AttributeWeights.Boundary = 1.0; AttributeWeights.Normal = 1.0; AttributeWeights.Diffuse = 0.0; AttributeWeights.Specular = 0.0; AttributeWeights.Tex8 = 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0; 在必要的时候可以
11、修改网格中的权重值,一般推荐使用默认值。,15,12.3.3 ID3DXPMesh接口的相关方法,ID3DXPMesh接口继承自ID3DXBaseMesh接口,它也具有ID3DXMesh接口的所有方法,同时还拥有它专有的一些方法。 DWORD GetMaxFaces(VOID):返回渐进式网格的最大表面数量。 DWORD GetMaxVertices(VOID):返回渐进式网格的最大顶点数量。 DWORD GetMinFaces(VOID):返回渐进式网格的最小表面数量。 DWORD GetMinVertices(VOID):返回渐进式网格的最小顶点数量。 HRESULT SetNumFace
12、s(DWORD Faces):通过这个方法,可以设置简化或者细化网格后的表面数量。 HRESULT SetNumVertices(DWORD Vertices):通过这个方法可以指定简化后或细化后的网格顶点数量。,16,12.3.3 ID3DXPMesh接口的相关方法,HRESULT TrimByFaces(DWORD NewFacesMin, DWORD NewFacesMax, DWORD *rgiFaceRemap, / Face remap info DWORD *rgiVertRemap / Vertex remap info ); 使用这个方法可以分别通过NewFacesMin和N
13、ewFacesMax设置新的最小表面和最大表面数量。 HRESULT TrimByVertices(DWORD NewVerticesMin, DWORD NewVerticesMax, DWORD *rgiFaceRemap, / Face remap info DWORD *rgiVertRemap / Vertex remap info ); 使用这个方法可以分别通过NewVerticesMin和NewVerticesMax设置新的最小顶点和最大顶点数量。,17,12.3.4 示例:渐进式网格,这个示例允许用户通过键盘输入改变渐进式网格的细节层次。通过“A”键用户可以为网格增加表面,通过
14、“S”键可以为网格减少表面(效果见图12.3)。 在本示例中使用的全局变量与在.x文件中用到的全局变量很相似,只是增加了一个新的变量来存储转化后的渐进式网格: ID3DXMesh *SourceMesh = 0; ID3DXPMesh *PMesh = 0; / 渐进式网格 std:vector Mtrls(0); std:vector Textures(0);,18,12.3.4 示例:渐进式网格,生成渐进式网格之前必须导入一个 “源”网格,因此需要先把X文件装载到ID3DXMesh对象中去,然后生成渐进式网格: 参见教材P187 现在,就可以开始渲染渐进式网格,但是直接渲染的话,仅仅可以渲
15、染出具有最低细节层次的网格,而如果渲染有最高细节层次的网格,需要这样做: 参见教材P188 在WIRELESS模式下用黄色线表示网格中的三角形,可以看到更改网格的细节层次时对渐进式网格中三角形数量的影响情况。 参见教材P188,19,12.4 包 围 体,图12.4分别展示了包围体是球体和立方体的网格(球体可以用中心点和半径来描述;立方体可以用最小值点和最大值点来描述)。 包围立方体和包围球体经常被用于加快可见性检测(Visibility Test)和碰撞检测(Collision Test)的速度。,20,12.4 包 围 体,计算球形包围体的函数: HRESULT D3DXComputeBo
16、undingSphere(LPD3DXVECTOR3 pFirstPosition,DWORD NumVertices,DWORD dwStride,D3DXVECTOR3 *pCenter,FLOAT *pRadius ); 计算方形包围体的函数: HRESULT D3DXComputeBoundingBox(LPD3DXVECTOR3 pFirstPosition,DWORD NumVertices,DWORD dwStride,D3DXVECTOR3 *pMin,D3DXVECTOR3 *pMax );,21,12.4.1 新的常量,常量INFINITY用来描述float可以表示的最大数
17、值。 常量EPSILON用来表示一个很小的数值,比它还小的浮点数会被当作0。 下面的函数展示了怎样用EPSILON来判断两个浮点数是否相等: bool Equals(float lhs, float rhs) / 如果lhs = rhs,它们的差应该为0 return fabs(lhs - rhs) EPSILON ? true : false; ,22,12.4.2 各种不同的包围体,为了更好地使用包围球体和包围立方体,需要用类对它们进行封装。在d3d命名空间中,将实现这些类: 参见教材P191,23,12.4.3 包围体示例,示例中使用了D3DXComputeBoundingSphere和
18、D3DXComputeBoundingBox两个函数。 最后网格模型分别和它的包围立方体或者包围球体一起进行渲染(请参看图 12.5)。使用空格键在显示包围球体或显示包围立方体之间进行切换。,24,12.4.3 包围体示例,示例很简单,下面直接来看代码。以下两个函数将分别被用来计算指定网格的包围球体和包围盒: 参见教材P193 注意这里对v进行强制转换,(D3DXVECTOR3*)v将指向存储在顶点数据结构开始位置的顶点位置向量。此外,还可以用D3DXGetFVFVertexSize()函数来获得给定灵活顶点格式的顶点结构的大小。,25,12.5 小 结,在3D建模软件中完成复杂的网格的建模并将它们导出到X文件中。 然后通过D3DXLoadMeshFromX()函数,将X文件里面记录的网格数据导入到应用程序所创建的ID3DXMesh对象中。 ID3DXPMesh接口所表示的渐进式网格可以用来控制网格的细节层次(LOD),这样可以动态地改变网格的细节,提高程序的渲染效率。渐进式网格很有用,因为经常需要根据网格在场景中的不同位置而改变网格的细节层次。 使用D3DXComputeBoundingSphere和D3DXComputeBoundingBox函数,可以计算包围球体和包围立方体。,