本来章节的名字又要放飞自我,叫什么「与神之假身的接触」之类的,还好我刹车踩住了。
对不起最后还是没踩住2333
在第六节里,我们要为莉沫酱添加一些不同的表情。
这个章节还是草稿,它会在之后的commit中加入和谐内容,请您不要收好鸡儿,不要文明观球。
在这个章节,你需要准备:
- 更好的电脑
- 前面所有的代码
- 全靠蒙的知识
- Python3
- NumPy
- OpenGL
在之前的章节里,莉沫酱的五官位置都是固定的,现在我们来让她做一些不同的表情!
我们先来做一些简单的事情,把眉毛弯下来一点,做成一个嘲讽(?)的形态
首先,我们用上一节定义深度网格的方法把眉毛分割成三格——
深度:
- [0.65, 0.65, 0.65, 0.65]
- [0.65, 0.65, 0.65, 0.65]
如果把网格绘制出来,看起来应该是这样——
接下来,我们要让莉沫酱做出讽刺的表情。
我们同样用一个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)
眉毛变弯啦!(另一边我也偷偷加上了)
它看起来稍微有点抖……这是因为现在只分了三段,你可以适当地多分一点让它看起来更加自然。
接下来我们来给嘴巴也做上开合的效果——
一般来说,嘴是由三个图层构成的,包括上
、下
、颜色
,像是这样——
以上嘴唇为例,先把其他的图层隐藏起来,像是这样——
你可以让你的画师朋友在嘴巴上画一条线,来确定闭嘴时上下嘴唇的位置。然后你就能以它为基准,对上下嘴唇分别添加变形,让它们都刚好重合到这条线的位置上。
顺便说一下,因为顶点多起来以后变形很难写,所以我加了点法术——
魔法会侵蚀你的灵魂!
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
太棒了,看起来就像是莉沫酱真的在说话一样!
(对了,别忘了把颜色图层的变形也补上)
介绍完嘴巴的变形之后,你可以试着把眼睛的开合、眼球运动等变形耶实现一遍,有了这些变形之后,接下来我们就可以让莉沫酱的表情和自己的表情同步了。
添加表情有几种方法,一种是通过表情识别模型来控制,另一种是使用嘴的大小、眼睛的大小、眉毛的高度等特征分别驱动各个五官。
还有其他奇怪的方法比如按键盘……
因为我们没有表情识别模型,所以我们就搞用特征分别驱动的方法吧。
你可以用类似于第一章所说的简单的计算几何方法,计算出自己的脸的各种特征——在选择特征的计算方法时,别忘了缩放不变性。
有了这些特征之后,在渲染时就可以根据各个图层和这个特征有没有关联,来调整顶点位置——
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种变形,在绘制时逐个检查该变形是否包含下唇
图层,如果没有就跳过,这样就可以让各个图层绑定在各自的变形上啦。
最终的效果像是这样——