Unity动态创建Avatar骨骼映射

news2025/7/17 20:17:16

目录

  • 前言
  • 1 了解Avatar骨骼映射
  • 2 动态创建Avatar骨骼映射
    • 2.1 寻找相关API
      • AvatarBuilder.BuildHumanAvatar
        • Declaration
        • Parameters
        • Returns
        • Description
      • HumanDescription
        • Description
        • Properties
    • 2.2 创建Skeleton数据
    • 2.3 创建Human映射关系
    • 2.4 创建Avatar
  • 3 总结

前言

为了让单个动画可以通用于多个不同的人型模型上,Unity官方开发了一套骨骼重定向系统,把不同人型模型的骨骼映射到一套通用的骨骼映射上,然后再让动画去驱动这个通用的骨骼映射,从而实现驱动不同的模型。不过目前只支持人型模型,只需要把模型导入到Unity,就能自动生成骨骼映射(在Unity里是一个Avatar文件)。但是,并不是所有的模型格式导入到Unity都能自动生成这个Avatar,例如Glb/Gltf格式。为了让这些格式的模型也能支持通用的动画,通常需要放到一些建模软件里进行操作再导出为Fbx格式,这个过程是复杂且痛苦的。还有一种更无可奈可的情况是:完整人型模型是由各个模型在运行时动态组装生成的,并没有一个完整的模型可以导入建模软件进行操作。因此,我们应该寻找一种方式,使其可以在Unity运行阶段创建Avatar的骨骼映射。

1 了解Avatar骨骼映射

为了动态去创建这个Avatar文件,我们首先需要了解它包含了哪些内容,我们打开一个Unity已经创建好的Avatar:
在这里插入图片描述

可以看到,这个文件主要保存的是骨骼的映射关系。上面的人体里的每一个圆点代表着一个关节点,Optional Bone下面左边那一列就是Unity里设定好的关节点的名称,它们在每个Avatar文件里都是一样的,而右边部分就是当前这个模型的骨骼节点,Unity已经帮我们映射好了它们与通用人型骨骼的对应关系。当我们把这个Avatar映射文件赋值给Animator后,Animator就会去驱动固定的那些骨骼信息点,而这些固定的骨骼信息点就会去根据它们和模型真实骨骼的映射关系找到真正需要驱动的骨骼点,从而对其进行驱动,最终整个模型就动起来了。

2 动态创建Avatar骨骼映射

2.1 寻找相关API

由于通常情况下模型导入时unity就能帮我们创建好这个Avatar映射,我们根本就没真正接触这个过程,因此首先需要去查询官方是否有开放相关接口。幸运的是,确实有相关的接口,官方API描述如下:

AvatarBuilder.BuildHumanAvatar

Declaration

public static Avatar BuildHumanAvatar(GameObject go, HumanDescription humanDescription);

Parameters

go
humanDescription

Returns

Avatar Returns the Avatar, you must always always check the avatar is valid before using it with Avatar.isValid.

Description

Create a humanoid avatar.
The avatar is created using the supplied HumanDescription object which specifies the muscle space range limits and retargeting parameters like arm/leg twist and arm/leg stretch. See Also: HumanDescription.

从API中我们可以得知,使用它需要传入两个参数,第一个参数好理解,就是我们当前这个模型本身,第二个参数是个描述数据,我们再看看它的定义:

HumanDescription

struct in UnityEngine/Implemented in:UnityEngine.AnimationModule

Description

Class that holds humanoid avatar parameters to pass to the AvatarBuilder.BuildHumanAvatar function.

Properties

armStretch
feetSpacing
hasTranslationDoF
human
legStretch
lowerArmTwist
lowerLegTwist
skeleton
upperArmTwist
upperLegTwist

它具有一堆属性,光看这里我们也不知道其如何赋值,但恰好这些数据在上面的Avatar映射文件里见过:
在这里插入图片描述

可以看到,都是对骨骼的一些限制,大部分直接默认值即可,但有两个属性是图上没显示的,也就是hunman和skeleton这两个属性,但是从描述可以知道,hunman保存的刚好就是骨骼映射关系,而skeleton保存的是模型的骨骼集合,因此我们只需要创建出这两个属性需要的数据即可。

2.2 创建Skeleton数据

由于skeleton保存的是模型的骨骼集合,因此比较好操作,我们先把它创建起来,代码也很简单,就是把模型的所有Transform都收集起来即可。即使不是骨骼的Transform也无所谓,后面会根据映射关系从这些骨骼中找出对应的骨骼Transform。因此我们直接写代码:

  private static SkeletonBone[] CreateSkeleton(GameObject avatarRoot)
    {
        List<SkeletonBone> skeleton = new List<SkeletonBone>();
       
        Transform[] avatarTransforms = avatarRoot.GetComponentsInChildren<Transform>();
        foreach (Transform avatarTransform in avatarTransforms)
        {
            SkeletonBone bone = new SkeletonBone()
            {
                name = avatarTransform.name,
                position = avatarTransform.localPosition,
                rotation = avatarTransform.localRotation,
                scale = avatarTransform.localScale
            };

            skeleton.Add(bone);
        }
        return skeleton.ToArray();
    }

代码很简单,就是传入当前的模型,然后获取模型所有的Transform组件,然后把数据赋值给SkeletonBone即可再添加到集合中即可。

2.3 创建Human映射关系

这里的映射关系本来是在建模软件里做的,那我们如何能知道它们之间的关系呢?有两个方法:

  • 1 询问CP同学得知这个模型的基础骨骼和Avatar的对应关系,简单快捷高效
  • 2 自己根据Transform的名称和节点在模型的位置来猜测对应关系,可以在视图里对比,这样低效且不一定正确,可能需要多次尝试才能找到关系
    其实还有第三种方法:写算法去自动匹配,但是会比较困难,毕竟导入的模型不一定就是标准的T Pose,而且计算也不是那么好写的,感兴趣的大佬可以去尝试一下,到时候成功了麻烦教教我~
    从上面我们千辛万苦终于得到了映射关系,我这里是使用一个Dictionary来保存,大概如下:
 public static Dictionary<string, string> HumanSkeletonMap = new Dictionary<string, string>()
        {
            {"pelvis", "Hips" },
            {"spine_01", "Spine" },
            {"spine_02", "Chest"},
            {"spine_03", "UpperChest" },
						...
            ...此处省略了一堆key-value...
						...
            {"neck_01", "Neck" },
            {"head", "Head" },
            {"eye_EyeJoint_L", "LeftEye" },
            {"eye_EyeJoint_R", "RightEye" },
            {"mouth_JawJoint_M", "Jaw" },
        };

但是看了一下上面映射属性的数据结构是HumanBone,我们还需要写个函数去做映射,代码如下:

    private static HumanBone[] CreateHuman(GameObject avatarRoot)
    {
        List<HumanBone> human = new List<HumanBone>();

        Transform[] avatarTransforms = avatarRoot.GetComponentsInChildren<Transform>();
        foreach (Transform avatarTransform in avatarTransforms)
        {
            if (HumanSkeletonMap.TryGetValue(avatarTransform.name, out string humanName))
            {
                HumanBone bone = new HumanBone
                {
                    boneName = avatarTransform.name,
                    humanName = humanName,
                    limit = new HumanLimit()
                };
                bone.limit.useDefaultValues = true;

                human.Add(bone);
            }
        }
        return human.ToArray();
    }

和上面的skeleton集合的写法有点相似,就是赋值本分稍有区别,这里主要记录的是映射关系,所以就把上面的Dictionary里的映射关系赋值进去即可,最终我们得到了一个映射集合。

2.4 创建Avatar

拥有了SkeletonBone骨骼数据集合和HumanBone映射数据集合,我们就可以创建Avatar的描述文件了,其他属性都设置默认值,代码如下:

HumanDescription humanDescription = new HumanDescription()
        {
            armStretch = 0.05f,
            feetSpacing = 0f,
            hasTranslationDoF = false,
            legStretch = 0.05f,
            lowerArmTwist = 0.5f,
            lowerLegTwist = 0.5f,
            upperArmTwist = 0.5f,
            upperLegTwist = 0.5f,
            skeleton = CreateSkeleton(gameObject),
            human = CreateHuman(gameObject),
        };

现在我们连描述文件也有了,就可以创建最终的Avatar了,代码也很简单:

 Avatar avatar = AvatarBuilder.BuildHumanAvatar(gameObject, humanDescription);

到此,我们成功创建出了Avatar,使用时直接把这个Avatar赋值给Animator,即可使用通用人型动画驱动我们的模型了!

3 总结

从上面的步骤中不难看出,真正困难的只有创建骨骼映射那部分,毕竟我们不一定那么方便地就能找到模型的骨骼映射关系,但是一旦找到它们的关系,其他部分就再简单不过了。过程中我们可能会遇到一些千奇百怪的问题,比如模型动作非常诡异等,Mesh扭曲等等,这些问题大部分都是因为映射关系不对,少部分是因为骨骼集合里没收集上所有骨骼信息,不要惊慌,多细心检查,最终肯定能成功!

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

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

相关文章

Linux(基于Centos7)(四)

文章目录一、任务目标二、任务资讯三、任务实施3-1.RPM软件包管理3-2.YUM方式安装软件一、任务目标 实施该工单的任务目标如下&#xff1a; 知识目标 1.了解RPM提供的功能。 2.了解YUM相对于RPM所具有的优点。 能力目标 1.能够通过RPM安装及管理软件包。 2.能够通过YUM安装及管…

MCE | ATM 激酶活化变单体后的神奇开挂!

ATM (Ataxia-telangiectasia mutated proteins) 是一种丝氨酸-苏氨酸蛋白激酶&#xff0c;是 DNA 损伤应答 (DDR) 的关键调节因子。ATM 是位名副其实的“指挥官”&#xff0c;在 DNA 双链断裂 (DSB) 中&#xff0c;参与细胞周期检查点维护、DNA 损伤修复和端粒维护等&#xff0…

基于Android的个人健康管理系统

目 录 基于Android的个人健康管理系统 Personal Health Management System Based On Android 1 引言 1 1.1 课题背景 1 1.2 编写目的 1 1.3 关于Android 1 1.4 关于MVC框架 3 2 可行性研究 6 2.1 技术可行性 6 2.2 经济可行性 6 2.3 时间可行性 6 3 需求分析 7 4 总体设计 8 …

栈——算法专项刷题(六)

六、栈 6.1后缀表达式 原题链接 根据 逆波兰表示法&#xff0c;求该后缀表达式的计算结果。 有效的算符包括 、-、*、/ 。每个运算对象可以是整数&#xff0c;也可以是另一个逆波兰表达式。 说明&#xff1a; 整数除法只保留整数部分。给定逆波兰表达式总是有效的。换句话…

【kafka】五、kafka工作流程

kafka工作流程 工作流程 kafka中消息是以topic进行分类的&#xff0c;生产者生产消息&#xff0c;消费者消费消息&#xff0c;都是面向topic的。 topic是逻辑上的概念&#xff0c;而partition是物理上的概念&#xff0c;每一个partition对应一个log文件&#xff0c;该log文件…

基建融资与预算软约束(2015年A股大牛市与“流动性堰塞湖”)-中国视角下的宏观经济

基建融资与预算软约束(2015年A股大牛市与“流动性堰塞湖”) – 潘登同学的宏观经济学笔记 文章目录基建融资与预算软约束(2015年A股大牛市与“流动性堰塞湖”) -- 潘登同学的宏观经济学笔记2015年A股大牛市定向宽松的货币政策导向定向宽松的货币政策原因投资项目的预算软约束政…

python基础之循环嵌套

文章目录一、break和continue二、while的循环嵌套知识点print:例题1打印星星&#xff1a;例题2打印九九乘法表一、break和continue break当某一条件满足时直接跳出当前循环&#xff1b; continue当某一条满足时&#xff0c;不执行后续循环重新开始新一轮循环&#xff1b; i1 …

[附源码]java毕业设计婴幼儿玩具共享租售平台

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

python基础之字典

文章目录一、字典1.dictionary(字典)2.和列表的区别&#xff1a;二、实例三、应用场景一、字典 1.dictionary(字典) 是除了列表之外最灵活的数据类型&#xff0c;字典同样可以用来存储多个数据&#xff08;通常用于存储描述一个物体的相关信息&#xff09;&#xff1b; 2.和…

数据分析软件的使用

一 数据分析概述 1 概念 数据分析是利用数学&#xff0c;统计学理论相结合的科学统计分析方法&#xff0c;对Excel数据&#xff0c;数据库中的数据&#xff0c;收集的大量数据&#xff0c;网页抓取的数据进行分析&#xff0c;从中提前有价值的信息并形成结论进行展示的过程。…

用于调整PID控制器增益的遗传算法的实现(Matlab代码实现)

&#x1f352;&#x1f352;&#x1f352;欢迎关注&#x1f308;&#x1f308;&#x1f308; &#x1f4dd;个人主页&#xff1a;我爱Matlab &#x1f44d;点赞➕评论➕收藏 养成习惯&#xff08;一键三连&#xff09;&#x1f33b;&#x1f33b;&#x1f33b; &#x1f34c;希…

第八章 兼容多种模块标准的软件包封装

第八章 如何封装兼容多种JS模块标准的软件包&#xff1f; 为了方便用户使用&#xff0c;一款成熟的类库都会提供多种模块封装形式&#xff0c;比如大家最常用到的 Vue&#xff0c;就提供了cjs、esm、umd 等多种封装模式&#xff0c;并且还会提供对应的压缩版本&#xff0c;方便…

Python异或运算符示例

目录 异或 异或的性质 示例1&#xff1a;值交换 示例2&#xff1a;找出现一次的元素 示例2代码&#xff1a; 异或 英文为exclusive OR&#xff0c;缩写成xor&#xff0c;符号是^ aba^b0假0假0假0假1真1真1真0假1真1真1真0假 异或的性质 1、a ^ a0 任何数字和自己异或结…

Java项目:JSP蛋糕甜品店管理系统

作者主页&#xff1a;源码空间站2022 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文末获取源码 项目介绍 本项目分为前后台&#xff0c;分为管理员与普通用户两种角色&#xff0c;管理员登录后台&#xff0c;普通用户登录前台&#xff1b; 管理员角色…

Zookeeper

文章目录Zk介绍特点数据结构应用场景Zk安装、集群下载、启动配置参数解读Zookeeper 集群操作选举机制&#xff08;面试重点&#xff09;客户端命令行操作客户端界面节点类型&#xff08;持久 / 短暂 / 有序号 / 无序号&#xff09;监听器1&#xff09;节点的值变化监听2&#x…

【通信】基于matlab模拟室内VLC模型(含BER和SNR)附matlab代码

✅作者简介&#xff1a;热爱科研的Matlab仿真开发者&#xff0c;修心和技术同步精进&#xff0c;matlab项目合作可私信。 &#x1f34e;个人主页&#xff1a;Matlab科研工作室 &#x1f34a;个人信条&#xff1a;格物致知。 更多Matlab仿真内容点击&#x1f447; 智能优化算法 …

短视频平台如何保证内容安全问题?

本文首发于&#xff1a;行者AI谛听 近些年&#xff0c;短视频的安全意识越来越强&#xff0c;随着各大平台的用户暴增&#xff0c;平台的一些审核及运营都有着巨大的挑战。那么对于平台来说&#xff0c;如何保证内容安全呢&#xff1f; 很多短视频平台的内容有着爆炸式的增长&a…

Vue3动态路由(Vite+Vue3+TS+Mock)

一、动态路由简介 Vue通过路由进行页面管理&#xff0c;不同的路由绑定到不同的页面。一般来说&#xff0c;前端直接写好的路由为静态路由&#xff0c;在不修改代码的情况下&#xff0c;路由表是不会改变的。对于不需要动态改变路由表的网站&#xff0c;静态路由就已经足够了&…

关于数据治理工具的选型,你了解多少?

数据治理的本质是盘点数据资产、治理数据质量&#xff0c;实施数据全生命周期的管理&#xff0c;这里面包括了建组织、立制度或者使用一款数据治理的软件帮助企业开展数据治理的相关工作等等。根据不同的数据治理项目特点&#xff0c;会用到不同的技术或工具。拥有一套趁手好用…

功率放大器的三种类型是什么意思

很多人都知道功率放大器&#xff0c;但是却不知道同样都是功率放大器&#xff0c;但是名字相同&#xff0c;作用却是完全不同的&#xff0c;总是会有工程师发出这样的疑问“功率放大器的三种类型是什么以及功率放大器怎么选择型号”等等&#xff0c;今天就请安泰电子来为我们解…