从3DMax导出数据方法比较
 

 网易杭州 卢立祎
email:luliyi1024@gmail.com

关键字:3D 3dsmax 插件 maxscript 3dmaxSDK 游戏开发

  在游戏开发过程中需要大量的模型数据来描述人物、建筑、场景,如果单纯开发一个编辑器来编辑网格、顶点、材质等信息,代价太大,因此往往利用一些现成的3D建模软件来代替。

  使用 3dsMax 进行模型、动画数据导出是3D引擎开发过程中的一个必然环节(或者使用Maya等类似的3D建模工具)。然而discreet公司在指导用户进行二次开发的方面做得并不是太好,有一些问题是在开发过程中会常常遇到。

  美术工作人员在3dsMax环境中编辑好了三维人物或物体网格信息,通过骨骼动画的方式加入了动作动画,指定了渲染方式,编辑了贴图和UV坐标之后,我们就必须想方设法把这类数据从编辑环境中导出成文件以供引擎使用。一般来说,最主要有三种方式来取得需要的数据:

  • 1. 利用已有的导出格式取得数据
  • 2. 制作max的export插件输出数据
  • 3. 编写maxscript输出数据
  • 利用已有的导出格式取得数据

  使用3ds文件和xml文件作为导出源文件,然后直接在引擎中读取数据转化为自己定义的数据格式是普遍使用的方法。一般来说会使用一些转换工具转换为更加高效的文件格式。比如在早期的MS DirectX SDK 中,就有一个convert3DS 的工具,把3ds格式的文件转换为D3DX使用的X文件。由于这种方式受到源文件信息固定的限制,渐渐的不被采用。DirectX SDK 也开始使用插件的方式导出数据。

  Max7中,在菜单文件中选择导出,可以看到“IGame Exporter”,可以导出XML格式的文本文件,由于现在解析XML文件已经非常简单,甚至可以用序列化直接映射到数据结构,因此此方法有一定的使用价值。

  使用举例:
  1、打开3dsMax7,编辑一个长方体,在菜单“文件”中选择“导出”项,在导出文件对话框中的“保存类型”下拉框中选择“IGame Exporter(*.XML)”。输入文件名(test),然后确定。(图1)

图1


  2、在弹出的对话框中如下选择,按OK导出。(图2)

 图2


  这个时候在相应的目录中就会出现两个文件:test.xml和box01.xml。

  其中test.xml:
     <IGame Version="1.0" Date="Mon Dec 12 18:34:52 2005">
      <SceneInfo>
       <Info FileName=""/>
       <Info StartFrame="0"/>
       <Info EndFrame="100"/>
       <Info FrameRate="30"/>
       <Info TicksPerFrame="160"/>
       <Info CoordinateSystem="directx"/>
      </SceneInfo>
      <Node Name="Box01" NodeID="1" NodeType="GeomObject">
       <NodeTM>
        <Translation>-2.780697 0.000000 0.928622</Translation>
        <Rotation>0.000000 0.000000 0.000000 1.000000</Rotation>
        <Scale>1.000000 1.000000 1.000000 0.000000 0.000000 0.000000</Scale>
       </NodeTM>
       <GeomData Include="C:\Documents and Settings\badfish\妗岄潰\Box01.xml"/>
      </Node>
     </IGame>

  这个文件其实描述了场景的基本情况和模型节点之间的相互关系,并且表明了我们刚才建立的物体Box01的信息存储在另一个文件 Box01.xml中。

  文件 Box01.xml(为了节省篇幅,简略部分内容):
     <IGame Version="1.0" Date="Mon Dec 12 18:34:52 2005">
      <GeomData Node="Box01">
       <Vertices Count="8">
        <vertex index="0">-16.477034 0.000000 -14.141105</vertex>
        <vertex index="1">10.915640 0.000000 -14.141105</vertex>
     ……
       </Vertices>
       <Normals Count="24">
        <normal index="0">0.000000 -1.000000 0.000000</normal>
        <normal index="1">0.000000 -1.000000 0.000000</normal>
        ……
       </Normals>
       <Faces Count="12">
        <Face index="0">
         <vert>0 3 2</vert>
         <smGrp>2</smGrp>
         <MatID>1</MatID>
         <norm>0 1 2</norm>
        </Face>
        ……
       </Faces>
      </GeomData>
     </IGame>

  这里包含的信息就有顶点、法线、面,其中面的信息中还有法线平滑组(SmoothGroup)、顶点索引、法线索引及材质索引的信息。

  通过这些信息,你就可以在你自己的系统中重现max中的编辑场景了。

  • 制作max的export插件输出数据

  使用maxSDK进行插件开发是大家普遍采用的方法,网上也可以查到相关的很多资料。在3dsmax7的目录中,可以找到maxsdk的目录,其中的help目录可以找到一些使用和开发帮助:

1. Sdk.chm

  SDK的帮助文件。版本为6。个人感觉无论从整体结构还是编排都很糟糕,很难在非常短的时间内找到你想要的资料。

  如果要使用CS(Character Studio)的功能,就必须包含Include\CS目录下的头文件,以前max版本必须先安装CS的SDK才可以使用此功能,在max7中,CS已经成为了max的一个标准组件,因此CS SDK的部分也已经在max SDK中包含。

  Max7包含的CS的版本号为4.2。

2. IGameHelp.chm

  Discreet提供了一套IGame的接口用来导出一般游戏制作需要导出的数据。上面提到的“IGame Exporter”就是使用了此接口。可以在maxsdk\samples目录下找到这个插件的源代码。

  DirectX9提供的max插件也是使用了IGame接口导出数据,可以在DirectX SDK中的\Utilities\Source\Max找到此插件的源代码以供参考。

  IGameHelp.chm是使用Doxygen( http://www.stack.nl/~dimitri/doxygen )自动生成的文档文件,因此使用和查找习惯于Doxygen通用生成格式相同。

  其中的IGameSkin已经自动包含了Skin和Physique两种Modifier,无须对应两种Modifier分别写两套处理代码。

  Max7自带的IGameInterface版本号为1.121。

3. sparks_archive.chm

  sparks是max的一个讨论站点,在里面可以找到很多你需要的问题的答案,sparks_archive.chm可以自动更新到最新,但是我从来没有更新成功过。
 
  此外,max7依旧提供了一套Ravi K Karra编写的VC6的3ds max Plugin Wizard,可以自动生成所需要的插件框架,很可惜,版本还是R5,搭配max7的SDK会有一些小问题。把文件\MAXSDK\HELP\SDKAPWZ.ZIP拷贝到VistualStudio的模版目录中(比如:\Microsoft Visual Studio\Common\MSDev98\Template),解压缩zip文件,VC6的新建模版中就会出现max Plugin Wizard这一项。

  如果要在VC6上编译max7的插件,还需要对一个头文件作一点小小的修改:在文件include\istdplug.h文件的第1685行:
 typedef struct {  改为:  typedef struct Options{
 1708行:
 class Options2: public Options 改为:class Options2: public IAssignVertexColors::Options
 这都是因为VC6编译器无法对匿名结构进行继承。

  当然,也可以选择使用VC7\7.1进行Plug编写。可以在 http://sparks.discreet.com/downloads/downloadshome.cfm?f=2&wf_id=134 下载到3ds max 6 and 7 Plugin Wizard for Visual Studio 7。

  以VC6.0为例,在菜单“文件”中选择新建,3ds max Plugin Wizard 的项目就在项目列表中出现了,输入项目名称之后,就进入了插件向导。(图3)

图3


  Max插件向导提供了很多插件的种类供选择,不同的插件可以提供不同的max扩展功能,要使用数据导出的功能,就要选择“File Export”项。(图4)

图4

  接下来的两部,分别要求输入类名、相关路径、是否需要注释等细节,最后,向导便会产生一个插件的框架,只要根据自动生成的注释,填写相关的内容,编译链接之后,便会在输出目录中生成相应的插件文件,比如:IGameExporter.dle。然后把文件拷贝到max目录下的plugins 目录,启动max7就可以像第一节中使用IGameExporter那个插件一样使用这个插件了。

  使用插件导出数据有一个很大的弊端:在插件编写调试过程中,经常要重新重新启动max,浪费了不少时间,这一点无法与下面将要提到的maxscript相比。

  • 编写maxscript输出数据

  使用maxscript进行数据输出是现在我手头项目的使用方法,使用简单、调试方便,每次有了改动不用重新启动3ds max。可以在帮助菜单内找到MAXScript Reference 7.0。这个参考手册内容相当详尽。maxscript可以操作max编辑环境内的所有的对象,并且可以通过plugin来增加功能与接口(插件的gup类型,在上一节的VC6向导里面可以找到一种插件类型“Global Utility Plug-Ins”,就是这个)。

  Max还内置了一个脚本编辑器,有语法高亮功能(到处是Bug!),菜单“MAXScript”中点击“打开脚本……”,可以打开一个已经编辑好的脚本。(图5)

        图5

  建议使用EditPlus等文本编辑软件来编写脚本,并且可以在
http://www.editplus.com/files/maxscript.zip
下载到最新的EditPlus的maxscript语法高亮配置文件。

  Max7内置的Visual MaxScirpt编辑器可以看作是一个界面资源编辑器,可以方便的编辑界面,所见即所得,一般可以在Visual MaxScirpt中编辑好界面后把界面代码导出成脚本文件拷贝到相应的代码里边去。很方便,但是附带的方法编辑器同样非常难用,建议使用外部编辑来编辑事件处理程序。(图6)

       图6

我们现在来简单的建立一个max脚本:

首先打开Visual MaxScirpt,在上面编辑一个button(按钮)。并且设置一下按钮的一些属性,比如把按钮上的文字设置为“Create Cube”。(图7)

图7

  然后我们把Rollout 的名字改为TestRollout,选中那个刚刚加的button,选择“时间处理程序”页,这里可以看到对于那个按钮有一个pressed事件可供编辑。选中并且点击,就会出现时间处理程序的编辑器,在这里输入的代码都会在那个按钮被按下的时候被触发。我们在里面输入:

  b = box name:"foo" position:[10,10,10] height:20

  意思是在世界坐标的(10,10,10)处创建一个高20单位的名字是“foo”的box对象。很好理解吧。(图8)

  至于具体的语法细节,可以在参考手册中详细的查到。

      图8

  然后保存脚本“test.vms”格式,并且另存为一个“test.ms”文件,用编辑器打开“test.ms”,可以看到Visual MaxScirpt已经自动生成了大部分的代码:

rollout TestRollout "脚本测试" width:198 height:202   --定义Rollout
(
 button btn1 "Create Cube" pos:[35,43] width:122 height:56 --定义按钮Create Cube
 on btn1 pressed  do         --按钮按下时触发事件
  b = box name:"foo" position:[10,10,10] height:20  --创建立方体
)

  脚本简洁易懂,我们只需要再添加两行代码令对话框产生出来即可:

try(destroyDialog TestRollout)catch()     --摧毁已经有的对话框
createDialog TestRollout 300 500      --在指定坐标创建对话框

  这样,这个简单的脚本就编写完成了,运行脚本,结果就会在世界坐标的(10,10,10)处创建一个高20单位的名字是“foo”的box对象。(图9)

 


        图9


  Character Studio可以很方便地生成人物骨骼和动作,一般在游戏制作中被广泛使用,如果需要导出Character Studio数据,需要注意一些问题。

  Character Studio包含Biped、Physique和群组三个组件,一般我们会用到前两个。

  Biped是CS中主要的和最受认可的组件,它是用与类人角色的通用装备,但同时又足够灵活,可以进行自定义以适合各种不同形状的角色。在MAXScript Reference 7.0中的MAXScript Extensions中可以找到Biped相应扩展各种使用方式的介绍和范例。一般来说,如果只是制作数据导出脚本的话,基本上只需要通过表达式:
  if isKindOf node Biped_Object do
  (
   ……
  )
  就可以判定是Biped节点。

  Physique是类似于“蒙皮”的修改器,但它有额外的功能以更好的控制基本骨骼影响网格的方式。当使用Biped时,不需要Physique。它只是一种可选的蒙皮系统,也可以使用标准的“蒙皮”修改器。虽然在MAXScript Reference 7.0也有对Physique扩展方法的介绍以及范例,但是遗憾的是,Discreet并没有在Max7中实现它(这一点非常奇怪,有了说明却没有实现。据说在Max8中,这个扩展已经被删除)。如果在脚本中使用了此扩展,运行时会报告undefined。因此,我们必须自己实现此扩展,编写扩展的plugin。幸好已经有人把这个扩展导出插件写好,名字叫IPhysique,网上可以搜索到IPhysique.zip或者IPhysique.gup这个文件,把它拷贝到max的plugins目录下(如果是zip文件的话需要解开后使用)就可以了。因为这个导出方法和官方Reference中提到的方法不兼容,因此需要阅读自带的IPhysique.doc文档,里面大致介绍了各种函数方法的使用。在使用的时候,不要忘记在函数调用前加上physiqueOps前缀,比如physiqueOps.getPhysiqueModifier。但是要注意的是:网上下载的IPhysique.gup有不同的版本,一般来说是for max5.1的,无法在max7中使用,因此,可以有两个选择来解决这个问题:

  下载源代码,重新在maxSDK7下编译。源代码地址:
http://sparks.discreet.com/downloads/downloadshome.cfm?f=2&wf_id=130
  有人已经把它在maxSDK7下编译生成了二进制文件。下载地址:
http://sparks.discreet.com/downloads/downloadshome.cfm?f=2&wf_id=129
  在下载之前需要先注册。
  这样,就可以很方便的使用Physique的功能进行数据导出了。
  例如以下函数就从Physique中取得蒙皮数据:

fn AddMdfPhysique subskins sklarray mesharray mdf node =
(
 sk = subskin_t name:""
 sk.name = node.name
 sk.vertices = #()

 m = findMeshInMeshArray mesharray node

 v_calculated = #()
 for i=1 to m.pos.count do
 (
  append v_calculated false
 )

 face_num = getNumFaces node;
 idx = 1
 for i=1 to face_num do
 (
  vf = getFace node i
  for j=1 to 3 do
  (
   if v_calculated[m.idx[idx]] == false do
   (
    vvidx = vf[j]
    sv = skin_vertex_t pos:[0,0,0]
    sv.influences = #()
    bonenum = physiqueOps.getVertexBoneCount node vvidx
    for n=1 to bonenum do
    (
     bone = FindBoneInSkl sklarray (physiqueOps.getVertexBone node vvidx n)
     if bone == undefined do
     (
      format "error: bone: %" (physiqueOps.getVertexBone node vvidx n)
     )
     svi = skin_influence_t bone_id:bone.id weight:(physiqueOps.getVertexWeight node vvidx n)
     append sv.influences svi
    )
    at time 0
    (
     sv.pos = getVert node vvidx
     sv.normal = getNormal node vvidx
    )

    sk.vertices[m.idx[idx]] = sv
    v_calculated[m.idx[idx]] = true
   )
   idx += 1
  )
 )

 append subskins sk
 return true
)

  还有一些方法在编写数据导出脚本的时候常常非常有用。比如:
  弹出对话框
  messageBox "Err: name too long."+outstr

  判断数据类型
  fn IsMesh node =
  (
   if isKindOf node Editable_mesh then
    return true
   else return false
  )

  锁定时间点(获取动画数据时常常会用到)
  at time 0
  (
   b.pos = node.transform.pos
   b.rot = node.transform.rotation
   b.scale = node.transform.scale
  )

  打开并写入一个二进制文件
  output_file = fopen filename "wb"
  WriteString output_file "HelloWorld!"

  格式化输出数据到文件
  format " bone num=%\n" sklarray.count to: output_file

  • 总结

  总的来说,使用maxscript来进行数据导出是一个比较好的选择,无须编译、无须重新启动max,方便调试。