1. POD文件基本结构和读取方法
POD是powvr sdk提供的一种场景文件,包含了scene tree常用的对象。
POD是二进制文件,通过导出插件PVRGenPOD或者Collada2POD工具从dae转换得到。通过分析学习POD格式以及pvr sdk装载POD的代码,可以了解3d引擎场景模型动画文件的一种二进制实现方式,作为自己设计文件格式的参考。并且POD格式特别针对Imagination自家的GPU做了优化,值得研究。
POD文件按照block的方式组织,block可以嵌套block。每个block由一对marker包裹。marker包含两个32位值。marker分为start marker和end marker。
marker的第一个32位值是block name, 如果是end marker,name的最高位(bit31)置1。这个name是一个整形常量值,在sdk里面有定义。
marker的第二个32位值是block size。如果是end marker,这个值就为0。
POD文件的读取就是从读取start block开始,根据相应的name嵌套读取。处理完一个block循环处理下一个。如果没有子block, end marker可只读不处理。如果有子block,end marker就可以作为该block处理完毕的标志,从而让读取逻辑返回到上一个层次处理。例如,读取scene的子block light时,在读取代码中:
case ePODFileLight | PVRTMODELPOD_TAG_END: return true;
block在POD文件中会按照一定的顺序出现,基本的信息和必须要先知道的内容会在前面出现,通过跟踪读取代码,第一个block是ePODFileVersion,用于验证是否是一个合法的POD文件,第二个block是ePODFileExpOpt,包含选项,正常读取scene时不读取。
第三个block就是ePODFileScene。读取这个block需要嵌套读取其内部的子block。如:ePODFileColourBackground, ePODFileColourAmbient,ePODFileNumCamera等。具体见sdk的PVRTModelPOD.cpp中的ReadScene函数。
文件读取方式上,SDK采取了一次性将文件所有内容读取到内存buffer中,然后通过移动指针访问的方式,如果生成的对象需要内存空间则在读取时分配,最后全部读取完毕后再释放整个buffer。这种方式只进行一次文件io,速度较快,只是需要多占用内存。但只要读取时内存不到峰值影响不大,总体来说这种方式还是不错的。其实sdk设计的很灵活,文件读取和释放函数都可以由使用者传入。文件读取的接口使用CSource接口。具体实现使用了CSourceStream。CSourceStream又使用了CPVRTResourceFile,支持文件读取和memroy读取。
2. POD 文件层block
读取POD的第一个层次,和文件验证历史选项等有关,暂且称为文件层,包含如下block:
ePODFileVersion = 1000,
ePODFileScene,
ePODFileExpOpt,
ePODFileHistory,
ePODFileEndiannessMisMatch = -402456576,
注意ePODFileEndiannessMisMatch 是用来检测平台的endianness是否和POD文件兼容,这不是一个block。其实他是1000(0x03E8)的32位big endian值:0xE8030000。
在一些引擎里面如果endianness不兼容,可能不做任何处理,可能会做swap。PVR SDK采取一个中间路线。
2. ePODFileScene的内容
POD是按照常用的scene tree的方式组织scene的,从SDK可以看到ePODFileScene block中包含以下子block:
ePODFileColourBackground = 2000,
ePODFileColourAmbient,
ePODFileNumCamera,
ePODFileNumLight,
ePODFileNumMesh,
ePODFileNumNode,
ePODFileNumMeshNode,
ePODFileNumTexture,
ePODFileNumMaterial,
ePODFileNumFrame,
ePODFileCamera, // Will come multiple times
ePODFileLight, // Will come multiple times
ePODFileMesh, // Will come multiple times
ePODFileNode, // Will come multiple times
ePODFileTexture, // Will come multiple times
ePODFileMaterial, // Will come multiple times
ePODFileFlags,
ePODFileFPS,
ePODFileUserData,
其中camera, light, mesh, node, texture, material这些block都可能有多个。注意同类型的多个block并没有再增加一个层次,而是和其他类型block在同一个层次,因此他们的顺序都不一定需要连续,只不过总数量必须和NumXXX的block指定的相符合。并且NumXXX的这些block在文件中的位置也是在前面。
3. Mesh的结构
sdk里面的SPODMesh结构包含了mesh,skinmesh以及法线贴图需要的所有顶点数据。对应的block是ePODFileMesh,读取函数是ReadMesh。读取方式和其他block类似,不过针对顶点数据的特点设计了一个ReadCPODData方法去读取CPODData结构。CPODData包含:
EPVRTDataType eType; /*!< Type of data stored */
PVRTuint32 n; /*!< Number of values per vertex */
PVRTuint32 nStride; /*!< Distance in bytes from one array entry to the next */
PVRTuint8 *pData; /*!< Actual data (array of values); if mesh is interleaved, this is an OFFSET from pInterleaved */
Imagination不愧是是搞移动设备GPU的,这个结构简直就是为OpenGLES设计的。其中pData如果mesh使用独立的分量数组时是数据地址,如果使用Interleaved数组时则作为数据偏移。
mesh包含的子block有:
ePODFileMeshNumVtx = 6000,
ePODFileMeshNumFaces,
ePODFileMeshNumUVW,
ePODFileMeshFaces,
ePODFileMeshStripLength,
ePODFileMeshNumStrips,
ePODFileMeshVtx,
ePODFileMeshNor, //法线
ePODFileMeshTan, //切线
ePODFileMeshBin, //次法线
ePODFileMeshUVW, // Will come multiple times
ePODFileMeshVtxCol,
ePODFileMeshBoneIdx,
ePODFileMeshBoneWeight,
ePODFileMeshInterleaved,
ePODFileMeshBoneBatches,
ePODFileMeshBoneBatchBoneCnts,
ePODFileMeshBoneBatchOffsets,
ePODFileMeshBoneBatchBoneMax,
ePODFileMeshBoneBatchCnt,
ePODFileMeshUnpackMatrix,
从这儿就可以看出mesh的组成结构。