这样存储的好处很多,主要的就是如果想为模型顶点加上比如切线(tangent)这样的数据时非常的方便高效,不需要遍历顶点结构体挨个按位置进行数组元素插入,只需要追加一个数组到数据中即可,使用时按通道传输数据即可。
本例中,使用Assimp导入数据后,继续简单定义了如下结构体,来作为转换数据结构:
// 模型中子网格的顶点偏移等信息
struct ST\_GRS\_SUBMESH\_DATA
{
UINT m_nNumIndices;
UINT m_nBaseVertex;
UINT m_nBaseIndex;
UINT m_nMaterialIndex;
};
typedef CAtlArray<ST_GRS_SUBMESH_DATA> CGRSSubMesh;
const UINT g_ncSlotCnt = 4; // 用4个插槽上传顶点数据
struct ST\_GRS\_MESH\_DATA
{
const aiScene\* m_paiModel;
CStringA m_strFileName;
XMMATRIX m_mxModel;
CGRSSubMesh m_arSubMeshInfo;
CGRSARPositions m_arPositions;
CGRSARNormals m_arNormals;
CGRSARTexCoords m_arTexCoords;
CGRSARVertexBones m_arBoneIndices;
CGRSARIndices m_arIndices;
CGRSMapString2UINT m_mapTextrueName2Index;
CGRSMapUINT2UINT m_mapTextureIndex2HeapIndex;
CGRSARBoneDatas m_arBoneDatas;
CGRSMapString2UINT m_mapName2Bone; //名称->骨骼的索引
CGRSMapString2UINT m_mapAnimName2Index; //名称->动画的索引
UINT m_nCurrentAnimIndex; // 当前播放的动画序列索引(当前动作)
};
这两个结构体定义的总体思路是将一个模型文件中所有的子网格顶点数据都分组连续存储在几个数组中(CGRSARPositions m_arPositions等5个数组),并且用ST_GRS_SUBMESH_DATA结构体数组来对应每个子网格数据在数组中的起始位置和长度信息(CGRSSubMesh m_arSubMeshInfo;中存储)。
另外用两个映射(map:CGRSMapString2UINT m_mapTextrueName2Index;CGRSMapUINT2UINT m_mapTextureIndex2HeapIndex;)来标识出每个子网格对应的纹理索引以及对应描述符索引。
有了结构体,下一步就是组织代码把数据稍作转换,变换到我们的结构体中,这正好就是一开始我们说的LoadMesh函数做的事情:
BOOL LoadMesh(LPCSTR pszFileName, ST_GRS_MESH_DATA& stMeshData)
{
stMeshData.m_nCurrentAnimIndex = 0;
stMeshData.m_paiModel = g_aiImporter.ReadFile(pszFileName, ASSIMP_LOAD_FLAGS);
if (nullptr == stMeshData.m_paiModel)
{
ATLTRACE("无法解析文件(%s):%s (%d)\n", pszFileName, g_aiImporter.GetErrorString(), ::GetLastError());
return FALSE;
}
// 获取根节点的变换矩阵,其实就是 Module->World 的变换矩阵
stMeshData.m_mxModel = XMMatrixTranspose(MXEqual(stMeshData.m_mxModel, stMeshData.m_paiModel->mRootNode->mTransformation));
// 获取网格数量
UINT nMeshCnt = stMeshData.m_paiModel->mNumMeshes;
if ( 0 == nMeshCnt )
{
ATLTRACE("文件(%s)中没有网格数据!\n", pszFileName);
return FALSE;
}
const aiMesh\* paiSubMesh = nullptr;
const aiVector3D Zero3D(0.0f, 0.0f, 0.0f);
UINT nNumBones = 0;
UINT nNumVertices = 0;
UINT nNumIndices = 0;
stMeshData.m_arSubMeshInfo.SetCount(nMeshCnt);
// 加载Vertex基本信息
for (UINT i = 0; i < nMeshCnt; i++)
{
paiSubMesh = stMeshData.m_paiModel->mMeshes[i];
stMeshData.m_arSubMeshInfo[i].m_nMaterialIndex = paiSubMesh->mMaterialIndex;
stMeshData.m_arSubMeshInfo[i].m_nNumIndices = paiSubMesh->mNumFaces \* GRS_INDICES_PER_FACE;
stMeshData.m_arSubMeshInfo[i].m_nBaseVertex = nNumVertices;
stMeshData.m_arSubMeshInfo[i].m_nBaseIndex = nNumIndices;
// 当前Mesh的顶点数量和索引数量累加后,就是下个Mesh顶点和索引在整体缓冲中的索引开始位置