编写3dsmax骨骼动画导出插件

上一篇文章中我们讨论了蒙皮骨骼动画的基本原理,本文我们将继续编写3dsmax的骨骼动画导出插件。目前网上我找到的使用IGAME导出骨骼动画的文章只有那么几篇,而且讲得并不详细,自己还是踩了很多坑。之前也并没有相关经验,只能自己摸索着来并在此总结,希望能够让读者们绕坑而行。

1. 安装3dsmax sdk

在3dsmax的安装程序中会有一项“安装工具和实用程序”一项,选择其安装即可。

1

安装好后将maxsdk/howto/3dsmaxPluginWizard目录中的3dsmaxPluginWizard.ico、3dsmaxPluginWizard.vcz、3dsmaxPluginWizard.vsdir拷贝至vs目录中的vc/vcprojects中即可。我是用的是express 2013,目录名称叫做vcprojects_WDExpress。

2.配置Visual Studio

在vs的新建工程中将可以看到3dsmax导出插件的工程项目,新建后将自动生成程序入口代码。不过在开始之前还需要再做一些小动作:由于每次max加载插件后,一直到关闭3dsmax才会将句柄释放。也就是说当我们想测试插件时,每次都必须重启3dsmax才能重新加载插件,非常不方便。为了解决这个问题,我们可以再新建一个普通的DLL项目,所有的主体代码均在此DLL中实现。每次调用完毕后,立刻释放句柄。[1]

1

3.骨骼动画导出插件的编写

之后就可以开始导出插件的编写了。3dsmax sdk提供了一个叫做IGame的工具,具备很多实用的功能,可以方便我们收集场景信息。首先需要引入IGame的头文件,以及igame.lib。

导出模型、材质等过程这里就省略了,网上的教程应该有很多,可以搜索一下。

3.1 IGame初始化

我们首先需要做一点初始化的工作:获得IGame的相关接口,设置坐标系等等。坐标系设置好后,3dsmax会自动为我们计算矩阵变换。

bool exportSelected = (options & SCENEEXPORTSELECTED) ? true : false; IGameScene* scene = GetIGameInterface(); IGameConversionManager * cm = GetConversionManager(); UserCoord rightHandCoord = { 1, 1, 2, 5, 1, 0 }; cm->SetUserCoordSystem(rightHandCoord); scene->InitialiseIGame(exportSelected);

第一行首先确定用户是否是选择了“导出选择的物体”。IGame会根据此选项来创建场景树。

第四行设置了导出的坐标系,sdk的头文件中有详细的注释解释了UserCoord结构体各项的作用:

struct UserCoord{ //! Handedness /! 0 specifies Left Handed, 1 specifies Right Handed. */ int rotation; //! The X axis /! It can be one of the following values 0 = left, 1 = right, 2 = Up, 3 = Down, 4 = in, 5 = out. / int xAxis; //! The Y axis /! It can be one of the following values 0 = left, 1 = right, 2 = Up, 3 = Down, 4 = in, 5 = out. / int yAxis; //! The Z axis /! It can be one of the following values 0 = left, 1 = right, 2 = Up, 3 = Down, 4 = in, 5 = out. / int zAxis; //! The U Texture axis /! It can be one of the following values 0 = left, 1 = right / int uAxis; //! The V Texture axis /! It can be one of the following values 0 = Up, 1 = down */ int vAxis; };

下面首先先收集一下各个结点的信息。我们通过遍历整棵场景森林的方式,收集模型结点和骨骼结点的信息。导出模型、材质等内容本文就省略了,网上的教程应该有很多,搜索一下就可以了。我们来关注骨骼结点的统计:

map bones; void GetMeshNode(IGameNode* node, vector& nodes, int& totalVertexNum) { switch (node->GetIGameObject()->GetIGameType()) { case IGameObject::IGAMEMESH: /*此处省略*/ break; case IGameObject::IGAMEBONE: bones.insert(make_pair(node->GetNodeID(), node)); break; default: break; } int childNum = node->GetChildCount(); for (int i = 0; i < childNum; i++) { GetMeshNode(node->GetNodeChild(i), nodes, totalVertexNum); } }

 对所有根节点都调用此函数就可以收集到所有的骨骼结点信息。bones中存储的就是我们所有的骨骼结点,之后再导出骨骼和骨骼动画就非常简单了:

int startTime = staticcast(GetCOREInterface()->GetAnimRange().Start() / GetTicksPerFrame()); int endTime = staticcast(GetCOREInterface()->GetAnimRange().End() / GetTicksPerFrame()); boneHeader.FrameNum = (endTime – startTime) + 1; fwrite(&boneHeader, sizeof(boneHeader), 1, filp); for (int i = 0; i < boneHeader.FrameNum; i++) { for (map::iterator it = ::bones.begin(); it != ::bones.end(); it++) { float t = i * GetTicksPerFrame(); GMatrix gm = it->second->GetWorldTM(t); t /= 4.8; fwrite(&t, sizeof(t), 1, filp); Point4 p1 = gm.GetColumn(0); Point4 p2 = gm.GetColumn(1); Point4 p3 = gm.GetColumn(2); Point4 p4 = gm.GetColumn(3); fwrite(&p1.x, sizeof(p1.x), 1, filp); fwrite(&p2.x, sizeof(p1.x), 1, filp); fwrite(&p3.x, sizeof(p1.x), 1, filp); fwrite(&p4.x, sizeof(p1.x), 1, filp); fwrite(&p1.y, sizeof(p1.x), 1, filp); fwrite(&p2.y, sizeof(p1.x), 1, filp); fwrite(&p3.y, sizeof(p1.x), 1, filp); fwrite(&p4.y, sizeof(p1.x), 1, filp); fwrite(&p1.z, sizeof(p1.x), 1, filp); fwrite(&p2.z, sizeof(p1.x), 1, filp); fwrite(&p3.z, sizeof(p1.x), 1, filp); fwrite(&p4.z, sizeof(p1.x), 1, filp); fwrite(&p1.w, sizeof(p1.x), 1, filp); fwrite(&p2.w, sizeof(p1.x), 1, filp); fwrite(&p3.w, sizeof(p1.x), 1, filp); fwrite(&p4.w, sizeof(p1.x), 1, filp); } } fclose(filp);

上面的代码有几处需要解释一下:

首先我们存储的是模型坐标系中的矩阵(GetWorldTM),而不是相对于父节点的矩阵信息。在上一篇文章中我们曾提到,如果想要进行动画插值,需要计算出相对于父骨骼的变换矩阵。

其次,Tick是3dsmax中的一个时间单位,一秒钟有4800个Tick。我们导出时的时间以毫秒为单位,因此时间就是当前的Tick数除以4.8。

最后,一长串的fwrite实在是迫不得已。使用其他方式(比如)GetRow返回行时将会产生奇怪的编译错误;之前在用到Point4时也出现过问题。这有可能是由于我使用的是vs2013,而3dsmax 2012的sdk的目标工具集是vs2010导致的。

下一篇文章中我们将介绍如何在程序中计算骨骼动画并使用OpenGL显示。

参考

[1] 3dsmax模型导出插件调试技巧, http://blog.csdn.net/zhengkangchen/article/details/6424806

[2] 3DsMax导出插件编写(三)——使用IGame收集模型信息, http://blog.163.com/liweizhaolili/blog/static/16230744201311219926255/