一步步开发一个通用型的3ds max模型导出插件(1)
3ds
max是游戏开发当中使用的主流建模工具。美术人员在max建好的模型要放置到游戏的场景中去,有两种不同的选择:一是选择自由开放的max导出插件来导出模型(如3ds、obj等文件格式);二是使用自主开发的3ds
max插件来导出模型。一般的开放的模型格式往往不能满足特定游戏对3D模型的全部需求,这样开发自已导出插件就变得迫切和必须。
本文的目标是介绍以开发出满足以下需求的max插件:
1.导出基本的网格信息,即:顶点信息(顶点位置、法线、纹理坐标),三角形信息
2.导出基本的材质信息,包括标准的材质(diffuse,specular,emissive,ambient),纹理贴图信息(diffuse
map\opacity map\bump map等),uv变换矩阵、渲染状态(如 two-side等)等
3.导出 骨骼数据(cs bone和skin bone)
3.导出target camera 和 dummy 信息
4.导出 cs蒙皮和skin蒙皮并导出cs和skin的骨骼动画
5.导出基于max 节点的关键帧transform动画(translate,rotation,scale)
6.导出基本的材质颜色动画、alpha动画和uv变换动画
7.导出mesh的morph动画
8.支持自定义优化器对导出数据进行优化、冗余数据检测剔除
9.支持自定义的目标文件格式输出器,支持输出成不同的文件格式
(一)3ds
max插件基础
开发3ds max 插件,首先你应该掌握基本的c++编程、msvc 和计算机图形学,其次你应该通读一遍3ds max sdk
help和3ds max script help ,再次你应该多少会用一点3ds
max,这样有便于你理解开发过程中会遇到的很多概念术语。
3ds max提供了功能强大的max sdk和max script
,使用这些API或script使你几乎能开发出所有的max组件,同时max从6.0以后特别为游戏开发提供了IGame接口用来导出模型数据,这使游戏中常用数据的导出变得简单。在这篇文章里我不会采用max
script或IGame API来导出模型数据,而是仍然使用传统的max sdk来开发导出插件。
下面是一些max中基本的概念:
默认的,3ds max的使用的是X向右,Y轴向内,Z轴向上的右手坐标系
。这点和opengl或directx中所使用的坐标系都有所不同。
3ds
max使用场景图的方式来管理所有场景中的物体,Node是组成场景图的最小单位,一个场景中只存在一个根节点(RootNode),它由它的孩子节点和子孙节点组成了一颗树。每个叶子节点会有一个对象(Object)和一组材质(material可能会有多个subMaterial)
3ds
max的许多核心编辑功能都是由修改器(modifier)来完成的,这些modifer按先后顺序应用到一个Node的对象(Object)上,形成一个modifier
stack .
max插件都是windows的dll文件,但在max下会根据其功能不同而要求生成不同的扩展名,我们做成的导出插件的扩展名是dle.
max要求每个插件都包含以下的导出函数:
DllMain是每个windows dll的入口函数.
LibInitialize在这个DLL被加载的时候被调用,你可以在里面做一些必要的初始化工作。
LibShutdown是在这个DLL被卸载的时候调用的,你可以在这个函数里面做一些必要的清理工作。
注意:LibInitialize和LibShutdown并不是必须实现的,你可以不实现这两个函数
LibDescription 返回一个描述此DLL的字符串,必须实现
。
LibNumberClasses 返回这个DLL含有多少个插件
。
LibVersion 返回编译此插件所使用max
sdk版本
LibClassDesc
根据插件索引返回每个 插件的Desc对象指针,插件的Desc类必须从 ClassDesc2 或
ClassDesc继承而来,这个Desc类描述了插件的基本信息
并负责创建插件的核心对象,这个会在后面介绍Desc类时进行详细介绍。
这几个函数的更详细的描述可以参见 max sdk help的
Required DLL Functions一节。
本文所描述的插件DLL只含有一个插件,所以DLL入口函数和max插件基本函数的实现如下,GetPluginDesc函数将会在后面介绍Desc类时一起介绍.
2
3HINSTANCE g_Instance = NULL;
4bool g_Initilization = false;
5extern ClassDesc* GetPluginDesc(void) ;
6
7BOOL APIENTRY DllMain( HINSTANCE hModule,DWORD ul_reason_for_call,LPVOID lpReserved){
8 g_Instance = hModule;
9
10 if(!g_Initilization){
11 InitCustomControls(g_Instance);
12 InitCustomControls(NULL);
13 g_Initilization = true ;
14 }
15 return TRUE;
16}
17
18__declspec(dllexport) const TCHAR* LibDescription(void){
19 return TEXT("Universal Exporter Plugin") ;
20}
21
22__declspec(dllexport) int LibNumberClasses(void){
23 return 1;
24}
25
26__declspec(dllexport) ClassDesc* LibClassDesc(int i){
27 switch(i){
28 case 0:
29 return GetPluginDesc();
30 default:
31 return NULL;
32 }
33}
34
35__declspec(dllexport) ULONG LibVersion(void){
36 return VERSION_3DSMAX ;
37}
忘了说一个基础知识,上述这些函数只能选择C式导出,C++编译器支持重名函数,并会在编译的时候生成后缀名,而这不是我们在插件里所希望看到的。所以以上的函数你可以选择用extern
"C" {}包含起来或者写一个单独的def文件来具名导出这些函数。在这儿我的选择是用def文件来导出这些函数。
;exported method
EXPORTS
LibDescription @
1LibNumberClasses @
2LibClassDesc @
3LibVersion @
4;data defined
SECTIONS
.data READ WRITE