第八集 Mesh
构造简单的球的3D模型已经复杂化了, 如果用代码构造比球更复杂的3D模型就更难了, 还好有专业级的3D模型设计软件, 这些软件构造的3D模型在DirectX Graphics中称为Mesh, DirectX Graphics中对应的接口为,
ID3DXBaseMesh
| - ID3DXMesh
| - ID3DXPMesh
ID3DXSPMesh
ID3DXPatchMesh
可以在DirectX9c SDK中的Mesh Support in D3DX主题中找到有关Mesh的描述.
8.1 ID3DXMesh中的信息
8.1.1 顶点和顶点索引
Mesh中包含的物体模型实际还是由顶点及顶点索引组成的, Mesh只是起把顶点, 顶点索引, 纹理属性, 材质属性包装在一起的作用, 简单资源的统一管理.
我们可以类似创建顶点一样来创建Mesh, DirectX Graphics提供的函数为,
HRESULT D3DXCreateMesh(DWORD NumFaces,
DWORD NumVertices,
DWORD Options,
CONST LPD3DVERTEXELEMENT9 * pDeclaration,
LPDIRECT3DDEVICE9 pD3DDevice,
LPD3DXMESH * ppMesh);
HRESULT D3DXCreateMeshFVF(DWORD NumFaces,
DWORD NumVertices,
DWORD Options,
DWORD FVF,
LPDIRECT3DDEVICE9 pD3DDevice,
LPD3DXMESH * ppMesh);
其中D3DXCreateMeshFVF函数比较实用, 创建一个长方体的代码为,
// #define D3DFVF_MYVERTEX (D3DFVF_XYZ | D3DFVF_DIFFUSE)
// LPDIRECT3DDEVICE9
m_pD3DDev;
// LPD3DXMESH
m_pMesh;
D3DXCreateMeshFVF(12, 8, D3DXMESH_MANAGED,
D3DFVF_MYVERTEX, m_pD3DDev, &m_pMesh)
Directx Graphics 只是根据参数申请相应的内存,
长方体的顶点值和顶点索引还是要由我们自己去填写, 在Mesh中包含以下函数,
HRESULT LockVertexBuffer(DWORD Flags, LPVOID * ppData);
HRESULT UnlockVertexBuffer();
HRESULT LockIndexBuffer(DWORD Flags, LPVOID * ppData);
HRESULT UnlockIndexBuffer();
我们填写长方体的顶点值和顶点索引,
INT nSize =
sizeof(MYVERTEX) * 8;
MYVERTEX aVertex[ ] =
{
{-1.0f, -1.0f, -1.0f, D3DCOLOR_ARGB(0, 255, 0, 0
) },
{-1.0f, 1.0f, -1.0f, D3DCOLOR_ARGB(0, 0,
0, 255) },
{ 1.0f, 1.0f, -1.0f, D3DCOLOR_ARGB(0, 0,
255, 0 ) },
{ 1.0f, -1.0f, -1.0f, D3DCOLOR_ARGB(0, 0, 0,
255) },
{-1.0f, -1.0f, 1.0f, D3DCOLOR_ARGB(0, 0,
255, 0 ) },
{-1.0f, 1.0f, 1.0f, D3DCOLOR_ARGB(0, 0,
0, 255) },
{ 1.0f, 1.0f, 1.0f, D3DCOLOR_ARGB(0, 0,
255, 0 ) },
{ 1.0f, -1.0f, 1.0f, D3DCOLOR_ARGB(0, 0,
0, 255) }
};
LPVOID pV = NULL;
if (FAILED(m_pMesh->LockVertexBuffer(0, &pV)))
{
return E_FAIL;
}
MoveMemory(pV, aVertex, nSize);
m_pMesh->UnlockVertexBuffer();
nSize =
sizeof(WORD) * 36;
WORD aIndex[] =
{
0, 1, 2,
2, 3, 0,
4, 7, 6,
6, 5, 4,
0, 3, 7,
7, 4, 0,
3, 2, 6,
6, 7, 3,
2, 1, 5,
5, 6, 2,
1, 0, 4,
4, 5, 1
};
if (FAILED(m_pMesh->LockIndexBuffer(0, &pV)))
{
return E_FAIL;
}
MoveMemory(pV, aIndex, nSize);
m_pMesh->UnlockIndexBuffer();
DirectX Graphics中提供了实用的基本模型创建函数,
HRESULT D3DXCreateBox(LPDIRECT3DDEVICE9 pDevice,
FLOAT Width,
FLOAT Height,
FLOAT Depth,
LPD3DXMESH * ppMesh,
LPD3DXBUFFER * ppAdjacency);
HRESULT D3DXCreateCylinder(LPDIRECT3DDEVICE9 pDevice,
FLOAT Radius1,
FLOAT Radius2,
FLOAT Length,
UINT Slices,
UINT Stacks,
LPD3DXMESH * ppMesh,
LPD3DXBUFFER * ppAdjacency);
HRESULT D3DXCreateSphere(LPDIRECT3DDEVICE9 pDevice,
FLOAT Radius,
UINT Slices,
UINT Stacks,
LPD3DXMESH * ppMesh,
LPD3DXBUFFER * ppAdjacency);
HRESULT D3DXCreateTeapot(LPDIRECT3DDEVICE9 pDevice,
LPD3DXMESH * ppMesh,
LPD3DXBUFFER * ppAdjacency);
HRESULT D3DXCreateTorus(LPDIRECT3DDEVICE9 pDevice,
FLOAT InnerRadius,
FLOAT OuterRadius,
UINT Sides,
UINT Rings,
LPD3DXMESH * ppMesh,
LPD3DXBUFFER * ppAdjacency);
注意这些函数创建的模型的中心轴是Z轴.
//
宽3.0,
高2.0, 厚1.0的长方体
D3DXCreateBox(m_pD3DDev, 3.0, 2.0, 1.0, &m_pMeshBox, NULL)
//
被分成16块的圆台,
上半径1.0, 底半径2.0, 高4.0,
圆台被分为2层
//
这个函数可创建棱台,
棱锥, 圆台, 圆锥...
D3DXCreateCylinder(m_pD3DDev, 1.0, 2.0, 4.0, 16, 2, &m_pMeshCylinder, NULL)
//
球模型, 上集有描述
D3DXCreateSphere(m_pD3DDev, 2.0, 8, 8, &m_pMeshSphere, NULL)
//
茶壶,
想不通DX和OpenGL都喜欢这东西?
D3DXCreateTeapot(m_pD3DDev, &m_pMeshTeapot, NULL)
//
甜甜圈
D3DXCreateTorus(m_pD3DDev, 1.0, 2.0, 8, 8, &m_pMeshTorus, NULL)
渲染模型时, 只需调用Mesh的DrawSubset函数就可以了,
m_pMeshBox->DrawSubset(0);
m_pMeshCylinder->DrawSubset(0);
m_pMeshSphere->DrawSubset(0);
m_pMeshTeapot->DrawSubset(0);
m_pMeshTorus->DrawSubset(0);
m_pMesh->DrawSubset(0);
8.1.2 属性表及Mesh的优化
Mesh可以包含模型的表面材质和纹理属性, 模型的不同部分的表面材质, 纹理属性或渲染状态可能是不同的, 于是Mesh的内部根据模型的材质和纹理属性将组成模型的三角形分成不同的子集(subset), 在同一子集中的三角形的表面材质, 纹理或渲染状态是相同的, 每个子集都有自己的属性描述, 将这些属性描述合在一起就是属性表了,属性表中包含的元素的数量对应的就是Mesh中子集的数量.
Attribute Buffer
Index Buffer Texture & Material Buffer
[
0 ]---------+--[ 0
]--+-----------[ 0 ]---+
[
1 ]----+ |
[ 1 ]
| +------[ 1
] |
[
2 ]-+
| | [
... ] |
| [ ...
] |
[
... ] | |
+--[ 7 ]--+
| |
| +-------[ 8
]-------+ |
| | [
... ] |
|
| +-------[ 15
]-------+ |
+----------[ 16
]------------------------------+
| { ...
] |
+----------[ 23 ]------------------------------+
[ ...
]
图8.1
在图8.1中, Mesh中共有3个subset,其中subset0 和 subset2 使用相同的表面材质和纹理, 那么是否可以通过重新定义顶点索引把这两个subset合并呢? --- Mesh的优化.
Mesh优化的主要目的是为了渲染时更有效率, 可以根据模型的特点选择优化的方式,
HRESULT Optimize(DWORD Flags,
CONST DWORD * pAdjacencyIn,
DWORD * pAdjacencyOut,
DWORD * pFaceRemap,
LPD3DXBUFFER * ppVertexRemap,
LPD3DXMESH * ppOptMesh);
HRESULT OptimizeInplace(DWORD Flags,
CONST DWORD * pAdjacencyIn,
DWORD * pAdjacencyOut,
DWORD * pFaceRemap,
LPD3DXBUFFER * ppVertexRemap);
参考DirectX9c SDK中对两个函数的详细说明. 如果把图8.1中的subset0 和 subset2合并, Flags参数中需要包含D3DXMESHOPT_ATTRSORT.
现在我们把前面创建的长方体分成2个subset,
在渲染时使用不同的明暗处理,
DWORD dwSize = 0L;
LPD3DXATTRIBUTERANGE pRange =
new D3DXATTRIBUTERANGE[2];
if (pRange != NULL)
{
m_pMesh->GetAttributeTable(pRange, &dwSize);
// 看看修改前的Mesh的属性表内容...
pRange[0].AttribId = 0;
pRange[0].FaceStart = 0;
pRange[0].FaceCount = 6;
pRange[0].VertexStart = 0;
pRange[0].VertexCount = 8;
pRange[1].AttribId = 1;
pRange[1].FaceStart = 6;
pRange[1].FaceCount = 6;
pRange[1].VertexStart = 0;
pRange[1].VertexCount = 8;
m_pMesh->SetAttributeTable(pRange, 2);
delete[] pRange;
}
渲染模型时,调用Mesh的DrawSubset函数, DrawSubset函数的参数就是Mesh中子集的序号
m_pD3DDev->SetRenderState(D3DRS_SHADEMODE, D3DSHADE_FLAT);
m_pMesh->DrawSubset(0);
m_pD3DDev->SetRenderState(D3DRS_SHADEMODE, D3DSHADE_GOURAUD);
m_pMesh->DrawSubset(1);
8.2 ID3DXMesh的存储及读取
8.2.1 Mesh的存储
3D建模软件在存储模型时使用的存储格式是不相同的, DirectX Graphics只支持自己的存储文件格式X file, 可以参考DirectX9c SDK中X File Format Reference主题.现在把前面创建的所有模型都存成文件, 需要的函数是,
HRESULT D3DXSaveMeshToX(LPCTSTR pFilename,
LPD3DXMESH pMesh,
CONST DWORD * pAdjacency,
CONST D3DXMATERIAL * pMaterials,
CONST D3DXEFFECTINSTANCE * pEffectInstances,
DWORD NumMaterials,
DWORD Format);
D3DXSaveMeshToX(_T(".//1.x"), m_pMeshBox, NULL, NULL, NULL, 0, D3DXF_FILEFORMAT_TEXT);
D3DXSaveMeshToX(_T(".//2.x"), m_pMeshCylinder, NULL, NULL, NULL, 0, D3DXF_FILEFORMAT_TEXT);
D3DXSaveMeshToX(_T(".//3.x"), m_pMeshSphere, NULL, NULL, NULL, 0, D3DXF_FILEFORMAT_TEXT);
D3DXSaveMeshToX(_T(".//4.x"), m_pMeshTeapot, NULL, NULL, NULL, 0, D3DXF_FILEFORMAT_TEXT);
D3DXSaveMeshToX(_T(".//5.x"), m_pMeshTorus, NULL, NULL, NULL, 0, D3DXF_FILEFORMAT_TEXT);
D3DXSaveMeshToX(_T(".//6.x"), m_pMesh, NULL, NULL, NULL, 0, D3DXF_FILEFORMAT_TEXT);
8.2.2 Mesh的读取
从X文件读取数据, 创建相应的Mesh是最常见的方式, 也可以从可执行文件中的资源中读取X文件数据, 另外象Blizzard的游戏总把N多资源放在同一文件中, 运行时只需load这个文件, 这时我们是从内存中读取X文件数据的.
HRESULT D3DXLoadMeshFromX(LPCTSTR pFilename,
DWORD Options,
LPDIRECT3DDEVICE9 pD3DDevice,
LPD3DXBUFFER * ppAdjacency,
LPD3DXBUFFER * ppMaterials,
LPD3DXBUFFER * ppEffectInstances,
DWORD * pNumMaterials,
LPD3DXMESH * ppMesh);
HRESULT D3DXLoadMeshFromXResource(HMODULE Module,
LPCSTR Name,
LPCSTR Type,
DWORD Options,
LPDIRECT3DDEVICE9 pD3DDevice,