"真正的不幸是利己主义。"

引言

  也许是国美高材生肏了大半学期的呸,导致直到要结课前两星期才来找到我和牛哥提出了这次的奇葩需求。能用树莓派来做IoT(显然第一个是大写的I而非小写的l,这是相当重要的注意事项,也许你就会因为一次错误的读音而被认识的技术巨牛打一辈子黑枪。试想若干年之后你的每一份简历上都会被开核技术高超的技术大牛烙上il不分的标签,就令人不寒而栗)不禁令人感叹双一流的雄厚科研实力,而在IoT之中再缝入unity3D,更让人对任课老师强化使命担当,勇于创新突破的伟大精神感到敬佩。很难想象在如此全面一流教育中诞生的全栈高材生会是一个正大光明画着南桐雅作的耽美画手,过于先锋与前卫的艺术创想实属是目前迂腐陈旧的我无法品鉴的。但被这样一位虽素未谋面的专业课老师大脑升级到不能自己的我,还是答应这一份奇葩的需求(大部分原因只是牛神踢皮球,桀桀,我的球技太烂辣)。当然在熬夜学习unity,终于完成需求后又被高材生告知做错了捏,都是后话了。一想到我初中许多被某人借走就了无音讯的文具,中考毕业由于某个霓虹人因为忘记回家还要办签证而泡汤的日本之旅,以及已经半年没看见的我的wacom笔盒,这也就不足为奇了。
  牛神踢出了皮球,夫哥白兰了课设,我学会了用unity搞二刺猿模型,可能唯一失去的只有我电脑d盘里的50个g罢了。

MMD模型与unity

  做unity时,没有现成大碗好用的人物模型是其中一大痛点,但MMD有很多免费配布的精致二刺猿模型,拿来练练手不失为一项选择。虽然mikumikudance使用pmx,pmd格式的模型文件,而unity3D基本使用fbx格式,但由两种格式都是多边形建模,可以将两者的模型文件进行转换。

pmx格式fbx格式模型之简单研究

  遗憾的是密窟密窟当思的日文文档我完全看不懂,只能去研究洋人的翻成英文的版本。可惜我不是CET6 580的上海国奖哥,也不是600的国际公民牛神,只能用我贫瘠的英语知识读个糊涂。而fbx的数据格式一目了然,不可谓能从中窥见连个纸牌对对碰的页游都做不明白的日本程序员有多么拉。

FBX

  让我们看一段fbx

1
2
3
4
5
6
7
8
9
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
FBXHeaderExtension:  {
FBXHeaderVersion: 1003
FBXVersion: 7500
CreationTimeStamp: {
Version: 1000
Year: 2022
Month: 5
Day: 14
Hour: 22
Minute: 4
Second: 22
Millisecond: 796
}
Creator: "FBX SDK/FBX Plugins version 2016.1"
SceneInfo: "SceneInfo::GlobalInfo", "UserData" {
Type: "UserData"
Version: 100
MetaData: {
Version: 100
Title: "PMX2FBX Mesh for Unity"
Subject: "PMX2FBX Mesh for Unity"
Author: "PMX2FBX Development build."
Keywords: "PMX2FBX"
Revision: "0.0.0"
Comment: "No comments."
}
Properties70: {
P: "DocumentUrl", "KString", "Url", "", "D:\Users\Ryujo_PAD\unity\test1\Assets\帕朵菲莉丝\帕朵菲莉丝.fbx"
P: "SrcDocumentUrl", "KString", "Url", "", "D:\Users\Ryujo_PAD\unity\test1\Assets\帕朵菲莉丝\帕朵菲莉丝.fbx"
P: "Original", "Compound", "", ""
P: "Original|ApplicationVendor", "KString", "", "", ""
P: "Original|ApplicationName", "KString", "", "", ""
P: "Original|ApplicationVersion", "KString", "", "", ""
P: "Original|DateTime_GMT", "DateTime", "", "", ""
P: "Original|FileName", "KString", "", "", ""
P: "LastSaved", "Compound", "", ""
P: "LastSaved|ApplicationVendor", "KString", "", "", ""
P: "LastSaved|ApplicationName", "KString", "", "", ""
P: "LastSaved|ApplicationVersion", "KString", "", "", ""
P: "LastSaved|DateTime_GMT", "DateTime", "", "", ""
}
}
}
GlobalSettings: {
Version: 1000
Properties70: {
P: "UpAxis", "int", "Integer", "",1
P: "UpAxisSign", "int", "Integer", "",1
P: "FrontAxis", "int", "Integer", "",2
P: "FrontAxisSign", "int", "Integer", "",1
P: "CoordAxis", "int", "Integer", "",0
P: "CoordAxisSign", "int", "Integer", "",1
P: "OriginalUpAxis", "int", "Integer", "",-1
P: "OriginalUpAxisSign", "int", "Integer", "",1
P: "UnitScaleFactor", "double", "Number", "",1
P: "OriginalUnitScaleFactor", "double", "Number", "",1
P: "AmbientColor", "ColorRGB", "Color", "",0,0,0
P: "DefaultCamera", "KString", "", "", "Producer Perspective"
P: "TimeMode", "enum", "", "",0
P: "TimeProtocol", "enum", "", "",2
P: "SnapOnFrameMode", "enum", "", "",0
P: "TimeSpanStart", "KTime", "Time", "",0
P: "TimeSpanStop", "KTime", "Time", "",46186158000
P: "CustomFrameRate", "double", "Number", "",-1
P: "TimeMarker", "Compound", "", ""
P: "CurrentTimeMarker", "int", "Integer", "",-1
}
}

由于完全没有建模的经验,所以我就瞎几把纯臆想来分析。FBX文件是node节点格式的文件,本质上是多组嵌套的节点列表,而fbx文件节点中的元素就是对模型几何数据的包装。FBX文件中的属性是用来添加数据的方法,属性由每个节点中的Properties70元素中的子元素表示。
  上列名叫帕朵菲莉丝的模型数据中存在两个父节点FBXHeaderExtensionGlobalSettings。其中GlobalSettings用于存储模型的全局设置信息,观察Properties70中的元素,辟如UpAxis的值为1,则表示规定Y轴向上,OriginalUnitScaleFactor值为1表示模型中1单位为1米。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Objects:  {
Geometry: 83465080, "Geometry::U_CharMesh_0", "Mesh" {
Vertices: *66465 {···}
···
}
···
Model: 83438896, "Model::U_Char", "Null" {
Version: 232
Properties70: {
P: "ScalingMax", "Vector3D", "Vector", "",0,0,0
}
Shading: Y
Culling: "CullingOff"
}
···
}

如上对对象的定义是fbx文件中最重要的部分,因为如材质,几何顶点,法线,UV坐标信息等模型最重要的内容都存储在了objects节点之中。对于父节点下的子对象,这个叫帕朵菲莉丝的模型中的对象有如下几种

对象名 存储数据
Geometry 顶点、法线、UV···
Model 模型的平移,旋转,镜像等信息
Material 模型的材质对象
Deformer 变形器,通常包含蒙皮、骨骼等

PMX

  相似的pmx与fbx的数据格式基本一致,只是我用文本编辑器死活打不开.pmx文件,只能引用国外二刺猿翻译成英文的文档罢。
  对应FBX的对象,PMX在全局中定义了模型的引索类型,如

类型 作用
Vertex 顶点
Bone 骨骼
Material 材质
Texture 纹理
Morph 变形
Rigidbody 刚体

不难发现由于密窟密窟当思面向二刺猿人形动画的多边形建模方式,PMX的引索类型更加细化,特别是面向骨骼与变形方面,强化了对IK骨骼的表现。但其中不乏有诸如表示枠这样令人倍感困惑的词语。如果从日语上理解,这三个汉字基本都与中文同义,表示[ヒョジ]即是展示,[わく]则是框架,但合在一起的展示框架到底什么东西。如果从英语上,日本人给出了一个更让人疑惑的display frame,展示帧。但拥有丰富黄油素养的我(指I社游戏与3D妹抖)一下就明白了,表示枠不就是3D黄油里那些形体模型以外的物品插件吗。二刺猿人物特别喜欢在衣服上搭载精致的小装饰,把自己裹得跟个圣诞树一样,突出一个精致感。
  然而FBX有自己的的SDK,令人感慨。

MMD模型导入

  使用MMD4Mecanim这个插件能十分方便的把密窟密窟当思的模型导入到unity中不排除导入过程中可能会出现的材质丢失

1
2
3
4
5
6
7
8
9
10
public const int FullBodyIKTransformElements = 4;
public const int FullBodyIKTargetMax = 36;

public static AnimData BuildAnimData(TextAsset animFile);
public static ExtraData BuildExtraData(TextAsset extraFile);
public static ModelData BuildModelData(TextAsset modelFile);
public static FullBodyIKGroup GetFullBodyIKGroup(int fullBodyIKTargetIndex);
public static FullBodyIKGroup GetFullBodyIKGroup(ref int fullBodyIKTargetIndex);
public static string GetFullBodyIKTargetName(int fullBodyIKTargetIndex);
public static string GetFullBodyIKTargetName(out FullBodyIKGroup fullBodyIKGroup, int fullBodyIKTargetIndex);

观察一下插件的脚本中的MMD4MecanimData类,不出所料一大半都是对密窟密窟当思模型文件中IK骨骼以及约束的重新定向。可惜没有文档,日本人只写了使用指南。
插件特性:

  • 将PMX/PMD+VMD转成FBX,转成Unity可以导入的格式
  • 骨骼调整以适应 Mecanim(unity的动画系统)
  • 用 Bullet Physics 再现头发和裙子的行为(烘焙成运动)
  • 材质和着色器初始设置
  • 面部表情变形修正脚本
  • 插件版 Bullet Physics 实时再现物理行为
  • AnimationType:初始化为 Generic 或 Humanoid

好像也可以导入密窟密窟当思的动作文件?但指南后面又写了Humanoid モーションはできません。(変換時に一緒に変換した VMD のみ再生可能) 人形动画不可能,只能够播放一起转换的VMD文件。是说不能使用unity的人形动画吗,但在我后来的尝试中在unity里重新给模型绑上骨骼一样能使用人形动画,我暂且蒙古。
珈乐 转换后就能成功导入。

人形动画与动画状态机控制

  在模型导入设置的Rig中将动画类型改为人形动画,进入骨骼调整,就能为模型绑定动画了。但令人疑惑的是我最初使用珈乐的模型绑定动画,脚踝与小腿处的骨骼都是扭曲的。模型附带的使用说明也写了“该模型人物是真实身高比例,在MMD中载入动作文件后,为避免出现动作幅度过大的问题需要重新调整骨骼帧”。笑死完全不会,我还是用点正常的模型罢。
  unity中播放动画需要动画状态机的控制,正如数电中的有限状态机,通过外界条件改变就能触发状态机向次态改变,从而使unity中的游戏人物能完成各式的操作。
动画状态机
为这一状态机添加三个动画状态,再从网上扒三个动画绑定在上面。众所周知数电中状态机一定有初态,一般来说终态与不安全状态最后都会回到初态。unity中的状态机自带Exit Entry Any State三个状态,进入exit后对象将不能进入任何状态,常用于角色死亡时。entry则表示对象进入状态机后的初态,任何非exit状态结束后都会进入entry后的第一个状态。any state后的则表示任何时候都能触发的状态。通过改变设定的条件值就能完成从一个状态到另一个状态的跳变,当然在游戏中我们可以通过绑定在UI或是人体输入设备上的脚本实现交互地切换操控人物的动作。
比如说上面提到过的动画状态机的脚本

1
2
3
4
5
6
7
8
9
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
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class AnimeController2 : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{

}

// Update is called once per frame
void Update()
{

}
public void Stop()
{
this.gameObject.GetComponent<Animator>().SetBool("kick1", false);
this.gameObject.GetComponent<Animator>().SetBool("kick2", false);
}
public void DoKick1()
{
this.gameObject.GetComponent<Animator>().SetTrigger("kick1_t");
}
public void DoKick2()
{
this.gameObject.GetComponent<Animator>().SetTrigger("kick2_t");
}
private string getCurrentClipInfo()
{
AnimatorClipInfo[] m_CurrentClipInfo = this.gameObject.GetComponent<Animator>().GetCurrentAnimatorClipInfo(0);
return m_CurrentClipInfo[0].clip.name;
}
}

脚本要做的无非就是改变所设定条件的值来使状态机切换当前动画,这个值可以是布尔型,数值型,文本甚至一个单一的标志符,非常简单。
将此动画状态机绑定在人物模型上,再将脚本绑定在按钮上就能简易地使用。看一段使用YYB式初音模型的实际效果
初音踢
头发的物理效果来自MMDM插件生成,只能说如果物理配件过多会导致物理效果失效并使你的二刺猿人物模型像插了电动棒一样抽搐。只想用插件不想使用Blender重新为模型加物理的懒狗建议使用物理布料较少的模型(意味深)。如果在脚本控制中同时改变模型的xy轴距离就能实现人物在平面上的行走。

仍在施工···