以前milestone2已经作完的工做, 如今趁有时间记下笔记.编辑器
1.设计工具
这里是指兼容3ds max导出/fbx格式转换等等一系列工做的设计.测试
最开始, Blade的3dsmax导出插件, 所有代码都是写在导出的DLL里面的, 后来考虑到FBX等等其余格式, 如今把模块分红两部分:优化
导入/导出之类的也天然分为两个phase:动画
collecting data => building meshui
这么作的好处是builidng mesh/animation的代码所有能够复用, 并且building mesh的代码和复杂度才是最大的. 因为model builder内置到model模块内部, 只要根据工厂建立出内置的对象就能够了, 这有点像Java的风格.google
而collecting data做为用户可扩展的方式, 只要有定义良好的接口, 就能够只实现model collector, 主要是使用三方SDK获取数据, 工做量相对要小不少.spa
2.编辑器导入插件插件
对于编辑器来讲, 设计的思路是能够直接打开FBX文件. 好比像photoshop和3ds max这样的软件, 若是安装了文件格式的插件, 就能支持打开对应格式文件.设计
以前Blade对于"文件导入"没有任何抽象, 因而添加了下面的接口:
1 struct SEditorImporterInfo 2 { 3 enum 4 { 5 IMPORTER_ID_TAG = 0x80000000, 6 }; 7 TString mName; ///factory class 8 TString mTarget; ///target IEditorFile type 9 TString mTargetExt; ///target file extension 10 TStringList mExtensions; ///supported extensions 11 //importer type: assigned by framework 12 FileTypeID mTypeID; ///importer type IDs are in the same space of editor file type ids 13 ///FileTypeID with IMPORTER_ID_TAG represent a importer. 14 IconIndex mIconID; 15 16 inline bool operator<(const SEditorImporterInfo& rhs) const {return mName < rhs.mName;} 17 static inline bool comparePtr(const SEditorImporterInfo* lhs, const SEditorImporterInfo* rhs) {return *lhs < *rhs;} 18 }; 19 20 class BLADE_EDITOR_API IImporter : public TempAllocatable 21 { 22 public: 23 virtual ~IImporter() {} 24 25 /** 26 @describe get the factory class name of the importer, 27 corresponding to SEditorImporterInfo::mName 28 @param 29 @return 30 */ 31 virtual const TString& getName() const = 0; 32 33 /** 34 @describe 35 @param source: input source file stream that importer can support 36 @param dest: output converted/imported format recognized by framework & plugins 37 @param params: extra parameters used for importing 38 @param extraFiles: extra file created by importer. extra files can only contains file names, files should be created at the same path/folder of input source file. 39 @param callback: callback for importing progress 40 @return 41 */ 42 virtual bool import(const HSTREAM& source, const HSTREAM& dest, const TParamList& params, TStringParam& extraFiles, CallbackRef& callback) = 0; 43 }; 44 45 extern template class BLADE_EDITOR_API Factory<IImporter>; 46 typedef Factory<IImporter> ImporterFactory;
导入信息记录了: 支持的源文件格式(扩展名), 目标格式. 也就是说, 导入器并不提供文件格式的解析(不会根据文件内容来建立任何编辑器对象), 只是单纯的转换成(其余插件)已经支持的其余格式.
这样以来, "导出"实际上作的事情就是格式转换. 好比FBX导入插件, 是使用FBXCollector和IModelBuilder, 生成Mesh(blm)文件到一个stream里面, 而blm文件已经有插件能够将它打开了.
在编辑器使用的时候, 能够直接拿memory stream做为dest stream, 经过导入器fbx文件完成格式转换, 而后在根据导入信息里面的"目标格式", 用工厂建立真正要打开的文件实例来打开转换后的stream; 等打开成功之后, 删除掉memory stream/memory file system里面的文件(临时mesh文件).
3.批量格式转换(CLI)
因为fbx文件的转换代码所有在编辑器的fbx importer插件里面, 为了模块复用, 这里的model converter工具其实是手动加载了fbx importer插件, 跳过编辑器的管线, 直接调用import接口完成文件格式转换.
同时, 添加了makefile来支持批量的fbx转换.
问题1: 模型/骨骼/动画合并
这个是跟具体项目的spec相关的, 不少项目的动画文件是拆分红单个clip的, 每一个动画对应一个源文件. 有的是单个文件包含全部的动画.
blade的model converter 支持多个源文件合并, 为了makefile可以方便处理, 只要将一组动画/模型放入同一个子文件夹, makefile会拿到全部文件列表并调用converter来合并出最终的模型和动画.
对于runtime, 实际项目中, 有的商业引擎没有很好的处理和优化, 致使一个角色的全部不一样的装备对应的全部骨骼, 都会参与骨骼计算, 实际上有不少是无效的 - 没有任何绑定. 并且有的引擎, 实际上想作针对性的优化, 也很难下手, 改动比较大. 由于实际项目中遇到过相似的状况, 因此Blade以及提早考虑并作了处理: 只有在runtime具备有效绑定mesh的骨骼才参与动画计算, 不然忽略.
问题2: 集成到工程
这个问题和之前的tex compressor同样, 把makefile集成到工程, 而且把max文件转换成fbx放入库里做为源文件, 实现一键构建, 具体怎么作再也不记录了.
因为如今的测试资源愈来愈多, 因此工程上也把美术资源和代码分开, 单独放到一个repo里面去.
使用fbx的另一个好处就是, 调试和改进mesh的导入/导出, 均可以不依赖3ds max了.
其余
submesh的bounding计算
在google上查到的最好方式, 是根据模型的绑定信息, 生成对应骨骼的包围. 注意全部绑定信息都在模型上面, 只要拿到对应骨骼id全部的顶点, 就能够计算出包围盒; 不须要骨骼/动画文件的任何信息.
因此包围盒能够只根据模型文件离线生成. 以后runtime更新时, 再根据骨骼动画来变换包围盒.
包围盒更新和可见性依赖
Blade的动画流程大体以下: 若是模型不可见, 那么就不会更新动画, submesh的包围盒就不用更新.
这里有一个死结, 就是模型的可见性自己也依赖包围盒, 若是不更新骨骼动画和包围盒, 就可能影响可见性.
目前解决方案是: 拿模型的静态包围盒作粗略的视锥剔除
动画LOD
因为远处的物体自己就比较小, 因此能够不更新包围盒. 这个能够经过骨骼动画的LOD来调整.
LOD的计算并不是是根据距离, 实际上应该根据屏幕上投影后的大小来, 由于远处的动画可能很大, 未必就应该忽略.
而骨骼动画的计算频率也不须要满帧计算, 记得以前提到过, 30FPS已经到达人眼的识别上限, 实际上大部分电影24FPS就足够, 但游戏有不少不一样http://www.zhihu.com/question/21081976, 并且现代电影都是48FPS, 有两帧同样的画面来组成.
目前Blade会根据投影后的尺寸来调整骨骼动画计算频率, 好比较远/较小的动画就会用低频更新, 最高频率也不会是满帧, 而是大约66FPS.