Direct3D学习手记十一:网格二【从.x文件中加载网格】
上篇我们介绍了自己创建网格的方法,此次我们介绍从.x文件中加载3D模型的方法
.x文件:
.x文件是一种存储3D模型数据的一种文本文件,其存储格式有两种:文本方式和二进制方式,
其中文本方式易于查看,.x文件有其特定的格式
.x文件中存储了顶点数据,材质信息和纹理贴图信息等待,可以通过特定的方式读取其中的内容。
从.x文件中加载网格:
HRESULT D3DXLoadMeshFromX( LPCTSTR pFilename, DWORD Options,
LPDIRECT3DDEVICE9 pD3DDevice, LPD3DXBUFFER * ppAdjacency,
LPD3DXBUFFER * ppMaterials, LPD3DXBUFFER * ppEffectInstances,
DWORD * pNumMaterials, LPD3DXMESH * ppMesh);
pFilename为.x文件的名称(注意目录结构)
Options为网格创建标志,有D3DXMESH_MANAGED、D3DXMESH_WRITEONLY、D3DXMESH_32BIT等待
pD3DDevice为设备指针
ppAdjacency保存网格的邻接信息
ppMaterials保存网格的材质信息
ppEffectInstances保存网格的特效信息,暂时不使用置为NULL即可
pNumMaterials保存网格所使用的材质的数目
ppMesh保存创建的网格
例如:
//从.x文件中加载网格
HRESULT hr=E_FAIL;
LPD3DXBUFFER pAdjBuffer=NULL,pMtrlBuffer=NULL;
hr=D3DXLoadMeshFromX(TEXT("Demo_11_Media\\baodao.x"),
D3DXMESH_MANAGED,
g_pd3dDevice,
&pAdjBuffer,
&pMtrlBuffer,
NULL,
&g_dwMtrlNum,
&g_pMesh);
其中LPD3DXBUFFER为ID3DXBuffer接口指针,该类型是一种泛型数据,类型为void,可以通过函数:
LPVOID GetBufferPointer();
得到内存地址,并通过强制类型转换为想要的数据格式,
如存储邻接信息(ppAdjacency)的是DWORD类型的数组可使用如下方式转换:
(DWORD *)pAdjBuffer->GetBufferPointer()
另外,保存材质信息的是D3DXMATERIAL结构类型的数组,该结构如下:
typedef struct D3DXMATERIAL {
D3DMATERIAL9 MatD3D;
LPSTR pTextureFilename;
} D3DXMATERIAL, *LPD3DXMATERIAL;
其中MatD3D是之前介绍过的材质,pTextureFilename指向该材质使用的纹理贴图文件名称字符串(使用时注意目录结构)
可以使用如下方式获得其首地址:
LPD3DXMATERIAL pMtrl=(LPD3DXMATERIAL)pMtrlBuffer->GetBufferPointer();
获取材质和纹理信息:
从.x文件中加载并创建完网格后,就可以读取其中的材质和纹理信息,并保存下来
全局变量用于保存:
std::vector<D3DMATERIAL9> g_vecMaterials(0);//材质
DWORD g_dwMtrlNum=0;//材质数量
std::vector<LPDIRECT3DTEXTURE9> g_vecPTextures(0);//纹理
读取信息:
//获取材质和纹理
CHAR szTextureFile[MAX_PATH];
if(NULL!=pMtrlBuffer && 0!=g_dwMtrlNum)
{
LPD3DXMATERIAL pMtrl=(LPD3DXMATERIAL)pMtrlBuffer->GetBufferPointer();
for(DWORD i=0;i<g_dwMtrlNum;i++)
{
pMtrl[i].MatD3D.Ambient=pMtrl[i].MatD3D.Diffuse;//设置材质对环境光的反射
g_vecMaterials.push_back(pMtrl[i].MatD3D);//添加材质
if(NULL!=pMtrl[i].pTextureFilename)//纹理文件名不为空
{
ZeroMemory(szTextureFile,sizeof(szTextureFile));
sprintf(szTextureFile,"Demo_11_Media\\%s",pMtrl[i].pTextureFilename);
LPDIRECT3DTEXTURE9 pTexture=NULL;
D3DXCreateTextureFromFileA(g_pd3dDevice,szTextureFile,&pTexture);//创建新纹理
g_vecPTextures.push_back(pTexture);//添加纹理
}
else
{
//贴图文件为空,则设置当前纹理为NULL
g_vecPTextures.push_back(NULL);
}
}
}
SAFE_RELEASE(pMtrlBuffer);//释放材质缓存ID3DXBuffer
网格优化:
完成上面的步骤就可以绘制网格了,但是在绘制之前,我们可以对其进行优化来提高绘制效率://对网格进行优化,可有可无
g_pMesh->OptimizeInplace(D3DXMESHOPT_COMPACT//优化标志,移除无用的顶点和索引
|D3DXMESHOPT_ATTRSORT//根据属性ID对三角形图元进行排序
|D3DXMESHOPT_VERTEXCACHE,//提高顶点高速缓存的命中率
(DWORD *)pAdjBuffer->GetBufferPointer(),//指向尚未优化的网格的邻接数组的指针
NULL,//存储优化后的网格的邻接信息
NULL,
NULL);
SAFE_RELEASE(pAdjBuffer);//释放存储邻接信息的缓存
克隆网格:
另外,由于.x文件存储的模型可能顶点结构中不包含顶点法向量,此时要想使用光照效果就必须为其添加顶点法向量,为此我们可以克隆一分网格,使克隆的网格具有顶点法向量,然后通过计算得到其顶点法向量,可使用如下方式完成:
//判断顶点是否具有法向量,可有可无
if(!(g_pMesh->GetFVF() & D3DFVF_NORMAL))
{
//没有法向量,则克隆一分
LPD3DXMESH pTempMesh=NULL;
g_pMesh->CloneMeshFVF(D3DXMESH_MANAGED,
g_pMesh->GetFVF()|D3DFVF_NORMAL,
g_pd3dDevice,
&pTempMesh);//克隆网格,使其具有法向量
if(NULL!=pTempMesh)
{
D3DXComputeNormals(pTempMesh,NULL);//计算顶点法向量
SAFE_RELEASE(g_pMesh);
g_pMesh=pTempMesh;
}
}
绘制网格:
//绘制网格
for(DWORD i=0;i<g_dwMtrlNum;i++)
{
g_pd3dDevice->SetMaterial(&g_vecMaterials[i]);//设置材质
g_pd3dDevice->SetTexture(0,g_vecPTextures[i]);//设置纹理
g_pMesh->DrawSubset(i);//绘制子集
}
释放纹理:
在程序退出时使用Cleanup函数释放资源,注意要释放所有的纹理:
for(DWORD i=0;i<g_dwMtrlNum;i++)
{
SAFE_RELEASE(g_vecPTextures[i]);//释放纹理
}
g_vecPTextures.clear();
g_vecMaterials.clear();
Setup函数:
Setup函数实现了从.x文件创建网格和信息读取的关键步骤
/****************************************************************
*函数名 : Setup
*功能 : 创建与初始化资源、缓存、变换等
*输入 : hWnd:窗口句柄
*输出 : 无
*返回值 : 成功:TRUE 失败:FALSE
****************************************************************/
BOOL Setup(HWND hWnd)
{
if(NULL==hWnd)
return FALSE;
//创建字体
//方式一
D3DXCreateFont(g_pd3dDevice,20,14,600,D3DX_DEFAULT,FALSE,DEFAULT_CHARSET,0,0,0,TEXT("微软雅黑"),&g_pHelpFont);
//方式二
D3DXFONT_DESC fd;
ZeroMemory(&fd,sizeof(D3DXFONT_DESC));
fd.Height=20;
fd.Width=14;
fd.Weight=600;
fd.MipLevels=D3DX_DEFAULT;
fd.Italic=TRUE;
fd.CharSet=DEFAULT_CHARSET;
fd.OutputPrecision=0;
fd.PitchAndFamily=0;
fd.Quality=0;
_tcscpy_s(fd.FaceName,TEXT("Times New Roman"));
D3DXCreateFontIndirect(g_pd3dDevice,&fd,&g_pTipFont);
//从.x文件中加载网格
HRESULT hr=E_FAIL;
LPD3DXBUFFER pAdjBuffer=NULL,pMtrlBuffer=NULL;
hr=D3DXLoadMeshFromX(TEXT("Demo_11_Media\\baodao.x"),
D3DXMESH_MANAGED,
g_pd3dDevice,
&pAdjBuffer,
&pMtrlBuffer,
NULL,
&g_dwMtrlNum,
&g_pMesh);
if(FAILED(hr))
return FALSE;
//获取材质和纹理
CHAR szTextureFile[MAX_PATH];
if(NULL!=pMtrlBuffer && 0!=g_dwMtrlNum)
{
LPD3DXMATERIAL pMtrl=(LPD3DXMATERIAL)pMtrlBuffer->GetBufferPointer();
for(DWORD i=0;i<g_dwMtrlNum;i++)
{
pMtrl[i].MatD3D.Ambient=pMtrl[i].MatD3D.Diffuse;//设置材质对环境光的反射
g_vecMaterials.push_back(pMtrl[i].MatD3D);//添加材质
if(NULL!=pMtrl[i].pTextureFilename)//纹理文件名不为空
{
ZeroMemory(szTextureFile,sizeof(szTextureFile));
sprintf(szTextureFile,"Demo_11_Media\\%s",pMtrl[i].pTextureFilename);
LPDIRECT3DTEXTURE9 pTexture=NULL;
D3DXCreateTextureFromFileA(g_pd3dDevice,szTextureFile,&pTexture);//创建新纹理
g_vecPTextures.push_back(pTexture);//添加纹理
}
else
{
//贴图文件为空,则设置当前纹理为NULL
g_vecPTextures.push_back(NULL);
}
}
}
SAFE_RELEASE(pMtrlBuffer);//释放材质缓存ID3DXBuffer
//对网格进行优化,可有可无
g_pMesh->OptimizeInplace(D3DXMESHOPT_COMPACT//优化标志,移除无用的顶点和索引
|D3DXMESHOPT_ATTRSORT//根据属性ID对三角形图元进行排序
|D3DXMESHOPT_VERTEXCACHE,//提高顶点高速缓存的命中率
(DWORD *)pAdjBuffer->GetBufferPointer(),//指向尚未优化的网格的邻接数组的指针
NULL,//存储优化后的网格的邻接信息
NULL,
NULL);
SAFE_RELEASE(pAdjBuffer);//释放存储邻接信息的缓存
//判断顶点是否具有法向量,可有可无
if(!(g_pMesh->GetFVF() & D3DFVF_NORMAL))
{
//没有法向量,则克隆一分
LPD3DXMESH pTempMesh=NULL;
g_pMesh->CloneMeshFVF(D3DXMESH_MANAGED,
g_pMesh->GetFVF()|D3DFVF_NORMAL,
g_pd3dDevice,
&pTempMesh);//克隆网格,使其具有法向量
if(NULL!=pTempMesh)
{
D3DXComputeNormals(pTempMesh,NULL);//计算顶点法向量
SAFE_RELEASE(g_pMesh);
g_pMesh=pTempMesh;
}
}
//设置取景变换矩阵
D3DXMATRIX matView;
D3DXVECTOR3 vEye(0.0f,0.0f,-300.0f);
D3DXVECTOR3 vAt(0.0f,0.0f,0.0f);
D3DXVECTOR3 vUp(0.0f,1.0f,0.0f);
D3DXMatrixLookAtLH(&matView,&vEye,&vAt,&vUp);
g_pd3dDevice->SetTransform(D3DTS_VIEW,&matView);
//设置投影变换矩阵
D3DXMATRIX matProjection;
::D3DXMatrixPerspectiveFovLH(&matProjection,D3DX_PI/4.0F,(FLOAT)g_nWidth/(FLOAT)g_nHeight,1.0F,1000.0F);
g_pd3dDevice->SetTransform(D3DTS_PROJECTION,&matProjection);
//设置光照
SetLight(2,g_pd3dDevice);
//设置纹理过滤方式
g_pd3dDevice->SetSamplerState(0,D3DSAMP_MAGFILTER,D3DTEXF_LINEAR);
g_pd3dDevice->SetSamplerState(0,D3DSAMP_MINFILTER,D3DTEXF_LINEAR);
g_pd3dDevice->SetSamplerState(0,D3DSAMP_MIPFILTER,D3DTEXF_LINEAR);
//显示窗口
ShowWindow(hWnd,SW_SHOW);
UpdateWindow(hWnd);
return TRUE;
}