XNA 蒙皮动画的导入与控制

纠结了将近一个月的东西..终于有点成果了,使用官方例子SkinningSample_4_0修改来的从maya导入蒙皮动画到XNA中,以及对动画的基本控制,基本上没问题了。现在就来总结下。

首先是目录:

1.对官方例子的简单解说。
  1.1.基本概念
  1.2.蒙皮信息的传递
  1.3.播放蒙皮动画
  1.4.AnimationPlayer类

2.模型蒙皮动画导入经验。
  2.1. 蒙皮绑定
  2.2.关节关键帧
  2.3.导出模型
  2.4.内容处理器

  2.5.其他

3.我加入的。
  3.1.多段动画
  3.2.模型骨骼点跟踪
  3.3.其他

 

1.                                                                                                                                                                                                                                                                       

1.1.                                                                                                                                     

   开始解说之前先要明确几个概念,首先是关于关键帧的概念,官方例子中和maya中是不一样的。在maya中的关键帧就是一个动作,从一个关键帧动作变化到另一个关键帧动作之间一段连续的变换就是关键帧动画,蒙皮动画就是靠关键帧动画来做的(不明白的同学请去看maya的有关教程)。而在XNA的例子中,我们会看到一个List<Keyframe> Keyframes(关键帧) 属性,这个属性表示的是整个动画中对每一个骨骼(maya中叫关节joint)的变换矩阵和变换执行的时刻。所以我们会看到maya的一段60帧的动画导到这里后,Keyframes中会有2000多个关键帧...嗯。这很正常,如果模型骨骼更多的话,这个值会更大。

  然后是模型骨骼的名称问题,maya中的关节(joint),即是XNA中的骨骼(bone),然后XNA中还把一个模型的全部骨骼称为骨架(skeleton). 


1.2.                                                                                                                                     
  官方例子通过一个名为SkinnedModelPipeline的自定义内容管道从fbx模型文件,中取出有关蒙皮动画的信息,信息如下:

   //绑定姿势
List<Matrix> bindPose = new List<Matrix>();
//骨架中每个骨骼的顶点位置矩阵
List<Matrix> inverseBindPose = new List<Matrix>();
//骨架层次
List<int> skeletonHierarchy = new List<int>();
//骨骼名字
List<string> SkeletonName = new List<string>();
复制代码
复制代码
复制代码

  然后将这些信息存储在一个SkinningData类的实例中,放在模型类的Tag属性中传递到游戏程序中,然后在程序中通过一个AnimationPlayer类读取Tag的内容进行动画的播放。


1.3.                                                                                                                                     
  播放时需要确定要播放的是哪段动画,一段动画称为一个动画剪辑,程序中使用AnimationClip类来表示,方法如下:

          //从模型的Tag属性中取出蒙皮信息
skinningData = currentModel.Tag as SkinningData;

if (skinningData == null)
throw new InvalidOperationException
("This model does not contain a SkinningData tag.");

// 创建一个动画播放器
animationPlayer = new AnimationPlayer(skinningData);
//从蒙皮信息中取出动画剪辑
clipAll = skinningData.AnimationClips["Take 001"];
//这些是非必要性的剪辑介绍
clipAll.ModelName = "Brave.fbx";
clipAll.Introduce = "循环播放的攻击动画";
//播放
animationPlayer.StartClip(clipAll);
复制代码
复制代码
复制代码

  这样就能播放动画了,但这里有一个问题,一个模型中只能包含一段动画(至少我目前没有找到maya中一个模型包含多段动画的方法,如果有人知道的话,请求赐教~),即一个动画剪辑,一旦animationPlayer.StartClip(Clip);就会被全部播放完,不过实际游戏中,我们的一个模型总会包含着多段互相独立的动画,比如,走路,跳跃,攻击等。 这里我使用的方法的将动画剪辑导出成一个.clip的自定义文件,这样读取一个模型和多个不同的.clip文件后,就可以使模型做出多种动画,这个待会会介绍~方法其实非常简单。

 

1.4.                                                                                                                                     
  AnimationPlayer的工作原理,基本上就是从AnimationClip里取出Keyframes,然后对每个骨骼按照指定的时间顺序的进行变换。

2.                                                                                                       
  首先是maya中的操作,我使用的是maya2012中文版,蒙皮动画的制作方法就不介绍了(我也是刚学几个星期的新手而已...),这里只说下,导出到XNA中需要注意的地方。


2.1.                                                                                                                                     
  蒙皮绑定时,注意使用‘互交式蒙皮绑定’其他的绑定会出现问题。。比如蒙皮变形,位置错乱之类的。


2.2.                                                                                                                                     
  制作动画时注意关键帧要设在关节上,不能设在IK控制柄上,XNA是不认识控制柄的。还有各种约束也是不认的。注意关节的命名,尽量使用有意义的名称,比如:jointR_Hand,jointL_Leg之类的,以便在XNA中识别。


2.3.                                                                                                                                     
  导出模型文件类型,自然是选fbx,注意勾上动画选项,我记得默认是没有勾选的。

2.4                                                                                                                                     
  XNA中,注意选择fbx文件的内容处理器,方法是在VS中的右击加入的.fbx文件,选择 Content Processor为 SkinnedModelProcessor。
2.5                                                                                                                                     

  注意,如果导入的模型不带纹理的话,需要在XNA中加入一张白色的纹理,否则无法显示材质颜色。

 

3.                                                                                               

我加入的地方,为了模型动画基本可用,我对官方例子做了些修改,接下来会一一介绍,当然官方也对这个例子做过扩充这是他做的改进:

SkinningControl:
对原例的改动:
1.  增加播放状态PlayState: Loop,Once,Pause
2.  增加NewPlay(clip,playstate)方法,该方法传入一个动画片段clip和播放状态playstate,它会重置动画片段,并按照传入状态开始播放.
3.  增加Play(playstate)方法,该方法只修改播放状态,不重置动画片段.
4.  增加finish事件,该事件接受void类型无参数的委托,在动画播放结束触发.

这个例子可以到XNA游戏世界论坛里下到。

3.1.                                                                                                                                     
  首先是为了使一个模型使用多段动画,我把动画剪辑信息导出到了一个.clip文件中。

.clip文件:

-------
Brave.fbx
Brave站立不动
416.16:00:00
16
00:00:00
{ {M11:0.9063078 M12:-3.279216E-09 M13:-0.4226182 M14:0} {M21:-7.622232E-09 M22:1 M23:-8.576585E-11 M24:0}
{M31:0.4226182 M32:-4.653076E-10 M33:0.9063078 M34:0} {M41:0.483943 M42:5.205487E-08 M43:1.118171E-08 M44:1} }

----------
这是.clip文件的内容,可以看出保存的数据有:
这个文件适用的模型名称;
这个文件的内容介绍;
动画的持续时间;
变换骨骼的编号;
执行变换的时刻;
变换矩阵;

保存与读取的方法写在了AnimationClip类中:

View Code
      /// <summary>
/// 保存动画剪辑文件
/// </summary>
public void SaveClip(string path)
{
//加上文件名及后缀名
path += Introduce + ".clip";
FileStream fs = new FileStream(path, FileMode.Create);
StreamWriter sw = new StreamWriter(fs, Encoding.GetEncoding("GB2312"));
//写入适用的模型名称
sw.WriteLine(ModelName);
//写入说明
sw.WriteLine(Introduce);
//写入时间
sw.WriteLine(Duration.ToString());
//写入Keyframes
for (int i = 0; i < Keyframes.Count;i++ )
{
sw.WriteLine(Keyframes[i].Bone);
sw.WriteLine(Keyframes[i].Time);
sw.WriteLine(Keyframes[i].Transform);
}
//清空缓冲区
sw.Flush();
//关闭流
sw.Close();
fs.Close();
}

/// <summary>
/// 读取动画剪辑文件
/// </summary>
public static AnimationClip LoadClip(string path)
{
StreamReader sr = new StreamReader(path, Encoding.GetEncoding("GB2312"));
string boneStr;
AnimationClip newClip = new AnimationClip();
//读取适用的模型名称
newClip.ModelName = sr.ReadLine();
//读取介绍
newClip.Introduce = sr.ReadLine();
//读取持续时间
newClip.Duration = TimeSpan.Parse(sr.ReadLine());

while ((boneStr = sr.ReadLine()) != null)
{
//读取骨骼标号
int bone = int.Parse(boneStr);
//读取播放时间
TimeSpan time = TimeSpan.Parse(sr.ReadLine());
//读取变换矩阵
Matrix transform = Keyframe.Parse(sr.ReadLine());

Keyframe keyf = new Keyframe( bone, time, transform);
newClip.Keyframes.Add(keyf);
}
sr.Close();

return newClip;
}
复制代码

 

3.2.                                                                                                                                     
  模型骨骼点跟踪,即在运动中的模型中跟踪某个骨骼的位置,下图的那个三角形蓝色的角所在的点,就是被跟踪的骨骼位置,会随着骨骼的变化而变化。
实现代码是:

 

View Code
            #region 根据骨骼位置,更新Sign位置

Matrix[] bonesWorld = animationPlayer.GetWorldTransforms();
for (int i = 0; i < bonesWorld.Length; i++)
{
//把Sign移动到jointR_Leg4骨骼处。
if (animationPlayer.SkeletonName[i] == "jointR_Leg4")
{
Vector3 v3 = bonesWorld[i].Translation;
sign.Position = Position + v3;
}
}
animationPlayer.RotationRoot(Rotation,0,0);
#endregion
复制代码

 

这里的一个问题是旋转,由于是通过骨骼的位置来跟踪,如果直接对模型进行旋转的话,跟踪位置就会出错,因为模型旋转后骨骼数据并不会发生变化,所以对模型的旋转要改为对模型骨骼的根节点的旋转,代码:

View Code
  /// <summary>
/// 旋转模型(根节点)
/// </summary>
/// <param name="yaw">绕Y-翻滚角</param>
/// <param name="pitch">绕X-倾斜角</param>
/// <param name="roll">绕Z-摇摆角</param>
public void RotationRoot(float yaw, float pitch, float roll)
{
rotation = Matrix.CreateFromYawPitchRoll(yaw, pitch, roll);
}

/// <summary>
/// 更新旋转
/// </summary>
private void UpdateRotationRoot(ref Matrix m)
{
m = m * rotation;
}
复制代码

在AnimationPlayer的Update中调用旋转更新

View Code
      /// <summary>
/// Advances the current animation position.
/// </summary>
public void Update(TimeSpan time, bool relativeToCurrentTime,
Matrix rootTransform)
{
//这里调用更新旋转
UpdateRotationRoot(ref rootTransform);
UpdateBoneTransforms(time, relativeToCurrentTime);
UpdateWorldTransforms(rootTransform);
UpdateSkinTransforms();
}
复制代码

  这样就随时获得一个骨骼的位置,再通过 List<int> BonesHierarchy获得其父骨骼就无敌了~。比如获得手臂上的两个父子骨骼后就可以知道手臂此时所指的方向。

这两个图演示的是脚上骨骼的跟踪~:

 

3.3                                                                                                                                     

  然后还加入一些模型控制比如,平移,旋转,逆放等,写在了SkinningSample.cs的KeyboardControl()与 ModelControl()里。一开始按下‘9’会出现异常,因为‘9’播放的动画是读取.clip文件来的动画,要把路径设置正确。加入了一个模型关节名称的List,和 骨骼层级(父子关系)的List List<string> SkeletonName, List<int> BonesHierarchy,在AnimationPlayer类中。

嗯..第一次写那么长的博文,有什么不足的地方还请各位多多指教。

 

接下来是资源:

http://115.com/file/dpxnombj#

Brave(GNX-Y903VW)模型文件(Maya2012)pose01.rar

http://115.com/file/be8hrraf#

SkinningSample_4_0(管道调试方法).rar


http://115.com/file/e7z6tbs6#

clip文件.rar

转载于:https://www.cnblogs.com/dshGame/archive/2012/01/18/2325156.html

评论将由博主筛选后显示,对所有人可见 | 还能输入1000个字符 “速评一下”
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页