将 Live2D Cubism 模型一键打包为自包含 HTML 文件

支持模型/纹理/着色器/脚本全部 Base64 内嵌到单个 HTML,浏览器直接双击打开即可看到 Live2D 模型,无需服务器、无需 npm、无需任何运行时依赖。

项目结构

Samples/Live2DMTF_QtCpp/

├── CMakeLists.txt # CMake 构建配置

├── build.bat # 一键编译脚本

├── deploy.bat # 部署脚本 (windeployqt 补全 Qt DLL)

├── src/ # 源码

│ ├── main.cpp # 入口

│ ├── mainwindow.h/cpp # 主窗口 (UI + 事件处理)

│ ├── model_discovery.h/cpp # 模型发现 (.model3.json 解析)

│ ├── resource_collector.h/cpp # 资源收集 (Base64 编码)

│ ├── html_generator.h/cpp # HTML 生成 (JS 补丁 + 拦截器注入)

│ ├── verbose_logger.h/cpp # 日志

│ └── resource_paths.h # 运行时数据路径常量

└── build_test/ # 编译输出目录

编译与部署

环境

组件

推荐版本

路径(默认)

MinGW-w64

13.1.0

D:\MSVCQt\Tools\mingw1310_64

Qt SDK

6.11.0 (mingw_64)

D:\MSVCQt\6.11.0\mingw_64

CMake

3.28+

D:\MSVCQt\Tools\CMake_64\bin

Windows

10/11 x64

\

编译


build.bat

脚本自动执行 cmake -G "MinGW Makefiles"mingw32-make -j4,产物 build_mingw\Live2DMTF.exe

部署

deploy.bat

将 EXE + data 目录复制到 jlasr\,并调用 windeployqt 补全所有 Qt 依赖 DLL,生成可分发的完整程序包。

CMake 参数

参数

说明

CMAKE_CXX_STANDARD

17

C++17 标准

CMAKE_AUTOMOC

ON

自动 Qt MOC 预处理

add_executable(... WIN32 ...)

\

Win32 子系统

Qt6::Core Qt6::Gui Qt6::Widgets

\

Qt 依赖

POST_BUILD 命令

\

自动复制 15 个运行时数据文件到 data/

核心技术

模型发现 model_discovery.cpp

数据结构:

struct ModelInfo {

    QString modelName;

    QString modelDir;

    QMap<QString, QStringList> files;       // "moc"→["warma.moc3"], ...

    QMap<QString, QStringList> motionGroups; // "Idle"→["motions/yu.motion3.json"], ...

};

解析流程:

1. 在指定目录下查找 *.model3.json

2. 用 QJsonDocument::fromJson() 解析 JSON

3. 遍历 FileReferences 节点提取 Moc/Textures/Physics/Pose/UserData/Motions

4. 独立扫描 motions/ 子目录 (拾取未在 model3.json 中声明的动作文件)

5. 独立扫描 *.cdi3.json 文件

资源收集 resource_collector.cpp

数据结构:

struct ResourceEntry {

    QString appUrl;   // "../../Resources/warma/warma.moc3"

    QString srcPath;  // "C:\...\warma\warma.moc3" (绝对路径)

    QString b64;      // Base64 编码后的字符串

    QString mime;     // MIME 类型

    qint64  rawSize;  // 原始字节大小

};

返回类型: QPair<QMap<QString, ResourceEntry>, QVector<ResourceEntry>> — 字典用于 O(1) 查找,列表用于顺序遍历复制。

Base64 编码: 使用 Qt 内置的 QByteArray::toBase64(),无需外部库。编码膨胀率约 +33%。

MIME 检测: guessMime() 通过文件扩展名映射: .png→image/png, .json→application/json, .moc3→application/octet-stream, .frag/.vert→text/plain

详细日志 (verbose): 每个资源文件输出 hex dump (每行 64 字节, 最多 200 行)、完整 Base64 (每行 120 字符)、文件大小与膨胀率。

HTML 生成 html_generator.cpp

JS 补丁函数: patchBuiltJs() 通过 QString::replace() 对 Vite 打包的应用代码做字符串替换:

原始 JS: warma新模型名

原始 JS: this._viewMatrix.scale(1,1) → this._viewMatrix.scale(1.2,1.2)

原始 JS: ve=.8, → ve=0.5,

原始 JS: n.createTextureFromPngFile(...height*0.95...) → 全屏拉伸

原始 JS: //# sourceMappingURL=... → 移除 (减小体积)

拦截器注入: 生成两种 JavaScript 拦截器:

- 内嵌模式 embed_res=True): 注入 __RESOURCE_MAP 字典, 包含所有资源的 Base64 数据window.fetch 被劫持, 精确匹配或后缀匹配返回内存中的 Response

- 外挂模式 embed_res=False): 注入 __URL_MAP, 将 ../../Resources/... 映射为 ./res/_.._.._Resources_... 文件路径, 拦截器重定向 fetch() 到此路径

Image 拦截器通过 Object.defineProperty(HTMLImageElement.prototype, 'src', ...) 劫持 src setter, 内嵌模式下替换为 data:image/png;base64,... Data URI。

异步生成 QThread + GenerateWorker

核心生成逻辑在当前 QThread 之外的**工作线程**中执行, 避免阻塞 UI:

MainWindow::onGenerate()

new QThread + GenerateWorker::moveToThread()

↓ signals/slots

├── simpleLog(const QString&) → appendLog()

├── verboseLog(const QString&) → appendDetail()

├── progressChanged(int,int) → onProgress()

├── elapsedTime(double) → onElapsed()

└── finished(bool,QString) → onDone()

批量日志刷新 flushLog/flushDetail): 日志和详细数据先存入 QStringList 缓冲区, 通过 100ms 间隔的 QTimer::singleShot 定时器批量写入 QPlainTextEdit, 避免频繁 GUI 更新导致界面卡死。

耗时计算: 工作线程中 QDateTime::currentMSecsSinceEpoch() 对比 t0 计算已用时间, 每 200ms 通过 QTimer 触发信号更新 UI。

ETA 估算: elapsed / progress * (total - progress) 计算剩余时间。

详细日志 verbose_logger.cpp

类设计:

class VerboseLogger {

    QFile* m_file = nullptr;        // 日志文件句柄

    QTextStream* m_stream = nullptr; // 流式写入

    QString m_logPath;               // 日志文件完整路径

    bool open(const QString& outDir);  // 创建 live2dmtf_verbose_<时间戳>.log

    void log(const QString& msg);      // 每行带毫秒时间戳 [HH:mm:ss.zzz]

    void close();                      // 写入结束标记, 关闭文件

};

日志文件路径: {outputDir}/live2dmtf_verbose_{yyyyMMdd_HHmmss}.log

资源路径管理 resource_paths.h

所有运行时数据文件路径集中在 ResourcePaths 命名空间, 统一从 QCoreApplication::applicationDirPath() + "/data" 派生:

namespace ResourcePaths {

    inline QString dataDir()   { return exeDir() + "/data"; }

    inline QString coreJs()    { return dataDir() + "/live2dcubismcore.js"; }

    inline QString builtJs()   { return dataDir() + "/index-DiW7gcuz.js"; }

    inline QString shaderDir() { return dataDir(); }

}

嵌入组合

资源

脚本

输出

使用方式

内嵌

内嵌

单个 HTML (<10MB)

直接打开

外挂

内嵌

HTML + res/

需本地 HTTP 服务器

内嵌

外挂

HTML + JS 文件

需本地 HTTP 服务器

外挂

外挂

HTML + js/ + res/

需本地 HTTP 服务器

技术参数总览

编译器与工具链

项目

编译器

GCC 13.1.0 (MinGW-w64, x86_64-posix-seh)

C++ 标准

C++17

构建系统

CMake 3.28+ + MinGW Makefiles

Qt 版本

6.11.0 (mingw_64)

Qt 模块

Core, Gui, Widgets

链接库

Qt6::Core, Qt6::Gui, Qt6::Widgets

子系统

Win32

优化级别

Release (-O2 -s)

运行时数据

文件

大小

来源

live2dcubismcore.js

247 KB

Live2D Cubism Core SDK

index-DiW7gcuz.js

244 KB

Vite 打包的 Sample TypeScript 应用

fragshadersrc*.frag

~20 KB

Framework/Shaders/WebGL/

vertshadersrc*.vert

~3 KB

Framework/Shaders/WebGL/

性能指标

指标

EXE 大小 (编译后)

~70 MB

启动时间

<100 ms

内存占用 (空闲)

~30 MB

资源加载(5MB)

<500 ms

Base64 编码 (1MB PNG)

~50 ms

HTML 写入 (~10MB)

~500 ms

总生成时间(5MB模型)

~1.2 S

License

本项目依赖 Live2D Cubism SDK,使用须遵守 [Live2D 开放软件许可协议](https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html)。

软件分发

仅供学习使用,下载的文件请在24小时内删除

源代码:https://file.candywind.com/@s/aG5Vd55i
软件:https://file.candywind.com/@s/cu0h4JSi
Github:https://github.com/lazylizilazy/Live2DMTF