现在市面上已经存在了大量的文件格式用于存储3D模型,这些格式大致上可以分为三个类别:authoring formats(专用格式),game/graphics engine formats(游戏引擎使用的格式),interchage formats(可交换的格式)。
一个authoring format由某个建模软件所特有。比如,Autodesk Maya有两种专用的文件格式,Maya ASCII(.ma)和Maya Binary(.mb)。3D Studio Max则使用.3DS文件格式。这种格式一般都会包含一些该建模软件特有的数据,而这些数据不适用于游戏引擎。例如,Autodesk Maya文件中存储了最终模型每一步的创建记录,但是在渲染引擎中只需要最终的结果,并不需要这些记录。
一个game/graphics engine文件格式只包含从模型资源中提取出来的必需数据,而不包含建模软件特有的数据。另外,这种格式通常会针对具体的引擎进行优化。如果一个游戏工作室开发的引擎是提供给工作室之外的人员使用(比如,对于一个需要不断修改的游戏),需要公开该格式的相关文档或提供其他的支持,以便用户自己创建的资源可以被正确的读取。例如,Blizzard的Starcraft II格式MDX3(.m3)已经公开发布了,并为3D Studio Max提供了一个导入导出的插件。
第三种类型的interchange formats,包含了一组独立与3D建模软件和游戏引擎的文件格式。这种格式的目的是支持在各个不同的应用程序之间交换资源,也就是希望使用一种格式用于多个应用程序中。COLLADA(Collaborative Design Activity)格式可能是目前应用最广泛的interchange format,被各种建模工具和游戏引擎所支持。
还有一个问题需要考虑的是,并不是所有的格式都支持存储应用程序可能需要的数据。比如,Wavefront OBJ(.obj)格式是一个简单的并被广泛支持的文件格式,但是该格式无法存储动画数据。
Content pipeline是处理模型asset的过程。在图形管线中,数据以primitives的形式输入并以pixels形式输出。在content管线中,由美术设计人员提供某种格式的assets文件作为数据的输出,然后输出一种在游戏中可以直接使用的assets文件。比如,把一个.obj格式的3D模型文件转换成游戏引擎使用的格式。但是与Direct3D图形管线不同的是,没有现成的content管线用于处理assets文件。需要自己创建一个这样的系统或者使用一个第三方的通用库。
通常情况下,在编译时通过content管线处理assets文件,并保存到当前所使用的游戏引擎格式的文件中。然后在运行时,加载具体游戏引擎格式的文件并使用相关的数据。另外,在运行时可以任意修改数据内容,但是也会带来相应的开销。一个游戏引擎通常不会支持同时在编译期和运行时调整assets,因为运行时与游戏的release版本相关太远。在本章,将会在运行时转换assets文件,但是把该系统应用于一个编译期content管线上是非常简单的。
Open Asset Import Library提供了一个C++调用方式的API,用于导入各种各样的3D模型格式,并以一种完全一致的方式表示这些格式。表格15.1列出了该库所支持的一小部分格式。在Open Asset Import Library(aasimp)网站上可以查看完整的支持列表。
表格15.1 Common 3D Model Formats the Open Asset Import Library Supports要在示例工程中使用这个库,首先把assimp库的开发包解压到 external 目录中,并在Library工程中添加相应的Include Directory和Library Directory。然后在Game工程或Library工程中添加对assimp.lib库的引用(或者创建一个完成独立的工程,用于创建一个编译期content管线)。Open Asset Import Library是以动态链接库(.dll)的形式发布的,并带有一个导入库文件assimp.lib,真正的API函数实现都包含在DLL文件中。因此,在启动应用程序之前需要把DLL文件拷贝到应用程序的输出目录中(也就是可执行文件的目录)。只需要一步简单的post-build event,就可以完成。如果你在配置工程时遇到了困难,可以查看本书配套网站上提供的完整工程代码。
列表15.1 The Model.h Header File
#pragma once #include "Common.h" namespace Library { class Game; class Mesh; class ModelMaterial; class Model { public: Model(Game& game, const std::string& filename, bool flipUVs = false); ~Model(); Game& GetGame(); bool HasMeshes() const; bool HasMaterials() const; const std::vector<Mesh*>& Meshes() const; const std::vector<ModelMaterial*>& Materials() const; private: Model(const Model& rhs); Model& operator=(const Model& rhs); Game& mGame; std::vector<Mesh*> mMeshes; std::vector<ModelMaterial*> mMaterials; }; }
在Model类的声明代码中,包含了用于存储Mesh和ModelMaterial objects的数组变量,以及一个成员变量对Game类的引用。需要的是,虽然在该类中包含了model material,但是模型材质主要用于描述模型中单个mesh的属性。因此多个meshes可以使用相同的模型材质。
列表15.2 The Mesh.h Header File
#pragma once #include "Common.h" struct aiMesh; namespace Library { class Material; class ModelMaterial; class Mesh { friend class Model; public: Mesh(Model& model, ModelMaterial* material); ~Mesh(); Model& GetModel(); ModelMaterial* GetMaterial(); const std::string& Name() const; const std::vector<XMFLOAT3>& Vertices() const; const std::vector<XMFLOAT3>& Normals() const; const std::vector<XMFLOAT3>& Tangents() const; const std::vector<XMFLOAT3>& BiNormals() const; const std::vector<std::vector<XMFLOAT3>*>& TextureCoordinates() const; const std::vector<std::vector<XMFLOAT4>*>& VertexColors() const; UINT FaceCount() const; const std::vector<UINT>& Indices() const; void CreateIndexBuffer(ID3D11Buffer** indexBuffer); private: Mesh(Model& model, aiMesh& mesh); Mesh(const Mesh& rhs); Mesh& operator=(const Mesh& rhs); Model& mModel; ModelMaterial* mMaterial; std::string mName; std::vector<XMFLOAT3> mVertices; std::vector<XMFLOAT3> mNormals; std::vector<XMFLOAT3> mTangents; std::vector<XMFLOAT3> mBiNormals; std::vector<std::vector<XMFLOAT3>*> mTextureCoordinates; std::vector<std::vector<XMFLOAT4>*> mVertexColors; UINT mFaceCount; std::vector<UINT> mIndices; }; }
Mesh类的声明代码中有一个aiMesh结构体的前向声明,并把该结构体类型用于Mesh类的私有构造的参数,该结构体是Open Asset Import Library中的mesh表示形式。之所以在构造函数中引用aiMesh对象,是因为Model类的实现代码中是在运行时使用Open Asset Import Library执行assets文件的转换。另外,该构造函数指定为私有的是为了保持Mesh类的接口不依赖于assimp库,意味着可以在别的地方引用Open Asset Import Library,而不会影响到应用程序的其他部分。如果要创建一个编译期的content管线,就需要重新引用assimp库。
3D模型系统中的最后一种类型是ModelMaterial类。与models和meshes一样,可以使用各种各样的表示方法描述一个模型的shader相关的数据和输入数据。ModelMaterial类中只包含一个name变量以及纹理列表的变量。其中,变量name表示渲染一个mesh使用的effect名称。比如,使用BasicEffect.fx文件时,model material的名称就是BasicEffect。纹理列表中存储了纹理文件名,而且在纹理容器中的每一个纹理文件名都对应该纹理的用途(纹理类型)。例如,map容器中的类型变量可以标识纹理为diffuse color纹理或specular纹理。其中TextureType枚举值定义了所有支持的纹理类型。
列表15.3中列出了ModelMaterial类的声明代码,以及枚举类型TextureType的枚举常量值。
列表15.3 The ModelMaterial.h Header File
#pragma once #include "Common.h" struct aiMaterial; namespace Library { enum TextureType { TextureTypeDifffuse = 0, TextureTypeSpecularMap, TextureTypeAmbient, TextureTypeEmissive, TextureTypeHeightmap, TextureTypeNormalMap, TextureTypeSpecularPowerMap, TextureTypeDisplacementMap, TextureTypeLightMap, TextureTypeEnd }; class ModelMaterial { friend class Model; public: ModelMaterial(Model& model); ~ModelMaterial(); Model& GetModel(); const std::string& Name() const; const std::map<TextureType, std::vector<std::wstring>*> Textures() const; private: static void InitializeTextureTypeMappings(); static std::map<TextureType, UINT> sTextureTypeMappings; ModelMaterial(Model& model, aiMaterial* material); ModelMaterial(const ModelMaterial& rhs); ModelMaterial& operator=(const ModelMaterial& rhs); Model& mModel; std::string mName; std::map<TextureType, std::vector<std::wstring>*> mTextures; }; }