■ 第三个参数,LPDIRECT3DDEVICE9类型的pD3DDevice,也就是我们的金钥匙,Direct3D设备的指针。
■ 第四个参数,LPD3DXBUFFER类型的*ppAdjacency,用于保存加载网格的邻接信息,也就是包含每个多边形周围的多边形信息的缓冲区的内存地址。
■ 第五个参数,LPD3DXBUFFER类型的*ppMaterials,用于保存网格的所有子集的材质,指向用于存储模型材质和纹理文件名的缓冲区的地址,而材质的数目存在之后第七个参数pNumMaterials中了。
■ 第六个参数,LPD3DXBUFFER类型的*ppEffectInstances,用于存储网格模型的特殊效果,指向用于存储模型效果实例的缓冲区的内存地址,这个参数通常设为NULL就可以了。 ■ 第七个参数,DWORD类型的*pNumMaterials,它配合着第五个参数,用于存储所有子集材质的数目。
■ 第八个参数,LPD3DXMESH类型的*ppMesh,指向我们从文件生成的Direct3D网格模型指针的地址。可以说我们调用D3DXLoadMeshFromX就是为了从文件加载X文件的模型信息并进行模型的创建,从而能得到这个指向创建好的模型的指针地址。后面关于我们创建好的网格模型的访问,都靠这个ppMesh参数了。
我们应该可以发现,在上面我们的D3DXLoadMeshFromX函数中引入了一个新的Direct3D类型,他就是LPD3DXBUFFER。LPD3DXBUFFER因数据操作的方便性而诞生,我们称它为泛型数据结构。它的好处是可以存储顶点位置坐标、材质、纹理等多种类型的Direct3D数据,而不必对每种数据都去声明一种函数接口类型。可使用接口函数
ID3DXBuffer::GetBufferPointer()获取缓冲区中的数据,使用ID3DXBuffer::GetBufferSize()获得缓冲区数据大小、这两个接口函数的声明如下:
[cpp] view plaincopyprint?
1. LPVOID GetBufferPointer(); 2. DWORD GetBufferSize();
没错,这就是原型声明,因为这两个函数都没有参数,所以他们的身子显得非常单薄。
比如,我们要从网格模型中提取材质属性和纹理文件名,那么代码就是像这样写:
[cpp] view plaincopyprint?
1. D3DXMATERIAL *pMtrls =(D3DXMATERIAL*)pMtrlBuffer->GetBufferPointer();
Ⅱ. 三步曲之二:载入材质和纹理
如果之前的D3DXLoadMeshFromX函数调用成功的话,那么参数ppMaterials就会获得.X文件中三维模型的材质和纹理等信息,而pNumMaterials参数就会获得材质的数目。 X文件中的材质信息是以D3DXMATERIAL结构类型的数组形式储存的。其中,该结构定义了D3DMATERIAL9结构类型的成员和一个指向以NULL结尾的字符串指针,而该字符串用于指定与网格子集相关的纹理贴图文件名。我们可以在MSDN中查到D3DXMATERIAL结构体的定义如下:
[cpp] view plaincopyprint?
1. typedef struct D3DXMATERIAL { 2. D3DMATERIAL9 MatD3D; //材质信息
3. LPSTR pTextureFilename; //纹理贴图文件 4. } D3DXMATERIAL, *LPD3DXMATERIAL;
当我们加载X文件后,需要遍历整个D3DXMATERIAL结构类型的数组,用于取出保存在ID3DXBuffer接口对象中的材质信息。由于X文件中并未存储具体的纹理数据,它只包含纹理贴图的文件名,因此需要我们自己根据该文件名创建相应的纹理对象。就像这样:
[cpp] view plaincopyprint?
1. // 从X文件中加载网格数据
2. LPD3DXBUFFER pAdjBuffer = NULL; 3. LPD3DXBUFFER pMtrlBuffer =NULL; 4.
5. D3DXLoadMeshFromX(L\,D3DXMESH_MANAGED, g_pd3dDevice, 6. &pAdjBuffer,&pMtrlBuffer, NULL, &g_dwNumMtrls, &g_pMesh); 7.
8. // 读取材质和纹理数据
9. D3DXMATERIAL *pMtrls =(D3DXMATERIAL*)pMtrlBuffer->GetBufferPointer()
; //创建一个D3DXMATERIAL结构体用于读取材质和纹理信息 10. g_pMaterials = newD3DMATERIAL9[g_dwNumMtrls];
11. g_pTextures = new LPDIRECT3DTEXTURE9[g_dwNumMtrls]; 12.
13. for (DWORD i=0;i 15. //获取材质,并设置一下环境光的颜色值 16. g_pMaterials[i] =pMtrls[i].MatD3D; 17. g_pMaterials[i].Ambient= g_pMaterials[i].Diffuse; 18. 19. //创建一下纹理对象 20. g_pTextures[i] = NULL; 21. D3DXCreateTextureFromFileA(g_pd3dDevice,pMtrls[i].pTextureFile name, &g_pTextures[i]); 22. } 23. 24. SAFE_RELEASE(pAdjBuffer) 25. SAFE_RELEASE(pMtrlBuffer) Ⅲ. 三步曲之三:绘制网格模型 完成前两步做好准备工作之后,也就是生成X文件网格和材质和纹理的读取之后,接下来就是把我们准备的内容绘制出来就行了。我们依然是用ID3DXMesh接口的DrawSubset方法绘制网格中的每个子集的。但是由于绘制的部分比较多,对每个部分的绘制,我们都需要专门为其进行材质和纹理的设置,然后才进行绘制,所以一般我们在绘制从X文件读取的三维模型的时候,一般用一个for循环来进行绘制,就像这样: [cpp] view plaincopyprint? 1. g_pd3dDevice->BeginScene(); // 开始绘制 2. 3. //用一个for循环,进行网格各个部分的绘制 4. for(DWORD i = 0; i < g_dwNumMtrls; i++) 5. { 6. g_pd3dDevice->SetMaterial(&g_pMaterials[i]); 7. g_pd3dDevice->SetTexture(0,g_pTextures[i]); 8. g_pMesh->DrawSubset(i); 9. } 10. g_pd3dDevice->EndScene(); // 结束绘制 Ⅳ.总结与升华 做一下总结,从X文件读取模型并进行绘制其实很简单,就三步工作,简明扼要十二个字,三步曲: 加载网格,加载材质纹理,绘制 核心代码如下: [cpp] view plaincopyprint? 1. // 三步曲之一,从X文件中加载网格数据 2. LPD3DXBUFFERpAdjBuffer = NULL; 3. LPD3DXBUFFERpMtrlBuffer = NULL; 4. 5. D3DXLoadMeshFromX(L\,D3DXMESH_MANAGED, g_pd3dDevice, 6. &pAdjBuffer,&pMtrlBuffer, NULL, &g_dwNumMtrls, &g_pMesh); 7. 8. 9. //三步曲之二,读取材质和纹理数据 10. D3DXMATERIAL*pMtrls = (D3DXMATERIAL*)pMtrlBuffer->GetBufferPoint er(); //创建一个D3DXMATERIAL结构体用于读取材质和纹理信息 11. g_pMaterials= new D3DMATERIAL9[g_dwNumMtrls]; 12. g_pTextures = new LPDIRECT3DTEXTURE9[g_dwNumMtrls]; 13. 14. for(DWORD i=0; i 16. //获取材质,并设置一下环境光的颜色值 17. g_pMaterials[i]= pMtrls[i].MatD3D; 18. g_pMaterials[i].Ambient= g_pMaterials[i].Diffuse; 19. 20. //创建一下纹理对象 21. g_pTextures[i] = NULL; 22. D3DXCreateTextureFromFileA(g_pd3dDevice,pMtrls[i].pTextureF ilename, &g_pTextures[i]); 23. } 24. 25. SAFE_RELEASE(pAdjBuffer) 26. SAFE_RELEASE(pMtrlBuffer) 27. 28. void Direct3D_Render(HWND hwnd) 29. { 30. 31. g_pd3dDevice->Clear(0,NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER, D3 DCOLOR_XRGB(100, 100, 100), 1.0f, 0); 32. //三步曲之三,绘制 33. g_pd3dDevice->BeginScene(); // 开始绘制 34. 35. //用一个for循环,进行网格各个部分的绘制 36. for(DWORD i = 0; i < g_dwNumMtrls; i++) 37. { 38. g_pd3dDevice->SetMaterial(&g_pMaterials[i]); 39. g_pd3dDevice->SetTexture(0,g_pTextures[i]); 40. g_pMesh->DrawSubset(i); 41. } 42. g_pd3dDevice->EndScene(); // 结束绘制 43. g_pd3dDevice->Present(NULL, NULL, NULL,NULL); // 翻转与显示 44. 45. } 七、详细注释的配套源代码欣赏 本篇文章的配套源代码依旧是包含四个文件,主要用于公共辅助宏定义的D3DUtil.h,用于封装了DirectInput输入控制API的DirectInputClass.h和DirectInputClass.cpp最后才是核心代码main.cpp。 其实D3DUtil.h,DirectInputClass.h以及DirectInputClass.cpp在上篇文章的配套demo的基础上并没有做任何修改,我们只是修改了main.cpp中的代码而已。但是为了大家的观看方便,浅墨依旧是把这些代码都依次贴出来。 此篇文章中我们的三维模型的素材文件选的是初音的战斗装(本来这次是想选diablo3中diablo的,但是那个看起来太暴力了,就选的这个初音)。如图: