一步步开发一个通用型的3ds max模型导出插件(1)

一步步开发一个通用型的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的入口函数.

BOOL WINAPI DllMain(HINSTANCE hinstDLL,ULONG fdwReason,LPVOID lpvReserved)

     

   
LibInitialize在这个DLL被加载的时候被调用,你可以在里面做一些必要的初始化工作。   

int LibInitialize(void);

 

   
LibShutdown是在这个DLL被卸载的时候调用的,你可以在这个函数里面做一些必要的清理工作。

 

int LibShutdown(void);

 

    
注意:LibInitialize和LibShutdown并不是必须实现的,你可以不实现这两个函数

 

 

   
LibDescription 返回一个描述此DLL的字符串,必须实现
。 

const TCHAR* LibDescription()

 

 

   
LibNumberClasses 返回这个DLL含有多少个插件

int LibNumberClasses()

    LibVersion 返回编译此插件所使用max
sdk版本

ULONG LibVersion()

 

 

   
LibClassDesc
根据插件索引返回每个 插件的Desc对象指针,插件的Desc类必须从 ClassDesc2 或
ClassDesc继承而来,这个Desc类描述了插件的基本信息

并负责创建插件的核心对象,这个会在后面介绍Desc类时进行详细介绍。

ClassDesc* LibClassDesc(int i)

 

    
这几个函数的更详细的描述可以参见 max sdk help的
Required DLL Functions一节。

    
本文所描述的插件DLL只含有一个插件,所以DLL入口函数和max插件基本函数的实现如下,GetPluginDesc函数将会在后面介绍Desc类时一起介绍.

 1
 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文件来导出这些函数。

 

LIBRARY    generic_exporter

;exported method
EXPORTS    

    LibDescription            @1

    LibNumberClasses        @2

    LibClassDesc            @3

    LibVersion                @4

;data defined
SECTIONS
    .data READ WRITE