骨骼动画原理学习笔记

  • 什么是骨骼?
  • 如何利用骨骼驱动顶点?
    • 单关节骨骼如何驱动顶点?
    • 多关节骨骼如何驱动顶点?
  • 有了骨骼,如何动画?
    • 动画控制
    • 动画数据加载流程
  • 遗留问题

骨骼动画可以看作一种高效的动画数据压缩技术。

最有弹性的动画系统,可想像成动画师能控制物体表面上无穷多的点。当然,用这种方法制作动画,其结果会是无穷大量的数据!此理想的简化版本是控制三角形网格的顶点,那么实际上,我们是把描述动画的信息加以压缩,限制了只能移动顶点…骨骼动画也是另一种通过加入约束来压缩顶点动画的方式。在此方法中,相对大量的顶点只能跟随相对少量的骨骼关节移动。
——《游戏引擎架构》11.1.5小节

因此,骨骼动画中骨骼最终驱动的是顶点,顶点着色器(Vertex Shader)中利用蒙皮矩阵(Skinning Matrix)对顶点做变换,使网格顶点变换到骨骼的当前姿势。

// position
#ifdef POSITION_LOCATION
layout(location = POSITION_LOCATION) in vec3 in_position;
vec4 getPosition()
{vec4 pos = vec4(in_position, 1.0);#ifdef USE_SKINNINGpos = getSkinningMatrix() * pos;
#endifreturn pos;
}
#endif

大量顶点的变换信息压缩在“骨骼”中,能够这样压缩是因为骨骼动画规定了一种压缩范式,即“如何利用骨骼驱动顶点”的范式。因此为了弄清楚骨骼动画的原理,提出如下三个问题:

  • 什么是骨骼?
  • 如何利用骨骼驱动顶点?
  • 有了骨骼,如何动画?

什么是骨骼?


Q:什么是骨骼?
A:骨骼就是一组关节(Joints)。
Q:什么是关节?
A:一个关节可以理解为一个局部坐标系。关节中存储的数据记录了控制这个坐标系的坐标系变换信息——变换矩阵 or SQT数据(缩放/Scale、四元数旋转/Quaternion、平移/Translation)
关节≈关节坐标系关节 \approx 关节坐标系 关节≈关节坐标系
Q:关节如何组织成骨骼?
A:骨骼的关节呈现层级结构,即树结构。一副骨骼有一个根关节点,其余关节点是根关节点的子节点,或者子节点的子节点,或者子节点的子节点的子节点…

因此一副骨骼可以理解为一个树节点为坐标系的树结构。子关节点的坐标系依赖父关节点的坐标系,即,子关节点存储的变换信息能将子关节空间中的顶点变换到父关节空间中。根关节点中存储的是从根关键点变换到模型空间中的变换信息。

如何利用骨骼驱动顶点?

单关节骨骼如何驱动顶点?

如果一副骨骼只有一个关节,那么可以理解为只是在一系列顶点变换(如下图)前又多了一个关节空间到模型空间的变换。顶点从关节坐标系到模型坐标系的变化,与渲染管线其他部分的坐标系变化(e.g. 模型坐标系到世界坐标系到相机坐标系)原理是一样的。

在关节坐标系内,受这个关节影响的所有顶点的位置是不变的。同样可以类比:不管模型变换到世界坐标系时发生了怎样的旋转、位移和形变,在模型空间中mesh的顶点坐标永远是不变的。
关节空间中的不变性并不像模型空间中的不变性那样直观。因为模型在呈现出来时(比如直接用一些3D Viewer打开模型文件时)就已经具有某种姿势(Pose)。此时的顶点位置,是已经经过关节坐标系到模型坐标系的某种变换后的位置。
因此在利用骨骼控制顶点位置时,需要利用默认姿势的变换矩阵的逆矩阵(Inverse Bind Matrix)将顶点恢复到关节空间,再计算新的关节空间到模型空间的变换,这样两阶段的矩阵变化合并计算得到的就是关节矩阵(Joint Matrix)。关节矩阵(Joint Matrix)一般在Shader外计算完成后以uniform的形式传入Shader。

多关节骨骼如何驱动顶点?

如果一个顶点只绑定了一个关节,那么这个顶点只需要关节矩阵(Joint Matrix)就可以完成新姿势的加载。如果绑定了多个关节,就需要利用多个关节矩阵(Joint Matrix)和每个关节的影响权重(Weights)加权平均得到蒙皮矩阵(Skinning Matrix),所有权重的和为1。

每个顶点会存储影响这个顶点的关节索引(Joints)和对应的影响权重(Weights),这两组参数和位置(Position)、法相(Normal)、颜色(Color)、纹理坐标(UV)一样存储在顶点属性(Vertex Attribute)中。

每个顶点一般最多被4个关节影响,因此shader中用2个vec4变量分别表示顶点的关节索引数组和权重数组。

通常游戏引擎会限制每个顶点能绑定的关节数目。典型的限制为每顶点4个关节,原因如下。首先,4个8位关节索引能方便地包裹为一个32位字。此外,每顶点使用2个、3个及4个关节所产生的质量很容易区分,但多数人并不能分辨出每顶点4个关节以上的质量差别。
——《游戏引擎架构》11.5.1小节

// joins
#ifdef JOINTS_0_LOCATION
layout(location = JOINTS_0_LOCATION) in vec4 in_joints_0;
#endif
#ifdef WEIGHTS_0_LOCATION
layout(location = WEIGHTS_0_LOCATION) in vec4 in_weights_0;
#endif#ifdef USE_SKINNING
uniform mat4 u_jointMatrix[JOINT_COUNT];
#endif#ifdef USE_SKINNING
mat4 getSkinningMatrix()
{mat4 skin = mat4(0);#ifdef JOINTS_0_LOCATIONskin +=in_weights_0.x * u_jointMatrix[int(in_joints_0.x)] +in_weights_0.y * u_jointMatrix[int(in_joints_0.y)] +in_weights_0.z * u_jointMatrix[int(in_joints_0.z)] +in_weights_0.w * u_jointMatrix[int(in_joints_0.w)];
#endifreturn skin;
}

有了骨骼,如何动画?

动画控制

动画控制有2个主要的部件:动画通道(Channel)和计时器(Timer)。

  • 动画通道中存储了不同的动画序列,e.g. 某个通道中存储了人的右手的动作,另一个通道中存储了人的左手的动作。每个通道中会存储一组动作的信息,一般为一组时刻序列和一组动作序列,时间和动作一一对应。动作一般是针对某一骨骼关节的变换。
  • 计时器用来控制协调不同通道的时间一致性。通过计时器得到全局的时间ttt,利用全局时间ttt得到每个通道中的局部时间t0,t1,...,tnt_0, t_1, ..., t_nt0​,t1​,...,tn​,再利用局部时间通过插值前后帧计算出每个通道中的动作,加载到骨骼中。

动画数据加载流程

  • 计时器得到下一帧的全局时间ttt
  • 动画控制器计算出每个通道的局部时间,插值得到关节变换(动作),加载到关节的SQT信息中
  • 利用更新后的SQT信息,更新计算每个关节点的局部变换矩阵(Local Transform Matrix)
  • 从骨骼根关节点,层级遍历更新计算每个关节点的全局变化矩阵(World Transform Matrix)
  • 计算每个关节点的关节矩阵(Joint Matrix)
  • 在Shader中计算蒙皮矩阵(Skinning Matrix)
  • 在Shader中对顶点应用蒙皮矩阵,得到变换后的顶点位置。

遗留问题

那么目前还没解决的问题是:

  • Joint Matrix 如何计算?
  • 这些复杂的动画控制数据,如何在模型文件中组织?

这些问题会在下一篇笔记中解答:利用glTF文件加载骨骼动画。
下一篇笔记施工中!

骨骼动画原理学习笔记相关推荐

  1. MOOC人工智能原理学习笔记1

    人工智能原理学习笔记1 The Foundations of AI: Philosophy Mathematics Economics Neuroscience Psychology Computer ...

  2. 自控原理学习笔记-反馈控制系统的动态模型(4)-频率特性函数Nyquist图及Bode图

    自控原理学习笔记 自控原理学习笔记专栏 文章目录 1.频率特性函数 1.1 图形表示方法: 1.2 零极点位置和暂态增益图 1.2.1 复轨迹曲线 1.2.3 例子 1.3 计算系统响应 2.开环频率 ...

  3. 自控原理学习笔记-系统稳定性分析(2)-环路分析及Nyquist-Bode判据

    自控原理学习笔记 自控原理学习笔记专栏 文章目录 3. 环路分析 3.1环路分析基本思想: 3.2 稳定程度的性能指标(相对稳定) 3.3 环路整形 4.Nyquist判据 4.1 与幅角原理关系 4 ...

  4. Golang底层原理学习笔记(一)

    LCY~~Golang底层原理学习笔记 1 源码调试 go源代码地址:GitHub - golang/go: The Go programming language 1.1 源码编译 现在的go语言大 ...

  5. [编译原理学习笔记2-2] 程序语言的语法描述

    [编译原理学习笔记2-2] 程序语言的语法描述 文章目录 [编译原理学习笔记2-2] 程序语言的语法描述 [2.3.1] 上下文无关文法 [2.3.2] 语法分析树与二义性 [2.3.3] 形式语言鸟 ...

  6. 自控原理学习笔记-反馈控制系统的动态模型(1)

    自控原理学习笔记 1.导论 2.反馈控制系统的动态模型(1) 3.反馈控制系统的动态模型(2) 3.反馈控制系统的动态模型(3) 4.反馈控制系统的动态模型(4) 5.反馈控制系统的动态模型(5) 文 ...

  7. Spring5底层原理 学习笔记(二)AOP篇

    文章目录 AOP实现之ajc编译器 AOP实现之agent类加载 AOP实现之动态代理 jdk动态代理 演示 模拟实现动态代理 动态生成代理类需要使用到asm的api,这里就不展开了 Jdk对于反射调 ...

  8. Unity动画状态机学习笔记

    Unity动画状态机学习笔记 一.建平面,拖人物模型.建状态机.动画导入.拖组件--实现Game时人物动画为等待状态. 二.拖WAIT01.WAIT02.WAIT03.WAIT04--实现按数字1切换 ...

  9. 编译原理学习笔记20——符号表

    编译原理学习笔记20--符号表 20.1 符号表的组织与操作 20.2 符号表的内容 20.3 利用符号表分析名字的作用域 20.1 符号表的组织与操作 符号表 符号表的作用与组织 符号表的整理和查找 ...

  10. GPU Skinning 一:骨骼动画原理

    最近在为引擎升级64位的过程中GPU蒙皮也出现了异常,平常骨骼动画和网格蒙皮用的还是非常多的,但是底层的原理并没有深究过,想着还是有必要好好整理下这部分内容. 骨骼蒙皮动画 一般我们称为骨骼动画(Sk ...

最新文章

  1. 《软件测试经验与教训》之二——测试内容先后顺序
  2. 机器学习热码one hot python
  3. css字体设置奇怪问题
  4. 英文版opensuse 12.2安装中文输入法ibus
  5. 1560F1. Nearest Beautiful Number (easy version)
  6. java cookie 覆盖吗_Java Web-Cookie和Session
  7. c语言发牌小游戏,大家想想怎么用c实现我们经常玩的斗地主游戏的发牌过程呢?...
  8. amd的处理器能兼容idea么_AMD新一代CPU不向下兼容,究竟意味着什么?
  9. 拿什么来拯救你,电视!
  10. Java引用多个jar包的写法
  11. 微博批量发布,微博定时发布,批量删除,批量评论等功能的实现
  12. AWSome Day 2019 线上云技术课堂(1)
  13. CentOS 8 安装tc流量控制工具
  14. vostro3470装win7_dell latitude3470怎么安装win7系统
  15. 桃源网盘php,桃源居业主自建论坛 - Powered by PHPWind
  16. Rust更适合经验较少的程序员?
  17. 东北大学第二场算法题解报告
  18. 2019程序员笔记本推荐
  19. PSO算法求解全局最大值
  20. 未知参数休哈特matlab,休哈特控制图 日语 请教控制图上下控制界限的确定

热门文章

  1. Hulu面试(或许待更)
  2. 关于百度有啊的几点看法
  3. 2倍研发费用=营销费用,小牛电动“智”在何方?
  4. c语言中大于号什么意思和作用,程序员必须要使用的大于号,小于号你知道几个?教你轻松入门C语言...
  5. Win10修修补补日记:Win10周年更新再出BUG 淡定
  6. LRU算法(有彩蛋)
  7. 【数据分析实例】 7000 条北京的租房数据分析
  8. 四大抓包神器,非常好用
  9. [51Nod 1035 最长的循环节] 循环小数的性质
  10. 阿里云网站备案时变更备案的问题解决总结 满满干货