Unity 使用 bvh 驱动骨骼动作

欢迎点击链接跳转 Github Page 博客,享受更舒适的排版界面。

本篇主要介绍如何在 Unity 中使用 bvh 驱动骨骼动画,同时从理论与实操两个方面进行阐述。

代码已打包成 unitypackage,见链接 BVHParser

前言

先简单介绍一些相关的理论基础。

BVH

BVH 文件是使用设备对人体运动进行捕获后产生的文件,它包含角色的骨骼和肢体关节旋转数据,是一种通用的人体特征动画文件格式。

BVH

上图是 bvh 最常记录的骨骼点,图用节点表示关节,连线表示躯干,身体的各个部分形成子树的形式。

BVH 文件的第一部分定义了关节树、每个关节点的名称、关节与关节之间的相对位置(偏移量),即基本骨架。Hips 关节点作为整个人体的根节点,拥有三维空间位置参数,从而完成了对人体运动情况的完整描述。

BVH 文件的第二部分记录了运动的数据,定义了动作数据持续的长度(帧数)以及每帧之间的时间间隔。且按照第一部分定义的关节顺序提供每帧数据,记录了每一帧中各个关节点的位置信息和旋转信息(局部旋转量)。

BVH 文件示例见 Example.bvh

角色姿势

一般来说,角色模型或 BVH 都有它的内置姿势,即创建模型时所设定的姿势。将一个模型所有关节的局部旋转量设置为单位四元数,则可显示出其内置姿势。

在对角色模型或 BVH 的处理中,通常涉及到三种姿势:A 型姿势(A-Pose)、T 型姿势(T-Pose)和其它姿势,其中 A-Pose 和 T-Pose 通常作为内置姿势或第一帧骨骼姿势。

驱动理论

了解了一些基础理论,接下来就来介绍一下 bvh 驱动的基本原理。

由于 bvh 的骨架与 unity 所使用和展示的骨架差异较大,因此仅赋值是无法实现需求的。但不管是 bvh 还是 unity 骨架,大多数都是基于 Tpose 的,且不同骨架的 Tpose 姿势一致,因此我们利用 Tpose 作为媒介,将 bvh 中的所有动画帧迁移至 unity 中。

接下来的推证,前提是 bvh 的第一帧是 Tpose

转换流程

CSDN图床炸了

理解一下这个图。起初我们所拿到的数据是,Unity 模型的骨骼点信息和 BVH 每一帧动画关节点的局部旋转量;要实现的是,求出图中矩阵 T 5 T_{5} T5,应用到 Unity 每个关节的旋转量上,使 Unity 模型展示出相应动作。

事实上,将该图命名为流程图不太合适,可以理解成 T 5 T_{5} T5求解过程吧。

先看 BVH。将 BVH 某一帧的所有关节点旋转量乘上矩阵 T 2 T_{2} T2,变换成 Tpose,再乘上 T 4 T_{4} T4,变化为相应动画。之所以这样做,是为了拿出 Tpose 这个中间媒介。Unity 和 BVH 骨架的 Tpose 姿势一致,想要展示的动作也一致,因此 T 4 T_{4} T4 作用于 Unity 的 Tpose上,可以在 Unity 模型上展示出动作。

这样的话,求解 T 5 T_{5} T5 仅需再求出 T 1 T_{1} T1 即可实现,我们下面具体介绍一下求解原理。

矩阵求解

变换矩阵 T 1 T_{1} T1/ T 2 T_{2} T2

通常而言,CMU 等提供的 BVH 动画,第一帧通常就是 Tpose,Unity 导入模型后通常也是 T 型姿势,因此无需复杂的转换。

但需要注意,我们所获得的数据信息,是骨骼节点相对于父节点的局部旋转量,需要使用下面公式将其转换为全局旋转矩阵 (Rotation)。

R i = R p × r i R_{i}=R_{p}\times r_{i} Ri=Rp×ri

其中, R i R_{i} Ri 为所求的当前节点的全局旋转(四元数), R p R_{p} Rp 为父节点的全局旋转, r i r_{i} ri 为当前节点的局部旋转。

使用上式将 Unity 模型和 BVH 第一帧的局部旋转转化为全局旋转,则得到了矩阵 T 1 T_{1} T1 T 2 T_{2} T2

变换矩阵 T 3 T_{3} T3

BVH 中记录了完整的动画数据,我们可以根据它们计算出每一帧所有关节的坐标位置 (Position)。

在 BVH 中,根节点比其它节点多了个位置信息,根据根节点的位置信息 Root Position、关节层次关系 Hierachy、各节点相对父节点的偏移量 Offset (BVH 初始姿势)和每一帧节点旋转量,就可以推算出所有关节的坐标位置:

P o s i = P o s p + R p × O f f s e t i Pos_{i} = Pos_{p} + R_{p} \times Offset_{i} Posi=Posp+Rp×Offseti

其中, P o s i Pos_{i} Posi 为当前关节点坐标, P o s p Pos_{p} Posp 为父节点坐标, R p R_{p} Rp 为父节点全局旋转, O f f s e t i Offset_{i} Offseti 为当前节点相对父节点的偏移量 (Vector3)。

变换矩阵 T 4 T_{4} T4

前面我们说过, T 4 T_{4} T4 是迁移的关键矩阵,计算出它,我们就能计算出 T 5 T_{5} T5

观察流程图,我们可以推出:

T 2 × T 4 = T 3 T_{2}\times T_{4} = T_{3} T2×T4=T3

T 4 = T 2 − 1 × T 3 T_{4} = T_{2}^{-1} \times T_{3} T4=T21×T3

变换矩阵 T 5 T_{5} T5

Tpose 姿势一致,动画效果一致,因此 T 4 T_{4} T4 可作用于 Unity 的 Tpose 上。故我们可求出 T 5 T_{5} T5

T 5 = T 1 × T 4 = T 1 × T 2 − 1 × T 3 T_{5} = T_{1} \times T_{4} = T_{1} \times T_{2}^{-1} \times T_{3} T5=T1×T4=T1×T21×T3

位置调整

上述矩阵都是作用于各关节点的旋转量上的,但动画除此之外还有根节点的位置,通过调整它来调整人物的位置。

因此 BVH 的人物大小和 Unity 模型大小不同,所以我们通常根据某根骨骼的长度计算缩放比例,然后对 BVH 的根节点位置乘以缩放比例,就得到 Unity 根节点的位置了。

P o s r ( u n i t y ) = P o s r ( b v h ) × S c a l e Pos_{r}^{(unity)} = Pos_{r}^{(bvh)} \times Scale Posr(unity)=Posr(bvh)×Scale

代码实现

前面讲解了 BVH 驱动相关原理,接下来大致讲述一下核心代码实现。项目代码见 BVHParser

核心代码

获取关节父子关系

public Dictionary<string,string> getHierachy()
{
    Dictionary<string, string> hierachy = new Dictionary<string, string>();
    foreach (BVHBone bb in boneList)
    {
        foreach (BVHBone bbc in bb.children)
        {
            hierachy.Add(bbc.name, bb.name);
        }
    }
    return hierachy;
}

欧拉角转四元数

注意所用的 bvh 数据是否是 ZYX 顺序,若不是,需要根据 bvh 的顺序修改函数参数顺序。

private Quaternion eul2quat(float z, float y, float x)
{
    z = z * Mathf.Deg2Rad;
    y = y * Mathf.Deg2Rad;
    x = x * Mathf.Deg2Rad;

    // 动捕数据是ZYX,但是unity是ZXY
    float[] c = new float[3];
    float[] s = new float[3];
    c[0] = Mathf.Cos(x / 2.0f); c[1] = Mathf.Cos(y / 2.0f); c[2] = Mathf.Cos(z / 2.0f);
    s[0] = Mathf.Sin(x / 2.0f); s[1] = Mathf.Sin(y / 2.0f); s[2] = Mathf.Sin(z / 2.0f);

    return new Quaternion(
        c[0] * c[1] * s[2] - s[0] * s[1] * c[2],
        c[0] * s[1] * c[2] + s[0] * c[1] * s[2],
        s[0] * c[1] * c[2] - c[0] * s[1] * s[2],
        c[0] * c[1] * c[2] + s[0] * s[1] * s[2]
    );
}

获取关键帧的全局旋转数据

public Dictionary<string,Quaternion> getKeyFrame(int frameIdx)
{
    Dictionary<string, string> hierachy = getHierachy();
    Dictionary<string, Quaternion> boneData = new Dictionary<string, Quaternion>();
    boneData.Add("pos", new Quaternion(
        boneList[0].channels[0].values[frameIdx],
        boneList[0].channels[1].values[frameIdx],
        boneList[0].channels[2].values[frameIdx],0));

    boneData.Add(boneList[0].name, eul2quat(
        boneList[0].channels[3].values[frameIdx],
        boneList[0].channels[4].values[frameIdx],
        boneList[0].channels[5].values[frameIdx]));
    foreach (BVHBone bb in boneList)
    {
        if (bb.name != boneList[0].name)
        {
            Quaternion localrot = eul2quat(bb.channels[3].values[frameIdx],
                                           bb.channels[4].values[frameIdx],
                                           bb.channels[5].values[frameIdx]);
            boneData.Add(bb.name, boneData[hierachy[bb.name]] * localrot);
        }                
    }            
    return boneData;
}

获取 BVH 初始姿势每个关节相对于父关节的偏移量

public Dictionary<string,Vector3> getOffset(float ratio) {
    Dictionary<string, Vector3> offset = new Dictionary<string, Vector3>();
    foreach(BVHBone bb in boneList)
    {
        offset.Add(bb.name, new Vector3(bb.offsetX * ratio, bb.offsetY * ratio, bb.offsetZ * ratio));
    }
    return offset;
}

获取 BVH 的全局旋转,即 T 2 T_{2} T2

bvhT = bp.getKeyFrame(0);

计算 T 5 T_{5} T5

foreach (BoneMap bm in bonemaps)
{
    Transform currBone = anim.GetBoneTransform(bm.humanoid_bone);
    currBone.rotation = (currFrame[bm.bvh_name] * Quaternion.Inverse(bvhT[bm.bvh_name])) * unityT[bm.humanoid_bone];
}

注意事项

  • 确保 bvh 动捕数据第一帧为 Tpose
  • 运行时 Scene 界面用红线画出了 bvh 动作骨架,可用以鉴别 bvh 动作是否导入成功
  • 若使用的 bvh 文件的旋转量不是 ZYX 顺序,请相应修改 BVHParser.cs 中的 eul2quat 函数,一般只需调换该函数参数顺序即可

总结

本文先简要介绍了几个基础知识 BVH 和角色姿势,后阐述了 BVH 驱动动作生成的理论(以 Tpose 为中间媒介求解转换矩阵),并使用代码实现。

项目地址见 BVHParser

参考

[1] Unity 中 BVH 骨骼动画驱动的可视化理论与实现 - CSDN

[2] BVH - 百度百科

  • 8
    点赞
  • 63
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
### 回答1: Unity是一款流行的游戏引擎,支持多种文件格式的导入,其中包括bvh文件格式。bvh文件格式是一种用于记录人体骨骼动作的文件格式,通常用于动画制作。 在Unity中导入bvh文件非常简单。首先,在Unity中创建一个新项目,并导入需要的人体模型。其次,打开导入人物模型的编辑器,在编辑器中选择导入bvh文件的选项。接下来,选择需要导入的bvh文件,并按照提示完成导入过程。在导入过程中,Unity会自动将bvh文件中的骨骼动作信息应用到导入的人体模型上。 在导入完毕后,可以使用Unity的动画编辑器来查看和编辑导入的骨骼动作。除了默认的动画编辑器,还可以使用一些第三方插件来更好地控制和编辑动画。此外,如果觉得需要,还可以利用Unity"动作匹配器"和"动作融合器"的功能,来组合和混合多个动画。 总之,Unity支持bvh文件的导入,使得人物动画制作变得更加容易和高效。通过导入和编辑bvh文件,人物动画制作者可以创造出更加生动、精彩的人物动作,从而提高游戏的质量和用户体验。 ### 回答2: Unity是一款非常强大的游戏引擎,可以实现3D场景的构建和游戏物体的操作。而导入bvh文件也是其中的一个非常重要的功能,这可以非常方便地实现角色动作的导入和应用。 首先,在Unity的资源管理器中选择需要导入bvh文件的对象,在其属性面板中选择“导入”的选项。随后选择bvh文件,并进行导入操作。 接下来,在导入后的动画对象上,我们可以进行各种不同的操作,比如修改动画的播放速度、添加新的动画片段、或者对动画进行差值和编辑。 需要注意的是,在导入bvh文件时,Unity会尝试将其重新调整为匹配当前场景的大小和比例。因此,有时候可能需要手动对动画进行一些微调和编辑,以实现最佳效果。 总之,使用Unity导入bvh文件是一项非常重要且实用的功能,可以让我们更加轻松和高效地创建精美的动画效果。 \end{cn} ### 回答3: Unity是一款流行的游戏引擎,可以用于开发各种类型的游戏。导入BVH文件是Unity中常见的任务,这种文件格式通常用于描述人体运动数据。以下是关于如何在Unity中导入BVH文件的一些提示。 首先,Unity可以通过使用第三方插件来导入BVH文件。有许多免费或付费的插件可供选择,例如"BVH Importer"或"FinalIK"。在查找和选择插件之前,要确保已经下载并安装了最新的Unity版本。 安装插件后,需要准备BVH文件。可以使用3D建模软件如Maya、Blender或MotionBuilder来创建或修改此文件。通常需要确保文件符合常见的BVH标准格式,例如正确的帧速率、帧数量、层次结构和骨骼命名。 导入过程与导入其他文件类型相似: 打开Unity,创建一个新项目或打开现有项目,然后从文件选项中选择导入功能。选择正确的插件和BVH文件后,将需要进行一些设置,例如指定是否移动,旋转或缩放人物模型。 成功导入BVH文件后,Unity会生成一个包含BVH数据的动画片段。可以通过将这段动画片段附加到人物模型上,使模型运动引擎和动画数据配合工作。 总之,在Unity中导入BVH文件需要使用适当的插件,并通过创建和配置人物模型来准备文件。成功导入后,BVH数据可用于实现复杂的人体动作和其他交互式游戏元素。
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值