前言

找了很久使用BVHunity中驱动骨骼动画的代码,但是都不是特别好用,自己以前写过,原理很简单,这里记录一下。

理论

初始姿态

在BVH或者其它骨骼动画中,一般涉及到三种姿势:A-pose,T-pose,其它姿势。其中A-pos或者T-pos通常是作为骨骼定义的姿势或者第一帧骨骼姿势。

比如在unity中,导入某个模型时,通常为T-pose的姿态,如unity娘模型刚导入的时候:

这个Tpose在CMU提供的BVH骨骼动画数据中,第一帧数据也是T-pose,比如:

但是在Maya中设计角色,或者做服装绑定的时候,有些模型需要保持Apose的姿态,如:

驱动理论

因为使用BVH数据在unity中驱动角色,而unity中的角色大部分是基于Tpose的,因而可以利用Tpose作为中间转换姿态,将BVH中的所有动画帧全部迁移到unity中。

unity动作模型必须有与BVH一致的动作,否则不适用此博客的理论,如果unity角色与BVH角色的一致动作非Tpose,也可以按照博客理论去转换。

整个转换流程如下图所示

为了解释上图,我们使用以下数据为示例:

  • BVH数据:CMU动捕数据BVH格式
  • Unity数据:Unity-Chan! Model

内置姿态

所谓内置姿态,即模型的所有骨骼旋转量为0,直接可视化其关节位置得到的结果

BVH的内置姿态可通过bvhackersetT按钮可视化(或者对我比较熟悉的人知道我前面关于动捕的博客有提供可matlab版本的可视化代码):

unity-chan的内置姿态可视化就需要通过代码将所有的关节旋转量设置为单位四元数,得到所有

变换矩阵T1/T2

这一步在BVHunity中都涉及到一个变换矩阵,如果内置姿态本来就是Tpose,那么变换矩阵就是单位阵。如果不是,那么也可以直接获取到。

CMU提供的BVH骨骼动画中,第一帧通常就是Tpose,所以直接计算出第一帧每个关节的全局旋转矩阵就是对应的Tpose变换矩阵T1,记住:
当前关节全局旋转=父关节全局旋转×当前关节局部旋转当前关节全局旋转=父关节全局旋转\times 当前关节局部旋转 当前关节全局旋转=父关节全局旋转×当前关节局部旋转
unity中骨骼动画因为导入以后通常就是T姿势,所以直接获取所有关节最开始的全局旋转矩阵就是对应的Tpose变换矩阵T2

变换矩阵T3

BVH中记录了很多动画数据,这些数据都以欧拉角的形式存储,其中根关节额外多了一个位置信息,按照根关节的坐标信息、关节层级关系、关节相对父关节偏移量(BVH初始姿态)、关节局部旋转量就能推出所有关节的坐标位置:
当前关节坐标=父关节坐标+父关节全局旋转∗当前关节相对于父关节定义的偏移量当前关节坐标=父关节坐标+父关节全局旋转*当前关节相对于父关节定义的偏移量 当前关节坐标=父关节坐标+父关节全局旋转∗当前关节相对于父关节定义的偏移量
简而言之:这个T3就是BVH所记录的所有动画帧的根关节坐标以及各关节的旋转数据。

变换矩阵T4

因为我们是以Tpose为媒介,将BVH动作迁移到unity中,这个T4能够达到这种效果:
BVHT×T4=UNITYT×T4\text{BVH}_T\times T4 = \text{UNITY}_T\times T4 BVHT​×T4=UNITYT​×T4
注意上面的=代表姿势相等,不是关节坐标相等。

怎么找到这个T4呢,非常简单,这样想:先将内置姿态通过T1变换成Tpose,然后再通过T4转换成动画姿态,那么原始BVH记录的全局旋转量就应该等于T4T2的累计旋转量。
T3=T4×T2T3 = T4\times T2 T3=T4×T2
所以
T4=T3∗T2−1T4=T3*T2^{-1} T4=T3∗T2−1

变换矩阵T5

这个就是我们最终需要应用到unity每个关节的旋转数据
T5=T4×T1T5 = T4\times T1 T5=T4×T1

位置调整

准确来说还有一步是调整人体的位置,因为BVH的人物大小和unity的人物大小不同,所以可以根据某根骨骼的长度计算一下缩放比例,然后对BVH的根关节位置乘以对应缩放比例就是unity人物的对应位置了。

实现

完整代码在github中获取,公众号和CSDN都有写地址。

使用BVHTool这个工程里面读取BVH数据的代码以及对应的数据结构进行后续开发。

核心代码有:

  • 获取关节父子关系:

     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记录的,如果不是这个,请自行书写转换代码,但是一定要转成全局旋转量即可):

    private Quaternion eul2quat(float z, float y, float x)
    {z = z * Mathf.Deg2Rad;y = y * Mathf.Deg2Rad;x = x * Mathf.Deg2Rad;// 动捕数据是ZYX,但是unity是ZXYfloat[] 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;
    }
    
  • 获取骨骼定义时候,每个关节相对于父关节的偏移量:

    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姿态变换矩阵(如果你的第一帧不是T,内置姿态就是T,那么这个变换矩阵就是单位阵)

    bvhT = bp.getKeyFrame(0);
    
  • 根据BVH的Tpose和BVH其它动画帧的旋转量,以及unity的Tpose变换矩阵,求解unity驱动所需的全局旋转:

    if (FirstT)
    {Transform currBone = anim.GetBoneTransform(bm.humanoid_bone);currBone.rotation = (currFrame[bm.bvh_name] * Quaternion.Inverse(bvhT[bm.bvh_name])) * unityT[bm.humanoid_bone];
    }
    else
    {Transform currBone = anim.GetBoneTransform(bm.humanoid_bone);currBone.rotation = currFrame[bm.bvh_name] * unityT[bm.humanoid_bone];
    }
    

    FirstT代表BVH数据第一帧是Tpose,否则内置姿态为Tpose。在工程中提供了temp.bvh13_29.bvh分别代表内置T和第一帧Tbvh数据例子。

    代码运行结果:

红色为BVH可视化,unity-chan为驱动结果。

后记

理论超级简单,不过需要注意,BVH全局旋转的计算一定要正确,不同动捕设备定义的旋转轴顺序不同。

完整的unity实现放在微信公众号的简介中描述的github中,有兴趣可以去找找,同时文章也同步到微信公众号中,有疑问或者兴趣欢迎公众号私信。

[外链图片转存中…(img-FEg8MV6D-1610182566145)]

Unity中BVH骨骼动画驱动的可视化理论与实现相关推荐

  1. Unity 2D教程 | 骨骼动画:创建动画

    转载自:2016-02-13 Unity官方平台 本教程主要讲解Unity引擎自带的2D骨骼动画工具,以及2D动画的基本概念.本篇会添加一些动画,如默认状态.跳动.坠落等. 基础动画理论 制作动画要牢 ...

  2. Unity中的Animator动画详解

    Unity中的Animator动画详解 Animator动画导入 Animator动画详解 动画类型选择 Rig面板属性 Mode面板属性 Animation面板属性 动画片段 控制使用 Animat ...

  3. 如何在unity中使用龙骨动画

    龙骨 龙骨是Egret公司的一个用来做动画的软件,本文分享一下如何在Unity2D中使用龙骨导出的2D动画 导出动画 在龙骨中文件->导出,导出动画数据和纹理到Unity的项目中,如果打包了的话 ...

  4. Cocos2d-x 3.8.1+Cocos Studio 2.3.2捉虫记之控制场景文件中的骨骼动画

    Cocos2d-x 3.8.1+Cocos Studio 2.3.2捉虫记之控制场景文件中的骨骼动画 引子 这段时间一直努力在把早期版本的拇指接龙游戏(Cocos2d-x 2.2.3+CocoStud ...

  5. 在Unity中如何制作动画

    场景: 在Unity中如何制作动画 方法 Unity中的动画主要是三种来源: 在Unity引擎中制作动画 从外部工具(如3ds Max.Maya等)导入动画 使用代码制作的动画 下面讨论的主要是前两种 ...

  6. Unity中Animator播放动画后无法修改transform的问题

    本文分享Unity中Animator播放动画后无法修改transform的问题 在使用Animator时, 如果某些动画状态设计到transform的改动, 比如位置, 缩放等, 在默认情况下我们就不 ...

  7. [Unity][FairyGUI]场景中龙骨骨骼动画设置播放

    Spine和DragonBones都是FairyGUI可以显示的骨骼动画. DragonBones骨骼动画显示. 首先得在Unity项目中配置 DragonBones的SDK. 在FairyGUI编辑 ...

  8. unity重定向_Unity 骨骼动画的运行时重定向

    上次我们说到在 IKinema 中添加自定义模型.方法很简单,就是利用运行时重定向,这次为大家介绍一下具体做法. Unity 中的重定向(retargeting)大家应该不陌生,重定向使得 Human ...

  9. swiftui动画之tab自定义切换动画_Unity动画系统详解1:在Unity中如何制作动画?

    摘要:在场景中加入动态的物体,可以让整个场景更加生动.真实.Unity场景中的物体可以通过制作动画,让物体动起来.简单的动画如物体的移动.旋转(比如旋转的风扇.闪烁不定的灯泡等),复杂的动画如游戏中角 ...

最新文章

  1. Spring中AOP相关的API及源码解析,原来AOP是这样子的
  2. java 栈内存结构_JVM内存结构概念解析
  3. 一步一步写算法(之克鲁斯卡尔算法 中)
  4. __declspec(dllimport)
  5. 笨方法“学习python笔记之关键字
  6. android string.format()长度,Android通过String.format格式化(动态改变)字符串资源的显示内容...
  7. 首页 、引导页、版本
  8. 【汇编】汇编程序入门篇
  9. AC双链路冷备份详解及配置原理
  10. Struts2实现通过浏览器返回一个helloworld页面给用户
  11. android中常用的宏,iOS-开发中常用的宏有哪些?
  12. 如何安装pygame模块
  13. LMDB内存映射型数据库
  14. 巨杉数据库兼容mysql_SequoiaDB 巨杉数据库
  15. ios 获取芝麻信用分
  16. 计算机应用技术与物联网专业介绍,《物联网应用技术》专业简介
  17. 数据结构1800关于图的代码精选(二)
  18. 【Vue3】vue3全解
  19. 微信群视频代理服务器,在微信群里发大于20M的视频,方法竟然这么简单
  20. 拿到新电脑后进行的一些配置,装软件

热门文章

  1. java增删改查_Java程序员你真的甘心只做增删改查吗
  2. 开放下载 | 阿里妈妈技术年货来啦!
  3. 服务器任务管理器详细信息,任务管理器服务器主机
  4. c# 计算空格宽度像素_Adv. Mater:2D材料缺陷工程实现全光控存储和神经形态计算...
  5. 2017计算机软件考试试题,2017年计算机应用考试试题含答案.doc
  6. php本地怎么接受小程序图片,怎么把本地图片当作小程序背景
  7. [机器学习] 分类 --- Naive Bayes(朴素贝叶斯)
  8. 稀疏组套索_Python中的稀疏组套索
  9. 背账100万,不付利息不用还钱,银行套路好深
  10. 如果白百何夫妻离婚了,房贷该谁来还?