骨骼动画详解

news2024/11/26 16:46:40

【物体怎么样是在动】

当物体的位置、朝向、大小即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.coloradmin.cn/o/1177666.html

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

相关文章

【Windows】解决电脑可以正常使用微信,但是打不开网页

问题 啊哈 如题,在安装软件的过程中突然就发现浏览器不能用了,但是微信可以正常接发消息。 记录一下解决过程。 解决 1、自动DNS 打开控制面板->网络和Internet->网络和共享中心,点击以太网,找到属性Internet协议4(TCP/…

手机怎么打包?三个方法随心选!

有的时候,电脑不在身边,只有随身携带的手机,这个时候又急需把文件打包发送给同事或者同学,如何利用手机操作呢?下面介绍了具体的操作步骤。 一、通过手机文件管理自带压缩功能打包 1、如果是iOS系统,就在手…

猫头虎分享从Python到JavaScript传参数:多面手的数据传递术

🌷🍁 博主猫头虎 带您 Go to New World.✨🍁 🦄 博客首页——猫头虎的博客🎐 🐳《面试题大全专栏》 文章图文并茂🦕生动形象🦖简单易学!欢迎大家来踩踩~🌺 &a…

dell r720

dell r720的idrac的地址默认是192.168.1.110,root 默认密码calvin fatal Error! All channnels have been disabled due to all DIMMs failed the Memoey 是什么意思 Dell PowerEdge T320服务器 开机显示 Fatal Errort!all channells have been disabled due to …

【鹰王系统】又一个干净纯净的window系统下载站点,值得推荐!

今天给大家分享一个高质量的window系统下载站点【鹰王系统】,此站长自己制作了winxp、win7、win10、win11等各种版本系统,都是非常不错的系统镜像,都是免费下载没有任何捆绑信息,值得推荐。 网站首页截图 系统下载截图&#xff1a…

Java 高效生成按指定间隔连续递增的列表(int,double)

简介 Java 按照指定间隔生成连续递增的List 列表&#xff08;引入Stream 类和流操作来提高效率&#xff09;&#xff1a; 1. 生成递增的List< Integer> Testpublic void test009(){int start 1;int interval 2;int count 10;List<Integer> list IntStream.ite…

集群外访问计算节点gpu上的web链接

情况描述 现在有一个程序&#xff0c;通过提交作业的方式在集群的计算节点C上运行&#xff0c;运行后给了一个web的地址,如下图所示 然而&#xff0c;在自己电脑A上只能访问集群的管理节点B&#xff0c;无法直接访问计算节点。管理节点可以访问计算节点&#xff0c;计算节点无…

Git extension 中合并工具kdiff3乱码问题

打开kdiff3合并工具&#xff0c;setting->region settings 设置下面的编码格式为utf-8就可以啦&#xff01; 注意&#xff1a;需要在合并工具中设置编码格式&#xff0c; 在git 中配置编码格式没有效果

Keep-Alive中通过component多次加载同样的动态组件无法保持状态的解决办法

Keep-Alive中通过component多次加载同样的动态组件无法保持状态的解决办法 Keep-Alive中通过component多次加载同样的动态组件无法保持状态的解决办法 | 软件开发服务商 (yidianhulian.com)https://yidianhulian.com/?p12263 问题描述 项目功能上有需要动态添加组件的需求&…

Python学习-shutil模块和OS模块学习

shutil模块 针对文件的拷贝&#xff0c;删除&#xff0c;移动&#xff0c;压缩和解压操作 # 1.copyfileobj只能复制文件内容&#xff0c;无法复制权限#复制文件时&#xff0c;要选择自己有权限的目录执行操作&#xff0c;创建的文件会根据系统umask设定的参数来指定用户权限 s…

Ubuntu 18.04.6 LTS安装docker和docker-compose、镜像导入导出

Ubuntu 18.04.6 LTS安装docker和docker-compose、镜像导入导出 文章目录 Ubuntu 18.04.6 LTS安装docker和docker-compose、镜像导入导出安装docker更新数据源&#xff0c;安装必要插件添加GPG密钥设置存储库再更新apt包索引安装docker-ce查看docker状态运行docker hello-world …

医学专题(6)--多组学在肿瘤分型研究中的应用思路

研究背景 肿瘤免疫分型的由来&#xff1a;每一种肿瘤&#xff0c;甚至每一位患者的肿瘤浸润免疫细胞都存在差异&#xff0c;研究者根据免疫细胞浸润的特点将肿瘤大致分为“冷”肿瘤和“热”肿瘤&#xff0c;此概念的提出是肿瘤免疫分型的雏形。 对肿瘤免疫分型的研究有很多&a…

HDFS速通之一文详解HDFS全部知识点

文章目录 HDFS介绍HDFS体系HDFS的Shell介绍HDFS的常见Shell操作 HDFS案例实操Java操作HDFS配置环境 HDFS的回收站HDFS的安全模式实战&#xff1a;定时上传数据至HDFSHDFS的高可用和高扩展HDFS的高可用(HA)HDFS的高扩展(Federation) HDFS介绍 HDFS是一个分布式的文件系统 假设…

二叉树的最大深度(C++解法)

题目 给定一个二叉树 root &#xff0c;返回其最大深度。 二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。 示例 1&#xff1a; 输入&#xff1a;root [3,9,20,null,null,15,7] 输出&#xff1a;3示例 2&#xff1a; 输入&#xff1a;root [1,null,2…

2005-2021年全国各省家庭承包耕地面积和家庭承包耕地流转总面积数据(无缺失)

2005-2021年全国各省家庭承包耕地面积和家庭承包耕地流转总面积数据 1、时间&#xff1a;2005-2021年 2、来源&#xff1a;农村经营管理统计NB 3、指标&#xff1a;家庭承包经营耕地面积、家庭承包耕地流转总面积&#xff08;单位&#xff1a;亩&#xff09; 4、范围&#…

MVC、MVP、MVVM区别

MVC、MVP、MVVM区别 MVC&#xff08;Model-View-Controller&#xff09; 。是一种设计模式&#xff0c;通常用于组织与应用程序的数据流。它通常包括三个组件&#xff1a;模型&#xff08;Model&#xff09;、视图&#xff08;View&#xff09;和控制器&#xff08;Controller&…

【OpenVINO】基于 OpenVINO C++ API 部署 RT-DETR 模型

基于 OpenVINO C API 部署 RT-DETR 模型 1. RT-DETR2. OpenVINO3. 环境配置4. 模型下载与转换4.1 模型导出4.2 模型信息对比 5. C代码实现5.1 模型推理类实现 5.2 模型数据处理类RTDETRProcess6. 预测结果展示7. 总结 RT-DETR是在DETR模型基础上进行改进的&#xff0c;一种基于…

JAR 文件规范详解

介绍 JAR文件是基于ZIP文件格式的一种文件格式&#xff0c;用来将许多文件整合成一个文件。一个JAR文件本质上是包含可选目录META-INF的zip文件&#xff0c;可以通过命令行jar工具或者在Java平台上使用java.util.jar中的API来创建。JAR文件的命名没有严格的要求&#xff0c;可…

Flink SQL Regular Join 、Interval Join、Temporal Join、Lookup Join 详解

Flink ⽀持⾮常多的数据 Join ⽅式&#xff0c;主要包括以下三种&#xff1a; 动态表&#xff08;流&#xff09;与动态表&#xff08;流&#xff09;的 Join动态表&#xff08;流&#xff09;与外部维表&#xff08;⽐如 Redis&#xff09;的 Join动态表字段的列转⾏&#xf…

Redis5 分布式系统之主从模式

目录 分布式系统 引子 分布式系统类型 主从模式 一个主节点和多个从节点 创建多个节点方法 配置主从结构 主从模式知识 主从复制 拓扑结构 1.一主一从 2.一主多从 3.树形主从 主从实现原理 psync数据同步 全量复制和部分复制 psync流程 1.全量数据同步 2.部…