Skip to content

Latest commit

 

History

History
178 lines (112 loc) · 5.73 KB

6.md

File metadata and controls

178 lines (112 loc) · 5.73 KB

从零开始的自制Vtuber: 6.与神之假身的接触

本来章节的名字又要放飞自我,叫什么「与神之假身的接触」之类的,还好我刹车踩住了。

对不起最后还是没踩住2333

在第六节里,我们要为莉沫酱添加一些不同的表情。

警告

这个章节还是草稿,它会在之后的commit中加入和谐内容,请您不要收好鸡儿,不要文明观球。

准备

在这个章节,你需要准备:

  • 更好的电脑
  • 前面所有的代码
  • 全靠蒙的知识
  • Python3
  • NumPy
  • OpenGL

简单变形-眉毛

在之前的章节里,莉沫酱的五官位置都是固定的,现在我们来让她做一些不同的表情!

我们先来做一些简单的事情,把眉毛弯下来一点,做成一个嘲讽(?)的形态

首先,我们用上一节定义深度网格的方法把眉毛分割成三格——

深度:
    - [0.65, 0.65, 0.65, 0.65]
    - [0.65, 0.65, 0.65, 0.65]

如果把网格绘制出来,看起来应该是这样——

./图/6-1.jpg

接下来,我们要让莉沫酱做出讽刺的表情。

我们同样用一个yaml文件来记录每个图层的变形信息,比如以这样的形式定义一个变形——

讽刺: 
    头/五官/眉毛/左:
        位置:
            - [[0, 0.01], [0, -0.01], [0, -0.02], [0, -0.02]]
            - [[0, 0.01], [0, -0.01], [0, -0.02], [0, -0.02]]

这个矩阵中的每个(dx, dy)即表示莉沫酱的左眉偏移的座标,比如第一个[0, 0.01]即为让眉毛左上角的顶点横向移动0单位,纵向移动0.01单位。

然后这个yaml加载到你的代码里,在对应的图层(眉毛)上画出来看看——

if '位置' in 变形[图层名]:
    d = np.array(变形[图层名]['位置'])
    a[:, :2] += d.reshape(a.shape[0], 2)

./图/6-2.jpg

眉毛变弯啦!(另一边我也偷偷加上了)

它看起来稍微有点抖……这是因为现在只分了三段,你可以适当地多分一点让它看起来更加自然。

麻烦变形-张嘴

接下来我们来给嘴巴也做上开合的效果——

一般来说,嘴是由三个图层构成的,包括颜色,像是这样——

./图/6-3.jpg

以上嘴唇为例,先把其他的图层隐藏起来,像是这样——

./图/6-4.jpg

你可以让你的画师朋友在嘴巴上画一条线,来确定闭嘴时上下嘴唇的位置。然后你就能以它为基准,对上下嘴唇分别添加变形,让它们都刚好重合到这条线的位置上。

顺便说一下,因为顶点多起来以后变形很难写,所以我加了点法术——

魔法会侵蚀你的灵魂!

d = 变形[图层名]['位置']
if type(d) is str:
    d = eval(d)

这样一来,就可以用几个二次函数来控制嘴唇的变化了——

闭嘴: 
    头/五官/嘴/下: 
        位置:
            '[[-0.001, -abs(i-4)**2/1280] for i in range(9)], [[-(i-4)/100, -abs(i-4)**2/1280 + 0.02] for i in range(9)]'
    头/五官/嘴/上: 
        位置:
            '[[0, abs(i-4)**2/960-0.033] for i in range(9)], [[0, abs(i-4)**2/960-0.027] for i in range(9)]'

调整到这个重合的位置之后,我们就来测试一下张嘴-闭嘴的效果吧。

我们刚才把张嘴的状态加上一个变形矩阵后得到了闭嘴的状态,那怎么得到张嘴和闭嘴中间的状态呢——只要给变形矩阵乘一个0~1间的小数,就可以实现不同强度的变形插值啦。

像是这样,我们先用一个cos函数控制强度,渲染出来测试一下——

f = (math.cos(time.time()*k)+1)/2
a[:, :2] += d.reshape(a.shape[0], 2) * f

./图/6-5.webp

太棒了,看起来就像是莉沫酱真的在说话一样!

(对了,别忘了把颜色图层的变形也补上)

绑定

介绍完嘴巴的变形之后,你可以试着把眼睛的开合、眼球运动等变形耶实现一遍,有了这些变形之后,接下来我们就可以让莉沫酱的表情和自己的表情同步了。

添加表情有几种方法,一种是通过表情识别模型来控制,另一种是使用嘴的大小、眼睛的大小、眉毛的高度等特征分别驱动各个五官。

还有其他奇怪的方法比如按键盘……

因为我们没有表情识别模型,所以我们就搞用特征分别驱动的方法吧。

你可以用类似于第一章所说的简单的计算几何方法,计算出自己的脸的各种特征——在选择特征的计算方法时,别忘了缩放不变性。

有了这些特征之后,在渲染时就可以根据各个图层和这个特征有没有关联,来调整顶点位置——

def 附加变形(self, 变形名, 图层名, a, b, f):
    变形 = self.变形组[变形名]
    if 图层名 not in 变形:
        return a, b
    if '位置' in 变形[图层名]:
        d = 变形[图层名]['位置']
        if type(d) is str:
            d = eval(d)
        d = np.array(d)
        a[:, :2] += d.reshape(a.shape[0], 2) * f
    return a, b
def 多重附加变形(self, 变形组, 图层名, a, b):
    for 变形名, 强度 in 变形组:
        a, b = self.附加变形(变形名, 图层名, a, b, 强度)
    return a, b
a, b = self.多重附加变形([
    ['眉上', 眉上度],
    ['左眼远离', 眼睛左右],
    ['右眼远离', -眼睛左右],
    ['左眼闭', 闭眼强度],
    ['右眼闭', 闭眼强度],
    ['闭嘴', 闭嘴强度],
], 图层.名字, a, b)

比如下唇这个图层,可能会同时进行以上6种变形,在绘制时逐个检查该变形是否包含下唇图层,如果没有就跳过,这样就可以让各个图层绑定在各自的变形上啦。

最终的效果像是这样——