零、准备知识
今天我们来分析一下加载一个 Live2D 模型的过程,所以文章焦点都集中在 Live2D 的格式和 SDK 的 API 使用上,其他内容就不多解释了,主要是 cmake、C++ 和 JNI 的基本知识。如果在看源码的过程中搜索到本文,可能要失望而归了。
上一篇文章介绍了 Live2D 和官方 Android Demo 的 Java 层的实现,传送门:
[Android]摸鱼计划:给 App 也加一个 Live2D 吧 | 七日打卡
一、Live2D 的文件构成
(仅介绍 Demo 中的示例模型的文件内容,不能用作参考文档)
这次我们用 Haru 这个模型,上一篇使用的 Mark 虽然简单,但外观差了一点,谁不喜欢美少女呢?
.
├── Haru.2048
│ ├── texture_00.png
│ └── texture_01.png
├── Haru.moc3
├── Haru.model3.json
├── Haru.pose3.json
├── Haru.userdata3.json
├── expressions
│ ├── F01.exp3.json
│ ├── F02.exp3.json
│ ├── F03.exp3.json
│ ├── F04.exp3.json
│ ├── F05.exp3.json
│ ├── F06.exp3.json
│ ├── F07.exp3.json
│ └── F08.exp3.json
└── motions
├── haru_g_idle.motion3.json
├── haru_g_m01.motion3.json
├── haru_g_m02.motion3.json
├── haru_g_m03.motion3.json
├── haru_g_m04.motion3.json
├── haru_g_m05.motion3.json
├── haru_g_m06.motion3.json
├── haru_g_m07.motion3.json
├── haru_g_m08.motion3.json
├── haru_g_m09.motion3.json
├── haru_g_m10.motion3.json
├── haru_g_m11.motion3.json
├── haru_g_m12.motion3.json
├── haru_g_m13.motion3.json
├── haru_g_m14.motion3.json
├── haru_g_m15.motion3.json
├── haru_g_m16.motion3.json
├── haru_g_m17.motion3.json
├── haru_g_m18.motion3.json
├── haru_g_m19.motion3.json
├── haru_g_m20.motion3.json
├── haru_g_m21.motion3.json
├── haru_g_m22.motion3.json
├── haru_g_m23.motion3.json
├── haru_g_m24.motion3.json
├── haru_g_m25.motion3.json
└── haru_g_m26.motion3.json
看着挺多,可以分类整理成 7 部分。
Haru.2048
| 纹理(贴图)资源文件夹Haru.moc3
| 模型文件Haru.model3.json
| 模型全局配置Haru.pose3.json
| 模型姿势配置Haru.userdata3.json
| 用户数据配置expressions
| 人物表情文件夹motions
| 人物动作文件夹
(有很多的 "3" 是因为 Live2D 制作软件的版本号是 3)
二、加载模型的过程(Cubism SDK API)
模型的加载从 LAppLive2DManager 的构造函数开始,LAppLive2DManager 是一个懒加载的单例,负责持有并管理一个 Live2D 模型的生命周期,并处理模型和用户的交互操作。Live2D 模型被封装在 LAppModel 对象中,通过 SetupModel 初始化。
模型载入的起始文件是固定的,也就是模型的全局配置文件(xxx.model3.json),代码里也确实是这么处理的:
// ModelDir[]に保持したディレクトリ名から
// model3.jsonのパスを決定する.
// ディレクトリ名とmodel3.jsonの名前を一致させておくこと.
std::string model = ModelDir[index];
std::string modelPath = ResourcesPath + model + "/";
std::string modelJsonName = ModelDir[index];
modelJsonName += ".model3.json";
ReleaseAllModel();
_models.PushBack(new LAppModel());
_models[0]->LoadAssets(modelPath.c_str(), modelJsonName.c_str());
全局配置文件读取后转换成 CubismModelSettingJson 对象,后面的顺序是根据 CubismModelSettingJson 决定的。加载模型的过程要调用 Java 代码加载 assets 的文件,我们可以在这里添加 log 查看具体的读取顺序。
将模型从文件读取到内存后就是一个 CubismUserModel,要把模型数据转换成图像绘制到屏幕上,接下来就是根据 CubismUserModel 初始化 CubismRenderer。
void CubismUserModel::CreateRenderer()
{
if (_renderer)
{
DeleteRenderer();
}
_renderer = Rendering::CubismRenderer::Create();
_renderer->Initialize(_model);
}
此时还没读取到 texture,看上面的日志顺序,texture 文件是最后读到的。CubismRenderer 初始化之后就是读取纹理了,读取到 png 文件之后调用了一系列 OpenGL 的函数创建纹理对象,最后将模型和纹理绑定:
GetRenderer<Rendering::CubismRenderer_OpenGLES2>()->BindTexture(modelTextureNumber, glTextueNumber);
以上过程准备完毕之后,当 nativeOnDrawFrame 触发的时候,调用 CubismRenderer::DrawModel 更新屏幕上的绘制内容即可。
一不小心又搞了一天的 Live2D,明日方舟同人作品还挺多,不过怎么没人做克洛丝呢。
去年学过的日语和被安排的 JNI+OpenCV 的工作经历终于发挥了作用,我也没想到摸鱼时间翻了翻 Cubism SDK Demo 的源码就能梳理出整个 Demo 的工作流程,不得不说,多学知识永远不亏。