利用3DMAXIGame接口可以方便的导出自已的动画格式数据,而且你仍然可以同时继续调用原来的SDK函数来完成一些IGame接口不支持的操作。下面是调用IGame导出骨骼动画的大概步骤,为了让简化问题,这儿通过对3DMAX骨骼动画进行采样的方式来完成骨骼动画数据的采集,至于像TCBBezier这类的控制器数据的导出,原理一样,就是需要计算一下每个关键帧骨骼数据的相关插值参数。懂的TCBHermite插值算法)和Bezier的插值算法应该比较好理解。

首先利用插件向导建立一个导出插件的框架,完成一些必要的接口例程。然后重点来到真正导出数据的接口函数中:

Int DoExport( )

{

//

       // 转换坐标系为D3D坐标系

       //

       IGameConversionManager * cm = GetConversionManager();

       cm->SetCoordSystem( IGameConversionManager::IGAME_D3D );

 

       // 初始化IGame 场景接口

       m_pIGame->InitialiseIGame();

 

       // 我们导出网格时的第一帧,当然不一定是0帧,任何一帧都是可以的

       m_pIGame->SetStaticFrame(  0  );

      

       // 导出其它的各种数据,网格,材质,灯光,摄像机

      

       // 这儿就是我们现在关心的是骨骼数据导出的地方

       ExportBones();

}

 

void ExportBones()

{

// 针对每一个Bone类型的Node

 

// 得到骨骼名称

TCHAR *value = pGameNode->GetName();

// 骨骼唯一的ID

int id = pGameNode->GetNodeID();

// 父骨骼的ID

IGameNode *parent = pGameNode->GetNodeParent();

int parentID = parent ? parent->GetNodeID() : -1;

 

// 得到骨骼控制器

IGameControl *pGameControl = pGameNode->GetIGameControl();

 

// 采样各个时刻的值, keys用来接收返回的采样值,m_sampleRate是采样的帧率,采样的

// 数据类型为IGAME_TM,代表骨骼矩阵,我们可以根据需要将其分解为缩放,旋转,平

// 移数据。m_bRelative代表骨骼矩阵是否需要乘以父骨骼矩阵,如果为FALSE的话,可以

// 直接用来变换蒙皮顶点,不需要再乘以父骨骼矩阵。

if ( pGameControl->GetFullSampledKeys( keys,

                     m_sampleRate,

                                                             IGAME_TM,

                     m_bRelative ) )

       {

              // 导出骨骼采样的各个帧的数据,共有三种类型:缩放,旋转,平移

              // 由这三种类型的数据可以组建一个骨骼的变换矩阵

              DumpSampleKeys( pDoc, keys, IGAME_POS, pGameNode );

              DumpSampleKeys( pDoc, keys, IGAME_ROT, pGameNode );

        DumpSampleKeys( pDoc, keys, IGAME_SCALE, pGameNode );

}
}

 

// 这儿是具体导出骨骼所有帧数据的地方

Void DumpSampleKeys( IGameKeyTab &keys,

            IGameControlType type,

              IGameNode *pGameNode )

{

             // 在这儿我们将得到的各个骨骼矩阵数据进行分解,得到骨骼的旋转,缩放,平移

     // 三种类型数据分别保存。

             GMatrix &matrix = keys[ k ].sampleKey.gval;

             Matrix3 m = matrix.ExtractMatrix3();

             AffineParts ap;

       decomp_affine( m, &ap );

   // ap的内容保存为自已的格式

       // …

}

  

至此我们就完成了骨骼动画数据的导出,是不是非常简单。下面将介绍如何利用导出的旋转,平移,缩放数据去重建3DMAX中的骨骼矩阵,在获得了骨骼在各个时刻的旋转、平移缩放三种数据后,我们就可以恢复出这三种数据所代表的骨骼变换。恢复方法见MAXDOC中说明:

Matrix3 srtm, rtm, ptm, stm, ftm;

ptm.IdentityMatrix();

ptm.SetTrans(ap.t);

ap.q.MakeMatrix(rtm);

ap.u.MakeMatrix(srtm);

stm = ScaleMatrix(ap.k);

mat = Inverse(srtm) * stm * srtm * rtm * ftm * ptm;

(一般情况下你直接采用rtm * ptm也不会出错,因为人体骨骼一般不会有缩放的情况,怪物例外。 )

这儿我们假定这个骨骼变换在导出时已经包含了父骨骼作用的影响(GetFullSampledKeys( keys, m_sampleRate, IGAME_TM, m_bRelative ) m_bRelative值为FALSE)。也就是说这个骨骼上的蒙皮顶点乘上这个骨骼矩阵就可以得到在该骨骼作用下的新的顶点,同理所有网格顶点都乘以它们所绑定到的骨骼上的骨骼矩阵就完成了网格(蒙皮)随着骨骼一起动画的目的。

在导出骨骼动画之前我们可以同时导出其网格的数据。记录获取这个网格的数据时刻是t0, 那么我们用这个to时刻的网格乘以各个时刻的骨骼矩阵是否就能将网格进行正确的蒙皮动画呢?如果你试一下就会发现结果是错误的。因为我们前面计算的骨骼矩阵不是针对to时刻的网格的。

前面计算的所有时刻的骨骼矩阵针对的都是同一个姿态的网格,然而这个姿态却不是我们t0时刻导出的网格。怎么办?我们需要将这个t0时刻的网格先变换到原始可以进行骨骼蒙皮的姿态即可。怎么做?

我们看看下面的公式:

(待求的可以进行蒙皮的网格数据) * t0时刻骨骼矩阵) = to时刻的网格数据)

现在我们手头有如下的数据,一是t0时刻网格的数据,二是可以得到t0时刻骨骼矩阵。

这样的话:

(待求的可以进行蒙皮的网格数据)=(to时刻的网格数据) * (t0时刻骨骼矩阵的 逆)

(待求的可以进行蒙皮的网格数据)*(各个时刻的骨骼矩阵),就实现了骨骼动画。

也即是 :

最终的蒙皮网格 = (待求的可以进行蒙皮的网格数据)* (各个时刻的骨骼矩阵) 

最终的蒙皮网格 = (to时刻的网格数据)*t0时刻骨骼矩阵的 逆)* (各个时刻的骨骼矩阵) 

上面的公式就是我们进行骨骼动画时的公式。其中t0时刻骨骼矩阵的 逆)我们可以称其为骨骼偏移矩阵或什么矩阵你认为能说明其作用的名字。

 

      至此已经完成了3DMAX骨骼动画数据导出后的使用方法,在验证过程中有个需要注意的地方是,在用导出的旋转,缩放,平移等数据还原骨骼矩阵时,那个缩放的四元数要特别照顾一下,将(x, y, z, w)中的w值反转一下极性,也就是w = -w; 为什么这样呢,我猜想可能是因为MAX中的旋转方向和D3D中的不一致的原因。具体没有去验证。

posted on 2011-01-25 22:17  操作系统  阅读(4424)  评论(0编辑  收藏  举报