Live2D动画引擎的图形学原理及实现
第一次看到live2D估计是群里看到的,第一次看的感觉就是这个动画看起来很奇怪,有点粗糙,搞不懂这种动画怎么都能火,不过看了一段时间以后,简直是越看越顺眼,在今年1月份的时候,萌生了自己也来做一个live2D的想法,没错,你没看错,这里指的是制作live2D,不是制作live2D模型
这里的制作live2D指的是,实现live2D的基础动画功能,完整实现其骨骼,动作追踪,网格建立,物理模拟到图元光栅化操作.
其实说困难吧,也不太困难,说不困难吧,算算也将近花了5个月时间来做.
这么一听瞬间高大上了是不是.
为了方便调试,我预先绘制了一个调试用的人设,这部分用sai就可以了
1.打个线稿
2.调整一下,上个涩
3.最麻烦的一步,将每一个图层,导出为独立的png图片
准备工作完成,启动Visual Studio
现在,我们可以开始来愉快的做live2D了
为此,我们先请出我们可爱的大学本科期间的一本基础教材<<计算机图形学>>来开始我们Live2D的基础原理说明
我们先来复习一下图形学的基础
首先,我们先对图元做个定义
- live2D的所有图形由图元分割
- 每个图元,由三个不共线的顶点构成,为一个三角形
- 每个顶点包含x,y坐标,每个由一个float32存储
- 每个顶点包含u,v纹理坐标,每个坐标范围为0-1.0f,超出范围的坐标视为无效映射坐标,每个由一个float32存储.
- 每个顶点包含一个法向量,表示该顶点所处图元的法向量,法向量由一个x,y,z组成,每个由一个float32存储
上面是图形学渲染中一个非常常用的结构,因为在渲染的过程中,live2D的模型不需要额外的投影变换矩阵进行顶点隐射(或者说使用了仿射变换),也不需要额外的ZBuffer来维护每个顶点的深度(因为我直接使用图层的方式对每个live2D图像进行管理,因此我直接使用画家算法对图层进行先后绘制即可),因此,我们可以将重点放在图元如何进行渲染中来.
幸好这也并不是什么很复杂的事情,我们直接使用扫描线填充算法来填充图元就可以了
对应伪代码如下
1.首先我们先计算扫描线的y坐标,设 y^{} 是一个整数,那么 y=y^{}+0.5
2.计算直线 y=n_i 与图元三角形左右交点
3.从左交点开始,逐一计算其映射的 (u,v) 坐标,并用该坐标映射到纹理中的像素,绘制扫描线
因此,当我们加载一个图形到live2D的项目中,其最终会依据一个2D图元的方式将图像最终显示出来
需要值得一提的是,为了进一步提高图元的渲染质量,在进行u,v纹理坐标隐射之前,笔者使用了双线性插值滤波,对纹理映射关系进一步处理
图元(三角形)的填充算法,在Live2D的渲染过程中,图元填充(纹理映射)是整个live2D项目的基石,在完成这一个关键步骤之后,所有的变换,都可以通过一些列的平移,旋转,缩放矩阵的级联并最终应用于顶点变换来完成.
到了这一步,我们将之前绘制好的所有图层加载进来,并准备为每一个图层设定网格(mesh)
到此,除了为每一个图层设定一个z坐标外,首当其冲要解决的是网格建立的问题,为此,不得不聊一聊著名的Delaunary三角剖分
先来看看定义部分(来自百度百科)
【定义】三角剖分:假设 V 是二维实数域上的有限点集,边 e 是由点集中的点作为端点构成的封闭线段, E 为 e 的集合。那么该点集 V 的一个三角剖分 T=(V,E) 是一个平面图 G ,该平面图满足条件:
1、除了端点,平面图中的边不包含点集中的任何点。
2、没有相交边。(边和边没有交叉点)
3、平面图中所有的面都是三角面,且所有三角面的合集是散点集V的凸包。
要满足Delaunay三角剖分的定义,必须符合两个重要的准则:
1、空圆特性:Delaunay三角网是唯一的(任意四点不能共圆),在Delaunay三角形网中任一三角形的外接圆范围内不会有其它点存在。如下图所示:
2、最大化最小角特性:在散点集可能形成的三角剖分中,Delaunay三角剖分所形成的三角形的最小角最大。从这个意义上讲,Delaunay三角网是“最接近于规则化的“的三角网。具体的说是指在两个相邻的三角形构成凸四边形的对角线,在相互交换后,六个内角的最小角不再增大。如下图所示:
以下是Delaunay剖分所具备的优异特性:
1.最接近:以最近的三点形成三角形,且各线段(三角形的边)皆不相交。
2.唯一性:不论从区域何处开始构建,最终都将得到一致的结果。
3.最优性:任意两个相邻三角形形成的凸四边形的对角线如果可以互换的话,那么两个三角形六个内角中最小的角度不会变大。
4.最规则:如果将三角网中的每个三角形的最小角进行升序排列,则Delaunay三角网的排列得到的数值最大。
5.区域性:新增、删除、移动某一个顶点时只会影响临近的三角形。
6.具有凸四边形的外壳:三角网最外层的边界形成一个凸多边形的外壳。
在Delaunay三角建立算法中,最著名的有Lawson及Bowyer-Watson算法,但lawson算法不论是速度还是实际应用的实用度都不如后者,因此在Live2D中笔者使用了后者作为三角剖分算法
其伪代码如下
- 构造一个超级三角形,包含所有散点,放入三角形链表。
- 将点集中的散点依次插入,在三角形链表中找出外接圆包含插入点的三角形(称为该点的影响三角形),删除影响三角形的公共边,将插入点同影响三角形的全部顶点连接起来,完成一个点在Delaunay三角形链表中的插入。
- 根据优化准则对局部新形成的三角形优化。将形成的三角形放入Delaunay三角形链表。
- 循环执行上述第2步,直到所有散点插入完毕。
- 剔除与超级三角顶点有关的三角形
但上述的伪代码是存在问题的,这个问题发生在后面加上的第五步,这可能导致剔除后产生一个非完整的凸包
因此,在第五步剔除三角形之后,应该预先遍历每一个有关顶点再次进行一次判断,以让其满足夹角为锐角的条件,以生成一个完整的凸包
之后,我们需要依据外围顶点的封闭区域,剔除掉封闭区域之外的三角形,最后建立网格
不管如何,当我们最终完成了三角剖分算法后,我们就可以着手将模型加载进来,正如你所见,每一个实际图层都绑定着一套网格
实际上整个live2D模型,也正是基于顶点变换的基础之上的
而基于这点,我们步入Live2D的下一个话题,骨骼动画,如果我们摸摸自己的身体,很容易总结出一个道理,在人体中的大部分骨骼,由一个关节节点连接到另一个关节节点上,而基于这点的拓展,我们同样可以为每一个图层设置一个关键的节点
为了简化说明下这个流程,笔者创建了一个三个图层组成的样例,每个图层由一条粗线组成
可以看到,每个图层都由一个关键节点进行控制,图层网格中的所有顶点,都围绕着该关键节点进行旋转,平移,伸缩变换,连接这些关键的节点,形成一个正确的骨骼系统
在live2D中,骨骼系统满足以下条件
1.所有的图层都有且只有一个节点
2.一个节点如果没有父节点,那么这个节点是根节点
3.一个节点可以有多个子节点,但一个节点最多只能有一个父节点
4.图层中所有的顶点以节点为参考点进行旋转平移缩放
5.父节点的任何变换将传递影响影响其所有子节点
于是,在动画系统中,我们可以通过操作骨骼节点,直接对模型进行动作编辑,它可以等效为一系列矩阵的级联操作-------动画由一系列骨骼节点组成,下一个节点的变换相当于上一个节点的变换加上本节点的变换,相当于一些列矩阵相乘起来,
简单来说就是
根节点的变换矩阵--->骨骼节点的变换矩阵--->顶点的变换矩阵-->额外的物理计算矩阵
至此,我们依照这个原理将整个模型的骨骼连接起来
这个时候,这个Live2D动画系统成为了一个正宗的纸娃娃动画系统,通过对骨骼节点的控制,我们就可以实现一个纸娃娃动画了
当然,当我们实现了一个纸娃娃系统,离live2D已经很接近了,但live2D相较于纸娃娃系统在动画中有更多的细节,你可能已经注意到,在上面的动画中,耳朵与头发有轻微幅度的摆动,这就是live2D中,对于某些顶点在物理上有额外的计算与模拟
我将它称之为弹性节点,通过对整个模型的平移,你可以很容易观察到弹性节点对于那些柔软的部位是如何进行物理模拟的
首先我们知道,每个图层的网格由若干个顶点控制,每个顶点互相连接
那么我们可以认为,对于那些弹性顶点,和静态顶点直接由一个可以视作一个弹簧连接,这样,我们可以直接给出弹性系数,来指定该顶点在动画中的运动规律,注意的是,这里的弹性系数和弹簧的弹力公式中的系数并不一致,为0表示这是一个静态顶点(刚体)
但多顶点之间级联会导致顶点运算变得非常复杂,因此,我们可以简化上述流程,视作每一个顶点与图层节点相连,距离节点越远,弹性系数越大,同时,运动过程中的阻力衰减也越弱
那么,剩下我们要做的,就是为这个弹力公式挑选一个合适的数学模型了.关于这部分,你可以在PainterEngine Live的代码中找到,笔者就不再进一步讨论了
到这里,有了纸娃娃动画,有了弹性节点,我们终于迎来了制作Live2D最后一个功能,我管它叫动作追踪(Action Tracer)
顾名思义,就是能够依据某个输入,完成某一类追踪类动作,例如,下面是一个非常简单的追踪例子,人物跟随着鼠标的运动进行运动,当然,鼠标的运动可以和机器识别相结合,绑定在摄像头中人人物进行联动
尽管我们的模型仍然是由一系列的二维图片组成,但3D渲染器的投影变换仍然能够给予我们足够的启发来完成这个让2D图像看起来像3D的功能
例如在上面的例子中,我们可以想象脸部的纹理贴在了一个类似于球面的曲面上,当我们尝试旋转这个球体的时候,当3D顶点坐标投影到2D坐标上时,我们可以近似认为这个坐标在保持u,v纹理坐标不变的情况下,在x,y方向上被压缩了
例如,下面的动画是以整个模型进行变换的(仅作为示范,其动画并不准确)
它能够实现小幅度的整体脸部及身体旋转功能,当然,商用的live2D有着更多的计算来完成这种效果,但对于我们这个简单版本的live2D,倒也足够了
最后说个后记
制作整个Live2D的核心渲染,骨骼框架和额外的弹性模拟与曲面贴图,其代码并不复杂大约在2-3k行左右,大约2周时间就完成了,但Live2D的配套的Editor占据了剩下5个月的所有时间
真是配套设施比核心功能还折腾的多,你可能以为笔者要开始吹一吹在这段时间学到了什么,实际上并没有,它涉及的知识,似乎就是你在本科时爱看不看的<<计算机图形学>>里的那堆东东,比起笔者老本行的语音信号处理和nlp里那些折磨人的玩意,实在是友好的多.
当然,上面的所有代码,都已经开源了,你可以在http://PainterEngine.com中找到editor和PainterEngine Live2D的所有核心和配套源代码
如果你喜欢这个项目,别忘了给笔者一个star
最后,随便做的一个live2D动画做结尾吧