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

前言

找了很久使用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=T3T21

变换矩阵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)]

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/246557.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

卡通驱动项目ThreeDPoseTracker——模型驱动解析

前言 之前解析过ThreeDPoseTracker这个项目中的深度学习模型&#xff0c;公众号有兄弟私信一些问题&#xff0c;我刚好对这个项目实现有兴趣&#xff0c;就分析一波源码&#xff0c;顺便把问题解答一下。 这个源码其实包括很多内容&#xff1a;3D姿态估计&#xff0c;坐标平滑…

卡通驱动项目ThreeDPoseTracker——关键点平滑方案解析

前言 之前对ThreeDPoseTracker的深度学习模型和unity中的驱动方法进行过解析&#xff0c;还有一个比较重要的就是从深度学习模型出来的3D关键点数据会有抖动&#xff0c;在ThreeDPoseTracker源码中有做两次平滑&#xff0c;一部分是卡尔曼滤波&#xff0c;还有一部分是低通滤波…

卡通角色表情驱动系列一

前言 分析完ThreeDPoseTracker来做卡通角色的身体驱动&#xff0c;接下来在卡通驱动领域还有一个是表情驱动。对这个真的是一窍不通啊&#xff0c;只能慢慢看论文了。 国际惯例&#xff0c;参考博客/论文&#xff1a; 《Landmark-guided deformation transfer of template f…

opencv相机标定和人头姿态估计案例

前言 头部驱动除了之前关注的表情驱动外&#xff0c;还有眼球驱动和头部方向驱动。本博客基于opencv官方文档和部分开源代码来研究如何基于人脸关键点获取头部的朝向。 国际惯例&#xff0c;参考博客&#xff1a; opencv:Camera Calibration and 3D Reconstruction opencv:…

卡通角色表情驱动系列二

前言 之前介绍了使用传统算法求解BS系数的表情驱动方法&#xff0c;其中提到过的三种方法之一是基于网格形变迁移做的&#xff0c;那么这篇文章就是对《Deformation Transfer for Triangle Meshes》做表情驱动的解析。 国际惯例&#xff0c;参考博客&#xff1a; 论文原文《…

UE自带重定向原理

UE自带重定向方法验证 核心源码在VS的解决方案中的位置&#xff1a; UE4\Source\Developer\AssetTools\Private\AssetTypeActions\AnimSequence.cpp中第3237行RemapTracksToNewSkeleton函数 跳转方法 AssetTypeActions_AnimationAsset.cpp的RetargetNonSkeletonAnimationHa…

【caffe-Windows】caffe+VS2013+Windows无GPU快速配置教程

前言 首先来一波地址&#xff1a; happynear大神的第三方caffe&#xff1a;http://blog.csdn.net/happynear/article/details/45372231 Neil Z大神的第三方caffe&#xff1a;https://initialneil.wordpress.com/2015/01/11/build-caffe-in-windows-with-visual-studio-2013-…

【caffe-Windows】caffe+VS2013+Windows+GPU配置+cifar使用

前言 国际惯例&#xff0c;先来波地址&#xff1a; CUDA WIN7&#xff1a;链接&#xff1a;http://pan.baidu.com/s/1nvyA3Qp 密码&#xff1a;h0f3 官方网址&#xff1a;https://developer.nvidia.com/cuda-toolkit CUDA WIN10:链接&#xff1a;http://pan.baidu.com/s/1…

【一些网站的收集】包含机器学习深度学习大牛主页等

数学概念部分 旋转矩阵、欧拉角、四元数的比较 欧拉角和四元数的表示 四元数与旋转 B样条曲线 非常好的概率统计学习的主页 误差方差偏差 编程语言学习 C#编程视频 OpenGL编程NeHe OpenGL官网 OpenGL“我叫MT“纯手工3D动画制作之1——基础介绍 【强大】非常好的Op…

Eureka源码分析

Eureka源码分析 Eureka server入口: Spring.factories PS: 意味着如果加载EurekaServerAutoConfiguration成功,需要 ConditionalOnBean(EurekaServerMarkerConfiguration.Marker.class)需加载成功. 通过Bean注入了很多类 本质上, eureka-server包含很多事件: EurekaInstanceC…

matlab程序中,如何解决矢量长度必须相同的问题

主要原因就是画图的x和y长度不一样&#xff0c;我用一个例子说明。 问题代码&#xff1a; clear all;close all;clc;x 0 : 1: 9;y sin(x);n 2*length(x);yi interpft(y, n);xi 0 : 0.5 : 10;hold on ;plot(x, y ,ro);plot(xi, yi, b.-);plot(x, sin(x),m--);legend(原始…

matlab 功率谱分析函数psd用法

psd简介 PSD(power spectrum analysis)功率谱分析&#xff0c;PSD在给定频带上的积分计算信号在该频带上的平均功率。与均值-平方谱相反&#xff0c;这个光谱中的峰值并没有反映出给定频率的能量。 单边PSD包含了信号的总功率在频率间隔从DC到一半的奈奎斯特速率。双侧PSD包含…

linux tar (打包、压缩、解压)命令

打包程序&#xff1a;tar c: 创建文档t&#xff1a; 列出存档内容x&#xff1a;提取存档f&#xff1a; filename 要操作的文档名v&#xff1a;详细信息 一&#xff1a;打包 打包&#xff1a;是指把文件整合在一起&#xff0c;不压缩 1.将文件打包&#xff1a;tar cf a.tar…

虚拟机添加硬盘扩容

1.设置→添加→硬盘 2.选择磁盘类型 3.开启虚拟机 4.用ls 命令查看&#xff1a;ls /dev/sd* 5.最后就可以对sdb进行分区操作 这里好麻烦&#xff0c;等我有空&#xff0c;在补上&#xff01; . . .

利用matlab将三维数据画成三维立体图

首先先分析对象。将数据利用matlab画出图&#xff0c;最开始是导入数据&#xff0c;然后处理数据&#xff0c;最后将处理的数据画出来。 所以我将它分为三个步骤。 第一步&#xff1a;导入数据 如果是mat数据。可以直接load如果是txt数据。可以用txtread如果是excel数据。可…

世界坐标、相机坐标、图像坐标、像素坐标的原理、关系,并用matlab仿真

世界坐标、相机坐标、图像坐标、像素坐标的原理、关系&#xff0c;并用matlab仿真 照相机是日常生活中最常见的。它能把三维的空间图片等比例缩小投影在照片上&#xff0c;称为一个二维图像。 以下我们就讲一讲原理&#xff0c;并相应的进行matlab仿真。 在学之前&#xff0…

matlab 三维高程根据图片颜色给对应点赋予颜色

目录 1. 问题分析 2. 技术分析 3. 程序代码 4. 代码运行结果 1. 问题分析 日常工作尤其是测绘、地质、遥感行业&#xff0c;需要画DEM模型&#xff0c;并在这个模型的基础上&#xff0c;进行着色、渲染。比如&#xff0c;地质分析地面三维地表形变之时&#xff0c;需要根据D…

matlab 计算N天前(后)的日期

注意时间的格式&#xff1a;是字符串、数字还是日期&#xff1f; 下面是计算明天、今天、昨天的日期。 day1 datetime(datestr(now,yyyy-mm-dd))caldays(1)%tomorrowday0 datetime(datestr(now,yyyy-mm-dd))%todayday_1 datetime(datestr(now,yyyy-mm-dd))-caldays(1)%yest…

CAT arguments dimensions are not consistent.CAT参数的维度不一致。

错误实例&#xff1a; 在写符号矩阵的时候常常会出现下面错误&#xff1a; 错误&#xff1a;CAT arguments dimensions are not consistent. CAT参数的维度不一致。 举个栗子&#xff1a; clear; close all; clc; syms A_0 B_0 B C D E F G H ;T_3 [2*A_0 C-D*1i H G*1i;C…

传感器尺寸、像素、DPI分辨率、英寸、毫米的关系

虽然网上有很多这种资料&#xff0c;但是太过于复杂&#xff0c;每个人的说法都不一样&#xff0c;看的让人云里雾里的&#xff0c;我总结了一下&#xff0c;不知道对不对&#xff01; 1. 1英寸25.4mm 2. 传感器尺寸&#xff1a;传感器的尺寸是指传感器的大小&#xff0c;一般…