经过前面两讲的内容,读者可以做到的是将.mesh文件转变为.xml文件,可以用浏览器打开查看其内容树结构,通过OgreXmlConverter的XmlDocument,XmlElement将.xml文件读取到Xml树结构中.XmlDocument继承自XmlNode,在这颗树里保存了我们的模型数据,获取数据只要调用XmlDocument::RootElement就可以递归整个树了,下面我们Ogre的类MeshSerializer就要粉末登场了,通过它你可以把XmlDocument里的数据转变到Mesh里,转变完成后,就可以通过渲染系统把他渲染出来了,当然内容比较庞大,还需要很多讲.
下面进入MeshSerializer:
通过结构图,可以看到,整个树的根是一个<mesh>节点,这个节点由<submeshes>和<skeletonlink>组成,<submeshes>由若干<submesh>组成,<submesh>内部是顶点数据面数据,骨骼数据。理解清楚结构,就容易理解Ogre的Mesh类,Mesh类内部有一个SubMesh类的vector<SubMesh*>用来存储Sinbad身体的每一部分,比如Eyes,Body等等。接下来,进入MeshSerializer::Import
void MeshSerializer::Import(const char* filename,Mesh* pMesh) { //submeshes 读取Submeshes const XmlElement* elem=rootElem->FirstChildElement("submeshes"); if(elem) ReadSubMeshes(elem); }
void MeshSerializer::ReadSubMeshes(const XmlElement* mElem) { for(const XmlElement* smElem=mElem->FirstChildElement();smElem;smElem=smElem->NextSiblingElement()){ SubMesh* sm=m_pMesh->CreateSubMesh(); //material const char* material=smElem->Attribute("material"); if(material) sm->SetMaterialName(string(material)); //usesharedvertices const char* useSharedVertices=smElem->Attribute("usesharedvertices"); if(useSharedVertices) sm->m_bUseSharedVertices=StringConverter::ParseBool(useSharedVertices); //operation type bool bReadFaces=true;//判断是否需要读取面信息 const char* operType=smElem->Attribute("operationtype"); if(operType){ if(!strcmp(operType,"triangle_list")) sm->m_OperationType=RenderOperation::OT_TRIANGLE_LIST; } //index bits 16 or 32 bool bUse32bitIndex=false; const char* tmp=smElem->Attribute("use32bitindexes"); if(tmp) bUse32bitIndex=StringConverter::ParseBool(tmp); //Faces if(bReadFaces){ //判断一下面的数量是否正确 int actualCount=0; const XmlElement* faces=smElem->FirstChildElement("faces"); if(faces){ for(const XmlElement* face=faces->FirstChildElement();face;face=face->NextSiblingElement()) ++actualCount; const char* claimCount=faces->Attribute("count"); if(claimCount){ int iClaimCount=StringConverter::ParseInt(claimCount); if(actualCount != iClaimCount){ LogManager::GetSingleton().LogMessage("ReadSubMeshes faces actualCount!=iClaimCount"); return ; } } //把数据存入索引数据块内 if(actualCount>0){ switch(sm->m_OperationType){ case RenderOperation::OT_TRIANGLE_LIST: sm->m_pIndexData->m_IndexCount=3*actualCount; break; } } } //allocate space HardwareIndexBufferSharedPtr ptrIndexBuffer=HardwareBufferManager::GetSingleton().CreateIndexBuffer( bUse32bitIndex?HardwareIndexBuffer::IT_32BIT:HardwareIndexBuffer::IT_16BIT,sm->m_pIndexData->m_IndexCount,HardwareBuffer::HBU_DYNAMIC); sm->m_pIndexData->m_IndexBufferPtr=ptrIndexBuffer; //将索引数据装入已分配的内存 unsigned int *pInt=0; unsigned short *pShort=0; if(bUse32bitIndex){ pInt=static_cast<unsigned int*>(ptrIndexBuffer->Lock(HardwareBuffer::HBL_DISCARD)); }else{ pShort=static_cast<unsigned short*>(ptrIndexBuffer->Lock(HardwareBuffer::HBL_DISCARD)); } for(const XmlElement* face=faces->FirstChildElement();face;face=face->NextSiblingElement()){ if(bUse32bitIndex){ *(pInt++)=StringConverter::ParseInt(face->Attribute("v1")); *(pInt++)=StringConverter::ParseInt(face->Attribute("v2")); *(pInt++)=StringConverter::ParseInt(face->Attribute("v3")); }else{ *(pShort++)=StringConverter::ParseInt(face->Attribute("v1")); *(pShort++)=StringConverter::ParseInt(face->Attribute("v2")); *(pShort++)=StringConverter::ParseInt(face->Attribute("v3")); } } ptrIndexBuffer->Unlock(); } //geometry if(!sm->m_bUseSharedVertices){ const XmlElement* geoElem=smElem->FirstChildElement("geometry"); if(geoElem) ReadGeometry(geoElem,sm->m_pVertexData); } //bone assignments const XmlElement* boneAssignElem = smElem->FirstChildElement("boneassignments"); if(boneAssignElem) ReadBoneAssignments(boneAssignElem,sm); } }
可以看到,ReadSubmeshes里循环读取每一个SubMesh,读取顺序和上方的文件内容对应,faces,geometry(暂不考虑boneassignment),faces就是顶点索引数据,当然你必须熟练DirectX或者OpenGL,否则就不要学Ogre了,geometry就是顶点相关数据,法向量,纹理数据。可能现在你对上面的函数唯一感觉比较吃力的就是HardwareBuffer了,没关系,先理清读取流程,ReadBoneAssignments先不要去看,多看几遍ReadGeometry内部的实现,下一讲讲解HardwareBuffer的内容,敬请期待,作者新浪博客:http://weibo.com/1012294255/profile?topnav=1&wvr=6