Skeleton with Assimp 骨骼动画解析

骨骼动画是图形学中十分常见应用很广泛的一个技术,也是比较基础的内容,作为图形学的工程师需要将这一部分内容梳理清晰,主要关键在于几点:第一,分清楚骨骼节点两个概念;第二,熟悉使用 Assimp(或者其他的)的解析方式,并编程实现骨骼的解析和动画的播放。

理解骨骼

首先,为什么会有骨骼动画这么一种东西的存在呢?如果我们从我们自己的身体上观察,就可以发现,我们全身可以活动的部分,其内部基本都有一根主要的骨头,比如小臂的挥动,小臂上所有的肌肉皮肤都一起和骨骼运动。回过头来看 3D 绘制,通常我们需要绘制的是一个 mesh, 也就是物体的表面部分,可以认为是一张皮。我们希望绘制的对象也可以像人体一样做一些动作。那么同样的,我们将这张皮上面的每一个最小单位,如顶点(vertex)都绑定一根骨头上去,骨头怎么动,皮就怎么动。这个部分,叫做蒙皮(skin rigging),是由艺术家完成的[1](真实情况中,一个点可能受多个骨骼影响,需要确定具体的权重)。这样,我们只需要考虑有限的几个骨头的运动就可以描述人体整体的运动,简化了很多,同时也是对自然规律的模拟。

节点又是什么?

节点就是一个点,在骨骼的语境中,可能称为“关节”,但是关节不同与骨骼。Keep in mind: 骨骼是有长度的线段,仅有空间中一个点的位置无法描述骨骼。只有用两个点,才能组成一条线段,只有线段才能代表骨骼。即使在 Unity 或其他软件中,用 node, 节点,关节 代表骨骼,但是心里要清楚这一点。如下图,RA 是一个骨骼,RB, BC 都分别是骨骼,但是我们不能说 A 是一个骨骼,单独提 A 是没有意义的,这只能是一个点。

艺术家的工作,将所有的顶点,都和骨骼绑定起来,显然,下图这个骨骼的配套的 mesh 的上面部分的顶点,都与 RA 骨骼绑定起来了,而在左下方和右下方的顶点,基本绑定在 BC 和 DE 两个骨骼上。在这几个节点处的顶点,会与多个骨骼绑定,每个骨骼有一定的权重。同时,艺术家会提供一个动画的关键帧的骨骼的姿态,即在关键帧时,每一个骨骼的位置。


A -
\
\
\
B---- R ----- D
| |
| |
| |
C E ------------------ R -> A
R -> B -> C
R -> D -> E

根据艺术家提供的数据,究竟我们怎么确定每一个点的位置呢? 首先骨骼之间存在父子结构关系,上图的 RB 骨骼是 BC 骨骼的父亲。我们也可以用节点来描述,那么就是 R 点是 B 点的 父亲, B 点是 C 点的父亲(如上图箭头所示)。在父子层级关系上我们用骨骼或者用节点都是可以描述的,其本质上描述的是同一个骨骼,就是图中画的那样。Assimp 用来帮助解析 FBX 文件,我们从 Assimp 中获取所有的信息。对于任意时刻的骨骼的位置,Assimp 提供每个骨骼相对上一级的变换,以 transform matrix 表示,从根节点开始遍历,就可以得到每一个骨骼相对根节点的变换,如果认为根节点就是在世界坐标系的中心,这就是从 bone space -> world space 的变换了。bone space 就是以这个骨骼当中的某个点作为坐标系的原点,具体是哪一个点,其实我们也是无法得知的,这个信息对于计算和理解都不重要,只要知道 bone space 就是骨骼的局部坐标系,知道绑定了这个骨骼的每一个点,在这个局部坐标系当中的位置即可。对于绑定了这个骨骼的每一个点,设其在 bone space 中的位置为 bone_pos, 那么,其在世界坐标系中的位置就是 bone_pos 乘上计算出来的变换矩阵。

Assimp 解析指南

使用 Assimp 加载 FBX 文件获得 aiScene 这是所有数据的入口。

Bone

aiBone *bone = aiScene->mMeshes[]->mBones[];

mBones 数组里面存储了所有的骨骼,每个骨骼存储对应绑定的顶点和该顶点的权重,以及一个 mOffsetMatrix 这个矩阵十分有用,后文提及。

Node

aiNode *root_node = aiScene->mRootNode  // root node
aiNode *child_node = root_node->mChildren[i] // get child node

aiNode 中除了存储父子关系相关信息外,最重要的属性就是 mTransformation 这就是相对于上一级 node 的变换矩阵。

这里我们又见到了 node 和 bone 两个说法,在 Assimp 的规定中,每一个 bone 必定会有一个相同名称的 node 与其对应,反过来不成立。每一个骨骼的变换矩阵就是同名节点的变换矩阵。

Bind Pose

Bind Pose 是根据 mesh 的顶点信息,不考虑骨骼,直接绘制得到的结果,也就是绘制对象初始的状态。另外,也可以用上 Assimp 读取出来的数据来验证。[2]

前文提到,bone space 的顶点位置是计算的前提,但是实际上,我们读取的到的 mesh 的顶点位置,是以 model space 也即模型空间来描述的,正因为如此,我们可以直接绘制出初始状态的模型来。那么如何得到 bone space 的位置呢?从理论上来说,逐级遍历得到 BoneToWorld transform matrix, 这个矩阵的逆矩阵就是 WorldToBone transform matrix, 即:

final_pos = (transform_matrix) * (transform_matrix)^(-1) * world_pos;  // means: final_pos = world_pos;

看起来无意义吧,因为逐级计算矩阵再求逆这个操作实在复杂,Assimp 直接提供了这个逆矩阵,就是 mOffsetMatrix, 上式可以写作:

final_pos = (transform_matrix) * (offset_matrix) * world_pos; // means: final_pos = world_pos;

可以利用这个方法验证 FBX 读取和计算是否正确。正常情况下应该和直接绘制的结果一样,如果不一样,就是某个地方出错了(最有可能出错的地方是逐级遍历节点计算变换矩阵)。

Animation

得到 bone space position 以后,计算每一帧的姿态就很简单了。aiScene 包含了若干个 aiAnimation,每个代表一组动画,每个 aiAnimation 包含一个 aiNodeAnim 的数组,称之为 mChannels, 根据 aiNodeAnim->mNodeName 找到对应的 node,那么这个 node 在某个特定时刻的 transform matrix 就可以通过对 Position, Rotation and Scaling 的插值计算出来,该矩阵仍然只代表相对父节点的变换,仍然通过逐级遍历得到每个节点的在特定时刻的,AnimationBoneToWorldTransformMatrix. 最后的计算:

final_pos = (animation_transform_matrix) * (offset_matrix) * world_pos;

Reference

[1] Skeletal Animation With Assimp

[2] can't get bones/skinning to work

Asset-Importer-Lib Documentation

Bones Animation - Matrices and calculations

Skeleton with Assimp 骨骼动画解析的更多相关文章

  1. Spine Skeleton Animation(2D骨骼动画)

    骨骼动画 首先我们来看到底什么是骨骼动画: 在早期的机器上,渲染本身已经占用了很多CPU资源,因此,对于渲染,往往采取的是一种空间换时间的策略,以避免在模型的渲染中继续加重CPU的负担.帧动画模型在这 ...

  2. three.js之初探骨骼动画

    今后的几篇郭先生主要说说three.js骨骼动画.three.js骨骼动画十分有意思,但是对于初学者来说,学起来要稍微困难一些,官方文档比较少,网上除了用圆柱体的例子就是引用外部模型的,想要熟练使用骨 ...

  3. CSharpGL(50)使用Assimp加载骨骼动画

    CSharpGL(50)使用Assimp加载骨骼动画 在(http://ogldev.atspace.co.uk/www/tutorial38/tutorial38.html)介绍了C++用Asism ...

  4. Unity骨骼动画资源解析与优化

    一,背景 最近发现项目的动画文件有点大,不光内存大,而且文件也很大,所以从这2个方面下手处理 二,动画文件大小优化 为了优化动画文件大小,我们可以先分析下文件,Ctrl+D将动画文件从FBX拷贝出来, ...

  5. cocos2dx骨骼动画Armature源码分析(一)

    源码分析一body { font-family: Helvetica, arial, sans-serif; font-size: 14px; line-height: 1.6; padding-to ...

  6. MD5骨骼动画模型加载(一)

    前面我们分析了静态模型OBJ格式,桢动画模型MD2,这篇主要分析骨骼动画MD5的一些概念并且实现. 混合桢动画有计算简单,容易实现等优点,但是在需要比较细致的效果时,则需要更多的关键桢,每桢都添加相同 ...

  7. 基于Babylon.js编写简单的骨骼动画生成器

    使用骨骼动画技术可以将网格的顶点分配给若干骨头,通过给骨头设定关键帧和父子关系,可以赋予网格高度动态并具有传递性的变形 效果.这里结合之前的相关研究在网页端使用JavaScript实现了一个简单的骨骼 ...

  8. three.js 自制骨骼动画(一)

    上一篇郭先生解析了一下官方的骨骼动画案例,这篇郭先生就要做一个稍微复杂一点的骨骼动画了,就拿一个小人下手吧.在线案例请点击博客原文.话不多说先上大图 骨骼动画在GUI上面都有体现.制作骨骼动画的步骤在 ...

  9. cocos2dx骨骼动画Armature源码分析(三)

    代码目录结构 cocos2dx里骨骼动画代码在cocos -> editor-support -> cocostudio文件夹中,win下通过筛选器,文件结构如下.(mac下没有分,是整个 ...

随机推荐

  1. Java集合框架之Set接口浅析

    Java集合框架之Set接口浅析 一.java.util.Set接口综述: 这里只对Set接口做一简单综述,其具体实现类的分析,朋友们可关注我后续的博文 1.1Set接口简介 java.util.se ...

  2. Leetcode之二分法专题-852. 山脉数组的峰顶索引(Peak Index in a Mountain Array)

    Leetcode之二分法专题-852. 山脉数组的峰顶索引(Peak Index in a Mountain Array) 我们把符合下列属性的数组 A 称作山脉: A.length >= 3 ...

  3. Leetcode之深度优先搜索(DFS)专题-1123. 最深叶节点的最近公共祖先(Lowest Common Ancestor of Deepest Leaves)

    Leetcode之深度优先搜索(DFS)专题-1123. 最深叶节点的最近公共祖先(Lowest Common Ancestor of Deepest Leaves) 深度优先搜索的解题详细介绍,点击 ...

  4. go-web程序的热更新

    前言: 一直编译累死人啊,该偷懒就得偷懒 当使用go开发web程序时,修改点代码就得编译,虽然编译速度很快,但是也累啊,想起java的spring-boot有热更新插件, php根本都不需要重启,go ...

  5. python内建Exception类型

    1.Exception类型及分层结构如下: BaseException +-- SystemExit +-- KeyboardInterrupt +-- GeneratorExit +-- Excep ...

  6. POJ-2155-Matrix二位树状数组应用

    题目: 一个只有0和1构成的二维平面,给你两种指令,一种是区间的更新,即0变为1,1变为0:一种是查询一个点是1还是0: 由于是二进制,所以每次更新在相应的点上加一,最后对2取余即可. 至于二维的树状 ...

  7. HDU-6333 Problem B. Harvest of Apples 莫队

    HDU-6333 题意: 有n个不同的苹果,你最多可以拿m个,问有多少种取法,多组数据,组数和n,m都是1e5,所以打表也打不了. 思路: 这道题要用到组合数的性质,记S(n,m)为从n中最多取m个的 ...

  8. ZOJ-3964 Yet Another Game of Stones

    Yet Another Game of Stones 题意: Alice 和 Bob 在进行取石子游戏, 现在一共有n堆石子, 每堆石头有ai个, 然后每堆石头有一个bi属性, 如果bi == 0, ...

  9. poj 3177 Redundant Paths(tarjan边双连通)

    题目链接:http://poj.org/problem?id=3177 题意:求最少加几条边使得没对点都有至少两条路互通. 题解:边双连通顾名思义,可以先求一下连通块显然连通块里的点都是双连通的,然后 ...

  10. poj 2777 Count Color(线段树(有点意思))

    题目链接 http://poj.org/problem?id=2777 题意:题意是有L个单位长的画板,T种颜色,O个操作.画板初始化为颜色1.操作C讲l到r单位之间的颜色变为c,操作P查询l到r单位 ...