使用网格模型最普遍的方式是从外部的3D 模型文件中加载一个网格。而这些3D 模型通常都是由3D 建模软件生成的,比较复杂的网格数据。目前市面上主流的3D 建模软件有3DS Max 和Maya。而目前流行的3D 模型文件格式有.3ds 、.max 、.obj 以及.mb 。其中.3ds 、. max 为3DS Max常用的格式, .mb 为Maya 常用的格式, 而.obj 为3DSMax 和Maya 通用的文件格式。
HRESULT D3DXCreateMesh( __in DWORD NumFaces, __in DWORD NumVertices, __in DWORD Options, __in const LPD3DVERTEXELEMENT9 *pDeclaration, __in LPDIRECT3DDEVICE9 pD3DDevice, __out LPD3DXMESH *ppMesh );这个函数的参数说明如下。
typedef enum D3DXMESH { D3DXMESH_32BIT = 0x001, D3DXMESH_DOnOTCLIP= 0x002, D3DXMESH_POINTS = 0x004, D3DXMESH_RTPATCHES = 0x008, D3DXMESH_NPATCHES = 0x4000, D3DXMESH_VB_SYSTEMMEM = 0x010, D3DXMESH_VB_MANAGED = 0x020, D3DXMESH_VB_WRITEOnLY= 0x040, D3DXMESH_VB_DYNAMIC = 0x080, D3DXMESH_VB_SOFTWAREPROCESSING = 0x8000, D3DXMESH_IB_SYSTEMMEM = 0x100, D3DXMESH_IB_MANAGED = 0x200, D3DXMESH_IB_WRITEOnLY= 0x400, D3DXMESH_IB_DYNAMIC = 0x800, D3DXMESH_IB_SOFTWAREPROCESSING = 0x10000, D3DXMESH_VB_SHARE = 0x1000, D3DXMESH_USEHWOnLY= 0x2000, D3DXMESH_SYSTEMMEM = 0x110, D3DXMESH_MANAGED = 0x220, D3DXMESH_WRITEOnLY= 0x440, D3DXMESH_DYNAMIC = 0x880, D3DXMESH_SOFTWAREPROCESSING = 0x18000 } D3DXMESH, *LPD3DXMESH;一般情况下,我们都把这个Options 取为D3DXMESH_ SYSTEMMEM 或者D3DXMESH_MANAGED , 表示对Direct3D 顶点缓冲区和索引缓冲区使用D3DPOOL_SYSTEMMEM 或者D3DPOOL_MANAGED 内存。
HRESULT D3DXLoadMeshFromX( __in LPCTSTR pFilename, __in DWORD Options, __in LPDIRECT3DDEVICE9 pD3DDevice, __out LPD3DXBUFFER *ppAdjacency, __out LPD3DXBUFFER *ppMaterials, __out LPD3DXBUFFER *ppEffectInstances, __out DWORD *pNumMaterials, __out LPD3DXMESH *ppMesh );
LPVOID GetBufferPointer(); SIZE_T GetBufferSize();没错,这就是原型声明,因为这两个函数都没有参数,所以它们的身子显得非常单薄。
// 读取材质和纹理数据 D3DXMATERIAL *pMtrls = (D3DXMATERIAL*)pMtrlBuffer->GetBufferPointer();
typedef struct D3DXMATERIAL { D3DMATERIAL9 MatD3D; LPSTR pTextureFilename; } D3DXMATERIAL, *LPD3DXMATERIAL;当我们加载X 文件后,需要遍历整个D3DXMATERIAL 结构类型的数组,用于取出保存在ID3DXBuffer 接口对象中的材质信息。由于X 文件中并未存储具体的纹理数据, 它只包含纹理贴图的文件名,因此需要我们自己根据该文件名创建相应的纹理对象。
// 从X文件中加载网格数据 LPD3DXBUFFER pAdjBuffer = NULL; LPD3DXBUFFER pMtrlBuffer = NULL; D3DXLoadMeshFromX(L"miki.X", D3DXMESH_MANAGED, g_pd3dDevice, &pAdjBuffer, &pMtrlBuffer, NULL, &g_dwNumMtrls, &g_pMesh); // 读取材质和纹理数据 D3DXMATERIAL *pMtrls = (D3DXMATERIAL*)pMtrlBuffer->GetBufferPointer(); //创建一个D3DXMATERIAL结构体用于读取材质和纹理信息 g_pMaterials = new D3DMATERIAL9[g_dwNumMtrls]; g_pTextures = new LPDIRECT3DTEXTURE9[g_dwNumMtrls]; for (DWORD i=0; i17.6.3 三步曲之三:绘制网格模型
完成前两步做好准备工作之后,也就是生成X文件网格以及材质和纹理的读取之后,接下来就是把我们准备的内容绘制出来就行了。我们依然是用ID3DXMesh 接口的DrawSubset 方法绘制网格中的每个子集的。但是由于绘制的部分比较多,对每个部分的绘制,我们都需要专门为其进行材质和纹理的设置,然后才进行绘制,所以一般我们在绘制从X 文件读取的三维模型的时候, 一般用一个for 循环来进行绘制,就像这样:
g_pd3dDevice->BeginScene(); // 开始绘制 // 用一个for循环,进行网格各个部分的绘制 for (DWORD i = 0; iSetMaterial(&g_pMaterials[i]); g_pd3dDevice->SetTexture(0, g_pTextures[i]); g_pMesh->DrawSubset(i); } g_pd3dDevice->EndScene(); // 结束绘制
// Desc : 从绝对路径中提取纹理文件名 //------------------------------------------------------------------------ voi d RemovePathFromFileName(LPSTR fullPath , LPWSTR fileName ) { //先将full Path 的类型变换为LPWSTR WCHAR wszBuf [MAX_PATH] ; MultiByteToWideChar ( CP_ACP, 0, fullPath , -1, wszBuf, MAX_PATH ) ; wszBuf[MAX PATH - 1] = L'\0'; WCHAR* wszFullPath = wszBuf ; //从绝对路径中提取文件名 LPWSTR pch=wc srchr (wszFullPath, ’\\’ ); if (pch) lstrcpy (fileName, ++pch) ; else lstrcpy (fileName, wszFullPath) ; }
//三部曲之一:从X文件中加载网格数据 LPD3DXBUFFER pAdjBuffer = NULL; LPD3DXBUFFER pMtrlBuffer = NULL; D3DXLoadMeshFromX(L"miki.X", D3DXMESH_MANAGED, g_pd3dDevice, &pAdjBuffer, &pMtrlBuffer, NULL, &g_dwNumMtrls, &g_pMesh); //三部曲之二: 读取材质和纹理数据 D3DXMATERIAL *pMtrls = (D3DXMATERIAL*)pMtrlBuffer->GetBufferPointer(); //创建一个D3DXMATERIAL结构体用于读取材质和纹理信息 g_pMaterials = new D3DMATERIAL9[g_dwNumMtrls]; g_pTextures = new LPDIRECT3DTEXTURE9[g_dwNumMtrls]; for (DWORD i=0; i另外, 在载入模型三步曲之二中,这句g_pMaterials[i].Ambient = g_pMaterials[i].Diffuse 是用于将材质对漫反射光的反应程度赋值给材质的环境光反应程度。这句加上和不加上渲染出来的模型会有不同的环境光效果,大家可以自己把这句注释起来重新编译运行一下。
void Direct3D_Render(HWND hwnd) { g_pd3dDevice->Clear(0, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(150, 150, 100), 1.0f, 0); // 三部曲之三:绘制 g_pd3dDevice->BeginScene(); // 开始绘制 // 用一个for循环,进行网格各个部分的绘制 for (DWORD i = 0; iSetMaterial(&g_pMaterials[i]); g_pd3dDevice->SetTexture(0, g_pTextures[i]); g_pMesh->DrawSubset(i); } g_pd3dDevice->EndScene(); // 结束绘制 g_pd3dDevice->Present(NULL, NULL, NULL, NULL); // 翻转与显示 } 17.7 示例程序D3demo12
这个程序的核心代码,其实重点部分在前面的X 文件模型载入三步曲核心代码中已经贴出过,这里只需要了解它们是放在哪里的就可以了。
运行这个程序,我们便会得到如下的效果, 一个高质量的初音模型,非常精致:
17.8 章节小憩
学完精彩绝伦的这章,我们从对3D 建模一无所知到了解了3DS Max 和Maya 这两大三维建模软件的威力,并可以熟练地从3DS Max 中导出帅气的人物模型成X 文件,供我们写的程序所用,其可谓是一趟奇幻的旅程。