捏脸有两种做法,一种是利用骨骼变换,一种是利用Blend Shape。
骨骼法是在脸的内部正常建立骨骼并蒙皮,并利用骨骼的缩放系数,位移来改变脸的外形。但由于是数学计算的结果,设计时难以直接对最终效果进行调整。
BlendShape则是正常做多个种类的脸型并离线出差异数据,设计的时候比较简单,但叠加的时候也不太好控制,而且数据量较大。
由于现在游戏都需要利用BlendShape来实现人物的表情,重叠的BlendShape不易处理。而骨骼法只是制作上麻烦点,但可控性较高,而且数据量也小,所以大部分游戏都是用骨骼来实现捏脸的。
骨骼的设计是唯一的难点
具体怎么布置骨骼可参考上图,其实并不需要太多的控制项,更细节的可动部分(比如口型和眼睛)是交给BlendShape完成的,骨骼仅仅用来布置捏脸需要变化的部分。
通常会在正常的骨骼上加上一个Position(0,0,0),Scale(1,1,1)的节点,然后让它代替父节点进行蒙皮,并把它作为捏脸时的可变数据。这样数值是单位化的,捏脸时容易处理。不希望缩放参数影响到子节点的时候也可以这样做。
这样直接改Bone的Transform数值就能看到变化了。
相比脸型,眼睛需要的节点数更多,因为需要针对眼球等多个物体处理,眼角长度和上挑都要可控,是捏脸骨骼最复杂的部分。
让捏脸变得人性化
虽然提供全部可变骨骼就能做出任何一种脸型,但那样只会让玩家轻易捏出怪物来。
通常的做法是给出一组可调整值的数值,对应骨骼的一个或者多个单位数值,让玩家在0-1之间选择。
至于这些调整数值背后的逻辑——
我选择让一个调整项同时影响多个骨骼数据,每个骨骼数据只影响某条骨骼的某个值,并且可以指定动画曲线,以应对弧面上的位移。
通过组合就可以应对各种情况(眼睛这些地方必然需要影响多个骨骼)。
将Value值调整到某个极值,然后修改Min,Max查看效果,修改Curve曲线处理中间状况,生成捏脸数据的过程还是比较直观的。设计好之后,将序列化的数据存储起来,以后要用的时候载入并只允许修改Value的值,就可以应用在游戏的捏脸部分了。
而且这个编辑器的实现也是非常简单的,利用Unity自己的数据序列化面板就可以了。
[AppleScript] 纯文本查看 复制代码
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
|
去掉捏脸用的无用数据
很显然,我们为了实现捏脸加了许多没用的骨骼数据,这些都会影响到游戏运行时的效能。虽然量并不是很大,但要移除也并不困难。
如果不考虑BlendShape,其实只需要调用Unity的BakeMesh方法,替换Mesh并删掉骨骼就可以了。
但如果有BlendShape,Bake后的Mesh数据应用BlendShape后,会和以前有微量的偏移。这是因为BlendShape是在骨骼变化前先应用的,而Bake后,相当于是在骨骼变化后应用。
两者的差异和骨骼的变化强度有关,也和BlendShape的改变幅度有关,但确实在通常情况下差异不大(第三幅图是前两幅图的叠加,可以看到在脸部表情影响到的部分产生了微量的偏移)
但是即使少也是存在的。
解决方法是先将每一个BlendShape分别应用到Mesh上,再进行骨骼变换,蒙皮生成网格数据,然后再减掉Bake后的Mesh,重新生成一个新的BlendShape。
这其实相当于重做了一次BlendShape的生成过程(两个模型间求差值),数据就是完全准确的了。
下面的代码除了修正BlendShape外,还有蒙皮网格烘焙的代码实现。
[AppleScript] 纯文本查看 复制代码
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 |
|
只烘培部分骨骼
上面的是将整个头部烘培成Mesh,但有时候我们也会想保留部分骨骼(例如头发)供捏脸之外的部分使用。而如果是体型部分的自定义,就更需要仅对部分骨骼烘培的功能。
虽然看上去骨骼的多层级和多权重难以下手,实际上也没有多困难。
——只要把不需要烘培的骨骼重置回未经蒙皮变换时的状态,也就是将那些骨骼的Transform设置为对应bindposes的逆,它们就不会对需要烘培的骨骼造成影响。
[AppleScript] 纯文本查看 复制代码
1 |
|
bindposes本身就是Mesh空间相对于骨骼结点空间的变换矩阵……的逆,Mesh空间的顶点乘bindposes相当于将自己转换到骨骼结点所在空间,所以再做一次“骨骼 -> 世界空间变换”就能完成蒙皮,这也是上面的蒙皮代码实现的原理,实在不懂可自行查阅蒙皮相关资料。
我这里再求一次bindposes的逆,就得到了骨骼结点相对于Mesh空间的矩阵,实际上就是骨骼最开始所在的位置。将骨骼移动回这个位置,就相当于这个骨骼没有参与蒙皮,也就不会影响到其他需要蒙皮的骨骼。
设置Transform,而非直接跳过蒙皮阶段,是为了让需要烘培的结点的父结点位置正确。比起重新计算需要烘培的结点的Matrix4x4,这种方式会简单一点(但是性能确实差一些)
之后走正常的BakeMesh流程就可以了。
完成后还需要单独移除已经进行烘培,实际上已经无效的骨骼的boneWeight。所有boneWeight里涉及到那些骨骼的地方,都需要重置为骨骼的根节点RootBone的序号,也就是0。
[AppleScript] 纯文本查看 复制代码
1 2 3 4 5 6 7 8 |
|
对于SkinMesh来讲,并不存在删除骨骼结点一说,不管怎么样都会至少做一次相对于根结点的骨骼变换。把所有数据都重置为0是不行的,至少要有留下一个{boneIndex0 : 0, weight0 : 1f}。
烘培完后还需要将骨骼的状态再次恢复。
不过如果是游戏运行时,其实并不需要专门重置骨骼,因为骨骼刚加载出来的时候本来就是重置好的。只要先加载捏脸的骨骼数据,烘培,修改BoneWeight,再加载其他和骨骼相关的部分,烘培和非烘培就不会有冲突了。
然而模型从哪里弄?
捏脸的技术难度其实就这些(烘培那段估计大部分人也不用),主要的难点还是骨骼和捏脸数据的设计。
我这个模型是从HoneySelect里扒的,它的模型数据全在abdata这个文件夹下以AssetBoundle形式存在,所以很简单就能扒出来。
但UnityStudio这些破解工具导出的文件迄今为止依然会丢失BlendShape,所以需要用UnityAPI直接读取ab文件并另存为。
HoneySelect其实也有捏脸参数的配置文件,有兴趣可以自己扒过来以节约配置参数的时间(这个恐怕才是工作量的大头)
工程地址:https://pan.baidu.com/s/1ggwsY3p#list/path=%2F
http://www.manew.com/thread-113711-1-1.html