骨骼动画详解

【物体怎么样是在动】

当物体的位置、朝向、大小即Transform有任意一者发生变化时,物体在动。

但变化要达到一定的幅度时,我们会看到物体在动,幅度是多少却决于我们看这个物体的距离、方向,物体的朝向等因素。

这里说的幅度是指空间上的变化,还有一个隐含的时间上的变化。

对于一般人而言,其眼睛的帧率(即人眼感知到的画面的变化的频率)在每秒24帧到30帧。当显示器的帧率大于人眼帧率时,我们会感到画面是流畅的,否则画面就是卡顿的。

以30帧为例,从上一帧到下一帧的时间恰好为33.3ms。此时物体Transform变化达到人眼能感知的最小幅度,人眼感知到物体在运动。如果每帧的间隔都为33.3ms其物体变化都是最小幅度,此时,人眼感知物体在完美的连续运动。

如果上一帧到下一帧的时间小于33.3ms,物体变化到达了最小幅度。如果走完了一帧,此时物体在动,但人眼感知不到物体在动。因为只有到了33.3ms时人眼感知的画面才会刷新。等到了33.3ms,物体变化已经超过最小幅度了,人眼仍能感知到物体在运动。但是如果此时物体变化的幅度非常大,说明物体运动的速度非常快。如果这个幅度超过了人眼可视范围,人眼会感觉物体瞬间消失了。

如果上一帧到下一帧的时间大于33.3ms,物体每帧变化达到最小幅度。等到了33.3ms时,物体在运动,但人眼感知不到,因为此时物体的变化小于最小幅度。等到了66.6ms时,人眼才感知到物体在动。如果物体速度够快,那么在33.3ms时人眼仍然能感知到物体在动。

因此,人眼是否能感知到物体在动,和人眼帧率、显示器帧率(物体世界中没有这项)、物体变化速度有关。

如果物体变化的速度是均匀的,那么人眼感知物体的运动是连续流畅的,否则就是卡顿的。

【逐帧动画和关键帧动画】

以30帧为例,我们只要准备30个物体画面,每个画面间物体有一定变化,在1s内播放30个画面即可,那么物体看起来是在动的。

如果希望物体在5s内都是动的,那么要准备150个画面。

Unity帧动画就是如此,一般用于UI的动画或者2D游戏中的动画大多是这样做出来的,这就是逐帧动画,每帧播放的画面都是预先固定好的。

但每秒30个画面成本过高,要累死美术了。因此,必须相办法降低画面帧数。

一种是结合具体的画面情景,降低画面帧数,前文说过,不一定要帧数大于30才会让人眼感觉在动。

另一种方式是给出关键的两帧画面,通过计算得到中间的画面。如何计算呢,这就是典型的通过两个已知求一个未知的问题,用插值解决。在插值中,如果两个已知值距离较远,那么计算出来的值会不准确,也即插值不平滑。反映到动画上就是前后动画衔接不流畅。

这就是关键帧动画,通过给定一系列的关键帧,插值得到一系列帧动画。

注意,如果要求1s内有30帧,那么实际上要准备31个画面,在0s和1s时必须有一个画面。用关键帧时,中间的画面可以省略,在开头和结尾的画面是不能省略的。

因此,如果动画是循环动画,那么开头帧和结尾帧必须保持相同。引擎在处理时通常会舍弃结尾帧。

【关节动画与骨骼动画】

解析来的问题是如何描述物体状态,在逐帧动画中,是通过一幅幅画来描述的,这样成本过高,而且难以插值。

在计算机中,是通过模型来描述物体状态的,具体来说就是Mesh+渲染。Mesh决定模型的形状,渲染决定模型表面的颜色。这里我们只关注Mesh。

我们知道Mesh是一系列的点云,在不同帧中让点云的分布不同,那么点云描述的物体就会发生变化,一系列帧中,物体就动了起来。

(插一句,如果以点来表示,那么真实世界的物体是有无限的点的,我们如何用一系列的点去表示无限的点?首先,人眼的分辨率是有极限的,当两个点足够近,在人眼看来就连在一块,很多足够近的点就连成线了,再多的点就可以构成一个面。其次,我们可以用少量的点计算出来更多的点,如何计算?插值出来)

现在,如果我们预先得到一系列帧中点云分布,就可以在运行时让物体动起来。同时还可以做插值,减少帧数。

这种方式理论上可以实现,但实际上却很耗费性能,数据总量过大。如果Mesh中有1万个点,动画时长5s,100帧画面,那要存储100万个数据。因为不同的点变化幅度不同,对不同的点插值参数还需要不同,这也需要额外记录。

因此,动画中不能仅有点云数据。(点云数据指什么?Position,点不需要Rotation和Scale。点云数据是模型空间下的,最终看物体在画面中的位置时,需要变换到世界空间下)

对于静态的物体,有点云数据就够了,静态物体的Mesh中一部分是点云数据,用于描述物体的形状,另一部分是点云中点之间关系的数据,用于渲染。

注意,这里的静态物体不是场景中没有位移的物体,而是物体没有整体的位移、朝向、大小变化时物体是否会动。

因此,我们在说模型动画时,不会考虑模型整体的Transform变化,也即世界空间下的变化。

参考静态物体,可以发现模型只是某部分在动,其他部分仍是静态的,模型可以视为一系列静态物体组成的,模型的不同部分相连接的地方叫关节。模型空间下,模型的一部分在动,我们认为模型在动,而模型的这部分自己的空间下,其仍是静态的。

因此,我们只需要知道模型每个部分的Transform,以及各部分各自空间下点云数据即可呈现当前时刻物体形状。

在同样100帧画面下,我们需要的总数据量为:100*部分数量+点云数据*1万(各部分的点云数据加起来和原来的点云数据量相同)。

这样总的数量量相比原来大幅减少。插值时只需要对各部分的Transform插值即可,插值次数也大幅减少。

如果模型各部分不是独立的,而是父子层级结构,那么每帧记录的动画数据会更少:省略了各部分的Position数据,只需要记录根部分的Position数据;记录各部分相对于其父层级的朝向。固定的数据是,各部分的长度(size)(特殊情形下也可以变化)

这样,可以得到了关节动画:模型各部分有各自的Mesh,不同部分构成父子层级结构,通过层层变换得到各部分的状态,根据各部分状态及各部分的点云分布计算得到所有点云的位置。

关节动画的缺点是在关节处容易出现裂缝。

骨骼动画解决了这个问题。骨骼动画中前面说的各部分叫做骨骼Bone,各部分没有单独的Mesh,模型有一个整体的Mesh,叫做SkinnedMesh,模型状态叫姿态Pose。关节动画中,顶点位置只受一个部分影响,而骨骼动画中,顶点位置可能受到多个骨骼影响,尤其是关节处的顶点。顶点位置通过骨骼位置加权计算得到。所以需要知道每个顶点受那些骨骼影响,以及影响权重是多少(权重加起来是1),这也叫蒙皮数据skin info。

总结一下,骨骼动画需要的关键数据有:

  • Mesh数据(点云位置)
  • 层次数据(初始每个子层到父层的变换)
  • 蒙皮数据(每个顶点受那些骨骼影响,以及影响权重是多少)
  • 关键帧数据(每帧每个骨骼的朝向,以及根骨骼的位置)

【顶点动画】

每帧改变顶点位置的动画就叫顶点动画。为了解决顶点动画中数据量过大的问题,我们引入了关节、骨骼的概念。还有什么其他方法可以解决这个问题吗?

1.通过函数描述模型姿态。例如草、树叶在空中左右摆动,丝带或过期在空中自然飘动等。这种可以全部用函数描述的情况下,我们只需要一个模型的静态Mesh即可,不需要美术提供模型动画,模型动画在顶点着色器中完成。

2.通过插值描述模型部分姿态。首先要知道我们能对动画控制的越多,动画的表现力和效果是越多的,就表现力而言,帧动画>顶点动画>骨骼动画。例如,角色的脸部表情可以用顶点动画完成,做几个极端表情的模型,在这个几个模型间做插值得到某个具体的表情。

【骨骼动画的制作】

骨骼动画可以用于角色、武器、机关等,主要用于角色,而且是人形角色。角色的动画是否有动感,主要取决于骨骼数量,骨骼数量越多,角色动感越强。但骨骼越多,性能消耗越大。

手游中,角色骨骼数量一般不超过30,PC中不超过75。

通过会选择角色的盆骨做根骨骼,而模型空间原点一般选择角色两个脚底之间的中点。此时根骨骼的位置和原点没有重合,这时美术会构建一个Scene_Root做为额外的虚拟骨骼,其位置就为世界原点,而根骨骼是虚拟骨骼的唯一子骨骼。

向模型添加好骨骼并摆好Pose后,就可以知道每个骨骼在模型空间的位置,骨骼所在位置是骨骼自身空间的原点,与关节所在的位置重合。动画师可以设定骨骼对顶点的影响权重,对同一个顶点的所有权重之和应该为1。

对于摆好的Pose,建模软件可以自动计算处父骨骼空间到子骨骼空间的变换。每个骨骼都对应一个变换,这个变换叫TransformMatrix,所有变化合起来就是这个Pose的层次数据。

每个关键帧都对应一个Pose,每个Pose都有各自的层次数据,这些层次数据合起来就是关键帧数据。一般初始的Pose是”T“字型,叫做绑定姿势bindpose。

随后是SkinnedMesh,其空间原点也是角色两个脚底中间的位置。对于初始Pose,可以知道每个顶点在模型空间的位置,建模软件会算出从模型空间到骨骼空间的变换BoneOffsetMatrix,继而能知道顶点在骨骼空间中的位置。

【骨骼动画播放流程】

  • 选择关键帧数据SelectKeyFrameData:根据当前时间找到邻近的两个关键帧数据,如果和某个关键帧数据很近,就用该关键帧的数据做最终数据
  • 更新骨骼矩阵UpdateBoneMatrix
    • 读取关键帧数据中每个骨骼的TransformMatrix
    • 矩阵累乘得到从骨骼空间到模型空间的变换CombinedMatrix(这里你需要知道点空间变换的知识),每个骨骼都有对应的CombinedMatrix,其是实时变化的
  • 计算骨骼坐标:从根骨骼开始,根据CombinedMatrix,可以依次计算出所有骨骼在世界空间中的位置、朝向、缩放
  • 插值骨骼坐标:将前后两帧的骨骼坐标插值得到新的骨骼坐标
  • 计算(骨骼的)顶点坐标:根据BoneOffsetMatrix可以计算出顶点在骨骼空间中的位置,根据当前骨骼到时间空间的变换CombinedMatrix和骨骼坐标可以计算出顶点在模型空间中的坐标
  • 混合顶点坐标:根据BoneWight计算出最终顶点在模型空间中的坐标

【关键计算步骤】

找到关键帧数据:为了节省内存,关键帧数据在内存中是压缩的,使用时需要先解压

插值骨骼坐标:骨骼坐标插值要在骨骼自身空间中完成,如果在模型空间,那么插值出来的骨骼组成的Pose很怪异,因此,我们需要保存骨骼的TransformMatrix,而不是骨骼的TransformMatrix。因为矩阵插值难以实现,在实际插值时,需要转换成位置(向量)、旋转(四元数)、缩放(标量是统一缩放,向量是非统一缩放)分别插值。不同关节的插值可以并行完成。

骨骼的顶点坐标 = 当前模型空间中骨骼坐标 +初始时模型空间的顶点坐标*BoneOffsetMatrix* CombinedMatrix

【如何混合顶点】

如何顶点受四个骨骼影响,那么最终计算出来的顶点坐标有4个,怎么从这个4个坐标中得到最终坐标呢?

这就是经典的如何从多个数据中选择一个数据的问题

结合骨骼动画的情景,显然可以取加权累积和值,这也叫线性混合蒙皮,即

最终顶点坐标 = 骨骼1的顶点坐标 * 骨骼1的权重 + 

                          骨骼2的顶点坐标 * 骨骼2的权重 + 

                          骨骼3的顶点坐标 * 骨骼3的权重 + 

                          骨骼4的顶点坐标 * 骨骼4的权重 

这种放能用,但是有点粗糙,最终导致角色动作变化不够平滑。

可以猜想不够平滑的地方是关节处,关节处的顶点受到多个骨骼的影响,在初始Pose中给定了每个骨骼的权重,在随后的计算中这些权重是固定不变的。而这在随后的Pose,尤其是插值出来的Pose中不再合适。

合适的权重需要实时计算。

目前用的广泛的是有界双调和权重算法,其原理为:

通过预设的控制单元建立双调和方程,使其拉普拉斯能量和最小化迭代算出权重,引入边界条件和边界权重来控制形变的强度和范围,引入限制因子让用户可以更精细的控制形变,以求得让模型形变更加平滑的权重。

该算法的实现见github

【Unity中骨骼的数据结构】

SkinnedMeshRender

  • rootBone:根骨骼Transform
  • bones:其他骨骼Transform,每个数据表示一个关节
  • sharedMesh:要渲染的mesh

Mesh:

  • boneWeights:骨骼权重,每个顶点对应一个boneWeights
  • bindposes:骨骼层次数据,用的4x4矩阵
  • vertices:所有顶点的位置

看下BoneWeight结构体:

BoneWeight中记录了最多4个对于骨骼的索引,值是SkinnedMeshRenderer中的bones的索引,权重之和为1:weight0 + weight01 + weight2 + weight3 = 1。

在Quality Setting中可以设置一个顶点最多能被几个骨骼影响,一般设置为4个。

【典型完整的骨骼层级结构】

  • Pelvis(骨盆)

    • Spine1(脊椎1)

      • Spine2(脊椎2)(还可以有3,分为是下中上脊椎)

        • LClavicle(左锁骨)

          • LeftUpperArm(左大臂)

            • LeftLowerArm(左小臂)

              • LeftHand(左手)

                • LFinger0

                • LFinger1

                • LFinger2

                • LFinger3

        • Neck(脖子)

          • Head(头)

            • Hair(头发)

            • Face(脸)

        • RClavicle(右锁骨)

          • RightUpperArm(右大臂)

            • RightLowerArm(右小臂)

              • RightHand(右手)

                • RFinger0

                • RFinger1

                • RFinger2

                • RFinger3

                • RFinger4

    • RightUpperLeg(右大腿)

      • RightLowerLeg(右小腿)

        • RightFoot(右脚)

    • LeftUpperLeg(左大腿)

      • LeftLowerLeg(左小腿)

        • LeftFoot(左脚)

【参考】

Skinned Mesh原理解析和一个最简单的实现示例-CSDN博客

《游戏引擎架构》 

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

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

相关文章

mysql 存储引擎

存储引擎 在MySQL中,数据用各种不同的技术存储在文件中,每一种技术都使用的是不同的存储机制、索引技巧、锁定水平以及最终提供的不同的功能和能力,这些就是我们说的存储引擎 功能 1、MySQL将数据存储在文件系统中的一种方式和格式 2、存储…

【LVS实战】03 LVS负载均衡-DR模式实验

本文介绍,如何通过ipvsadm配置负载均衡,并且转发模式为DR直接路由模式 一、网络拓扑 如下图: LVS负载均衡的机器,配置了一个VIP:10.1.0.7 二、前置准备 每台机器关闭防火墙 systemctl stop firewalldRS装好ngin…

如何用 GPT-4 全模式(All Tools)帮你高效学习和工作?

「十项全能」的 ChatGPT ,用起来感受如何? 之前,作为 ChatGPT Plus 用户,如果你集齐下面这五个模式,就会成为别人羡慕的对象。 但现在,人们更加期盼的,是下面这个提示的出现: 这个提…

ZZ038 物联网应用与服务赛题第I套

2023年全国职业院校技能大赛 中职组 物联网应用与服务 任 务 书 (I卷) 赛位号:______________ 竞赛须知 一、注意事项 1.检查硬件设备、电脑设备是否正常。检查竞赛所需的各项设备、软件和竞赛材料等; 2.竞赛任务中所使用的各类软件工…

http中的Content-Type类型

浏览器的Content-Type 最近在做web端下载的时候需要给前端返回一个二进制的流,需要在请求头中设置一个 writer.Header().Set("Content-Type", "application/octet-stream")那么http中的Content-Type有具体有哪些呢?他们具体的使用场…

超详细的wheel轮子下载和.whl安装

wheel安装 pip安装失败问题 基于Anaconda进行 pip install ,报如下 但基于此路径下安装.whl文件时候,会报如下 通过在可借鉴的博客上查找相关原因,发现原来是因为版本没有匹配的问题,基于此问题以及博客中的解决方案,进行亲测…

Pandas练手项目

一、chipotle tsv 数据集:chipotle.tsv-数据集 代码:https://download.csdn.net/download/Albert233333/88508819 1 导入数据 # order_id这一列相同的数字表示 一个消费者同一次进行的交易 # 表格中的每一行表示 用户一次购买的某一个品类 购买的数量…

1、Sentinel基本应用限流规则(1)

Sentinel基本应用&限流规则 1.1 概述与作用 随着微服务的流行,服务和服务之间的稳定性变得越来越重要。缓存、降级和限流是保护微服务系统运行稳定性的三大利器。 缓存:提升系统访问速度和增大系统能处理的容量 降级:当服务出问题或者影…

12 款小众宝藏AI工具,90% 的开发者不了解

AI工具的发展一日千里,了解这些AI工具的功能以及它们如何提高开发过程中的效率和创新,变得尤为重要,这里分享了 12个宝藏的人工智能和低代码工具,希望对大家的工作与学习有所帮助。 1.Pieces for Developers 网址:ht…

Technology Strategy Pattern 学习笔记5 -Creating the Strategy-Department Context

Creating the Strategy-Department Context 1 situations This pattern helps you organize your thoughts, and consider the department holistically in the following situations 1.1 •Aligning teams around a vision, especially a new direction 1.2 •Setting up a …

presto插件机制揭秘:探索无限可能的数据处理舞台

文章目录 1. 前言2. Presto插件架构3. Plugin接口3.1 插件协议3.2 插件实现类 4. 插件加载过程4.1 PluginManager 5. 插件应用6. 总结 关键词:Presto Plugin 1. 前言 本文源码环境: presto: prestoDb 0.275版本 在Presto框架中插件机制设计是一种非常常见…

lombok依赖介绍(帮助我们消除冗长代码,如get,set方法)

前言 lombok 是一个 Java 工具库,通过注解的方式,简化 Java 开发。要想使用 lombok 中的注解,我们需要先引入依赖,推荐看idea必装插件EditStarters(快速引入依赖),lombok是⼀款在编译期⽣成代码…

随身wifi编译Openwrt的ImmortalWrt分支

背景: 之前用酷安上下载的苏苏亮亮版友提供的Openwrt,在高通410棒子上刷机成功,但编译一直就没搞定。近期听说又出了个分支版本ImmortalWrt,刷了个版本,感觉界面清爽不少,内核也升级,遂打算搞定…

【Redis】SSM整合Redis注解式缓存的使用

🎉🎉欢迎来到我的CSDN主页!🎉🎉 🏅我是Java方文山,一个在CSDN分享笔记的博主。📚📚 🌟推荐给大家我的专栏《Redis》。🎯🎯 &#x1f4…

【ML】分类问题

分类问题 classification:根据已知样本特征,判断输入样本属于哪种已知样本类。 常用入门案例:垃圾邮件检测、图像分类、手写数字识别、考试通过预测。 分类问题和回归问题的明显区别: 分类问题的结果是非连续型标签&#xff0c…

Netty入门指南之传统通信的问题

作者简介:☕️大家好,我是Aomsir,一个爱折腾的开发者! 个人主页:Aomsir_Spring5应用专栏,Netty应用专栏,RPC应用专栏-CSDN博客 当前专栏:Netty应用专栏_Aomsir的博客-CSDN博客 文章目录 参考文献前言多线程…

基于APM(PIX)飞控和missionplanner制作遥控无人车-从零搭建自主pix无人车无人坦克

前面的步骤和无人机调试一样,可以参考无人机相关专栏。这里不再赘述。 1.安装完rover的固件后,链接gps并进行校准。旋转小车不同方向,完成校准,弹出成功窗口。 2.校准遥控器。 一定要确保遥控器模式准确,尤其是使用没…

轻量封装WebGPU渲染系统示例<20>- 美化一下元胞自动机(源码)

当前示例源码github地址: https://github.com/vilyLei/voxwebgpu/blob/feature/rendering/src/voxgpu/sample/GameOfLifePretty.ts 系统特性: 1. 用户态与系统态隔离。 2. 高频调用与低频调用隔离。 3. 面向用户的易用性封装。 4. 渲染数据(内外部相关资源)和渲染机制分离…

使用Anaconda安装TensorFlow环境以及没有搜到的报错的解决方法

1.在官网下载Anaconda 这一步几乎不会有人报错 下稳定的版本 或者最新的版本都可以 2.TensorFlow分两个版本 一个是用cpu跑 另一个是用gpu跑 显而易见 cpu的计算性能已经比不上现在主流的显卡了 所以有独显的电脑尽量安装gpu版本 CPU版本: 先给出cpu版本的安装方法: 打开A…

描述低轨星座的特点和通信挑战,以及它们在5G和B5G中的作用。

文章目录 2章4 章5章(没看)6章(没看) 2章 将卫星星座中每个物理链路中可实现的数据速率、传播延迟和多普勒频移与3GPP技术报告中的参数进行分析和比较[3]。 相关配置 面向连接的网络,预先简历链路 卫星和地面终端有…