3dsmax表情动画的导出

作者 任晓宇

1 基本概念

      1Morpher Modifier

   3dsmax中,使用Morpher Modifier可以改变mesh, patch, NURBS model的形状,同时支持Material morphing,通常用来实现复杂的表情动画。

   Mesh添加Morpher Modifier:选择一个mesh->Modify面板->Modifier List->Morpher

     

      2Channel

   每个channel对应一个不同的Mesh,代表一种表情。

  

      通过指定在不同关健帧时每个channel的权重,来实现不同表情的组合过渡。

2 准备工作

      3dsmax6 sdk并没有暴露访问morpher modifier的接口,但是我们可以在3dsmax6/maxsdk/samples/modifiers/morpher/wm3.h中找到它的定义和ClassID

  #define MR3_CLASS_ID           Class_ID(0x17bb6854, 0xa5cba2a3)

  

  class MorphR3 : public Modifier, TimeChangeCallback

  {

  

  };

          

 将此文件include到你的工程中就可以访问morpher modifier接口了。注意,把下面两行代码注释才可以联接通过:  

       

       //static GetMorphMod theModPickmode;

       

       //static GetMorphNode thePickMode;

 

3 实现代码

   1得到morpher modifier

       

      Modifier *wxFindMorpherModifier(INode *pINode)

      {

      #if MAX_RELEASE >= 4000

             // get the object reference of the node

             Object *pObject;

             pObject = pINode->GetObjectRef();

             if(pObject == 0) return 0;

  

             // loop through all derived objects

             while(pObject->SuperClassID() == GEN_DERIVOB_CLASS_ID)

             {

                    IDerivedObject *pDerivedObject;

                    pDerivedObject = static_cast<IDerivedObject *>(pObject);

  

                    // loop through all modifiers

                    int stackId;

                    for(stackId = 0; stackId < pDerivedObject->NumModifiers(); stackId++)

                    {

                           // get the modifier

                           Modifier *pModifier;

                           pModifier = pDerivedObject->GetModifier(stackId);

  

                           // check if we found the morpher modifier

                           if(pModifier->ClassID() == MR3_CLASS_ID) return pModifier;

                    }

  

                    // continue with next derived object

                    pObject = pDerivedObject->GetObjRef();

             }

      #endif

  

             return 0;

      }

 

    2,得到每个channel相对于原始Mesh的变化顶点和偏移量:

        

MorphR3*pMorpherModifier = static_cast<MorphR3*>( wxFindMorpherModifier(pNode));

if(pMorpherModifier != NULL )

{

intiNumPoses = (int)pMorpherModifier->chanBank.size();

for(int i = 0; i<iNumPoses; ++i )

{

morphChannel&currChannel =pMorpherModifier->chanBank[i];

if( !currChannel.mActive || !currChannel.mActiveOverride ||!currChannel.cblock || !currChannel.mNumPoints )

{

continue;

}

wxPose pose;

//表情的名称

memcpy( pose.chName, currChannel.mName, sizeof(char)*64);

wxPose::wxOffsetVertex offsetVertex;

//遍历所有顶点

for( int iVertexID = 0; iVertexID<currChannel.mNumPoints;++iVertexID )

{

//判断是否有偏移

if(currChannel.mDeltas[iVertexID] == Point3(0,0,0) )

continue;

//保存顶点ID和偏移量

offsetVertex.index =iVertexID;

offsetVertex.pos =currChannel.mPoints[iVertexID] - m_aVertexArray[iVertexID].vPos;

 pose.m_VertexOffsetMap.push_back(offsetVertex);

}

   3得到channel的关健帧信息

                                         // 权重的变化范围

                    float fRange = currChannel.mSpinmax - currChannel.mSpinmin;

                    Control *Controller = currChannel.cblock->GetController(0);

                    IKeyControl *pIKeyControl = GetKeyControlInterface(Controller);

                    // 关健帧数目

                    int iNumKeys = pIKeyControl->GetNumKeys();

                    for( int j = 0; j < iNumKeys; ++j )

                    {

                           IBezFloatKey FKey;

                           pIKeyControl->GetKey( j, &FKey);

                           // 权重

                           fInfluence = FKey.val / fRange;

                           // 时间

                           fTime = FKey.time;

                    }

4 TODO

      1morphChannel类的数据成员mDeltasmWeights的具体含义。发现mDeltas里面近似保存了每个顶点的偏移量乘以0.01,有何作用?mWeights按名字理解是顶点权重,不理解。

      // Actualmorphable points

      std::vector<Point3>              mPoints;

      std::vector<Point3>              mDeltas;

      std::vector<double>              mWeights;

 

   2求权重范围的方法。目前的方法是求SpinControl的取值范围:

   float fRange = currChannel.mSpinmax - currChannel.mSpinmin;

   在实验过程中发现这个范围并不准确,当SpinControl取值范围为{0,100}时,会得到大于100和小于0的数。

  

   3支持导出material morpher数据。支持material morpher可以做到材质的混合与过渡,实现更逼真的效果。比如在笑的同时脸色变红润,眼角出现皱纹等等。

  4,目前我将表情动画的数据序列化到模型格式里,但是3dsmax6不能将morpher modifier编辑的结果存成单独的文件(类似于骨骼动画里的.bip文件),如果一个模型对应多个morph动画文件就不容易序列化了。不知道高版本的max有没有此功能。

   5, 各个MorphTarget法向量的计算

 

for each face....

  FaceEx *pFaceEx = pGameMesh->GetFace(faceId);

 // 得到位置
 int vertexId = pFaceEx->vert[faceVertexId];
 Point3 vertex;
 if( pGameMesh->GetVertex(vertexId, vertex) )
  pVertexData->m_position = vertex;

 // 得到法向量
 int normId = pFaceEx->norm[faceVertexId];
 Point3 normal;
 if( pGameMesh->GetNormal(normId, normal) )
  pVertexData->m_normal = normal;

 // 得到纹理坐标
 // mapchannel
代表一层纹理坐标, 如果mapchannel个数为1,则直接使用GetTexVertex()函数
 //
否则就遍历所有的TextureMap,计算本TextureMap的纹理坐标
 if( pGameMesh->GetActiveMapChannelNum().Count() == 1 )
 {
  int texCoordId = pFaceEx->texCoord[faceVertexId];
  Point2 texCoord;
  if( pGameMesh->GetTexVertex(texCoordId, texCoord) )
   pVertexData->AddTextureCoordinate( texCoord.x, 1.f - texCoord.y );
 }
 else
 {
  IGameMaterial *pGameMaterial = pGameMesh->GetMaterialFromFace( pFaceEx );
  int iNumTexture = pGameMaterial->GetNumberOfTextureMaps();
  for( int i = 0; i < iNumTexture; ++i )
  {
   IGameTextureMap *pTexture = pGameMaterial->GetIGameTextureMap(i);
   TCHAR *tcName = pTexture->GetBitmapFileName();

   int iChannel = pTexture->GetMaxTexmap()->GetMapChannel();
   DWORD vertexId[3];
   Point3 uvVert;
   if( pGameMesh->GetMapFaceIndex(iChannel, faceId, vertexId) )
   {
    if( pGameMesh->GetMapVertex(iChannel, vertexId[faceVertexId], uvVert) )
    {
     IGameUVGen *pGameUVGen = pTexture->GetIGameUVGen();
     GMatrix gmUV = pGameUVGen->GetUVTransform();
     uvVert = uvVert * gmUV;

     pVertexData->AddTextureCoordinate( uvVert.x, 1.f - uvVert.y );
    }
   }
  }
 }

 // 得到skin信息
 if( pGameMesh->IsObjectSkinned() )
 {
  IGameSkin *pGameSkin = NULL;
  pGameSkin = pGameMesh->GetIGameSkin();

  int iBoneID;
  float fWeight;
  int type = pGameSkin->GetVertexType(vertexId);
  if(type==IGameSkin::IGAME_RIGID)
  {
   iBoneID = pGameSkin->GetBoneID(vertexId,0);
   IGameNode *pBoneNode = g_Exporter.GetIGameScene()->GetIGameNode(ULONG(iBoneID));
   TCHAR *chBoneName = pBoneNode->GetName();

   fWeight = 1.f;
   pVertexData->AddInfluence(iBoneID, fWeight);
  }
  else //blended
  {
   for(int b=0;b<pGameSkin->GetNumberOfBones(vertexId);++b)
   {
    iBoneID = pGameSkin->GetBoneID(vertexId,b);
    IGameNode *pBoneNode = g_Exporter.GetIGameScene()->GetIGameNode(ULONG(iBoneID));
    TCHAR *chBoneName = pBoneNode->GetName();

    fWeight = pGameSkin->GetWeight(vertexId,b);
    pVertexData->AddInfluence(iBoneID, fWeight);
   }
  }
 }

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值