Files
Yosuga/3rdparty/Live2D/Src/LAppLive2D/README.md
T
Misaki f7be85fbf6 1. 完全解耦了Live2D的LApp部分,使其可以接入任何GUI库
2. 初步尝试合并嵌入式Linux平台支持的相关代码,预计在下一次提交完整支持。
2026-06-24 01:05:26 +08:00

284 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# LAppLive2D — 独立 Live2D 渲染库
本文档是将相关代码投喂给AI所生成的,已经过人工审阅,未发现错误。
## 概述
`lapp_live2d` 是对 [Live2D Cubism SDK for Native](https://www.live2d.com/download/cubism-sdk/) 的封装层。
它将原 SDK 示例代码中的 OpenGL 硬编码抽离为抽象接口,使库本身**不依赖任何窗口框架**(Qt / SDL / GLFW / 自研引擎均可接入)。
**核心设计原则**
- 零 Qt / GLFW / SDL 依赖 — 仅依赖 C++ 标准库 + OpenGL 头文件 + Cubism SDK
- 渲染后端通过 `IRenderContext` / `ISpriteRenderer` 抽象接口注入
- 窗口尺寸变更通过 `std::function` 回调通知,不持有窗口指针
- 开发者可自由选择窗口框架,编写自己的 `GLCore`
---
## 依赖
| 依赖 | 说明 |
|------|------|
| **Live2D Cubism Framework** | `libFramework.a`(静态库,Live2D 官方) |
| **Live2D Cubism Core** | `libLive2DCubismCore.a`(静态库,Live2D 官方) |
| **OpenGL / OpenGL ES 头文件** | 桌面: `GLEW` + `GLFW`头文件;嵌入式: `EGL` + `GLES2` |
| **C++ 标准库** | `std::function`, `cstdint` 等 |
不依赖任何 Qt / SDL / GLFW 链接库。
---
## 架构
```
┌───────────────────────────────────────────────────────────┐
│ LAppDelegate (单例,应用入口) │
│ Initialize(w, h) — 仅需传入窗口尺寸 │
│ resize(w, h) — 窗口变更通知 │
│ update() — 每帧渲染 │
│ SetRenderContext() — 注入渲染后端 │
│ SetWindowResizeCallback() — 窗口尺寸变更回调 │
│ NotifyWindowResize() — LApp内部通知应用层调整窗口 │
│ GetWindowWidth/Height() — 获取当前存储的窗口尺寸 │
└──────────────┬────────────────┬────────────────────────────┘
│ 持有 │ 持有
┌──────────▼──────┐ ┌─────▼──────────────────────────┐
│ LAppView │ │ LAppLive2DManager │
│ 渲染管理 │ │ 模型生命周期(加载/切换/更新) │
│ - 触摸事件 │ │ - OnTap / OnDrag │
│ - 坐标变换 │ │ - ModelSizeChange → 回调通知 │
│ - Sprite绘制 │ │ │
└────────┬─────────┘ └─────┬──────────────────────────┘
│ │ 管理
┌────────▼───────────────────▼──────┐
│ LAppModel : CubismUserModel │
│ 单个Live2D模型实例 │
│ - 加载(.moc3 / .model3.json) │
│ - 动画、物理、口型同步 │
│ - 命中检测 (HitTest) │
│ - 渲染 (Draw → CubismRenderer) │
└───────────────────────────────────┘
```
### 抽象接口
```
IRenderContext ISpriteRenderer
│ │
├─ Clear(r,g,b,a) ├─ SetColor(r,g,b,a)
├─ ClearDepth(d) ├─ SetWindowSize(w,h)
├─ SetViewport(x,y,w,h) ├─ RenderImmidiate(texId, uv)
├─ CreateShaderProgram() ├─ IsHit(px, py)
├─ GetShaderProgram() ├─ ResetRect(x,y,w,h)
├─ InitializeGLState() └─ GetTextureId()
└── GLRenderContext (OpenGL实现)
- CompileShader() 内部编译 GLSL
- Clear → glClear / glClearColor
- SetViewport → glViewport
```
---
## 接入方式(CMake
```cmake
# 1. 在父 CMakeLists.txt 中配置 Framework 和 CoreIMPORTED
add_library(Framework STATIC IMPORTED GLOBAL)
set_target_properties(Framework PROPERTIES IMPORTED_LOCATION "/path/to/libFramework.a")
add_library(Live2DCubismCore STATIC IMPORTED GLOBAL)
set_target_properties(Live2DCubismCore PROPERTIES IMPORTED_LOCATION "/path/to/libLive2DCubismCore.a")
# 2. 导入 LAppLive2D 子项目
add_subdirectory(3rdparty/Live2D/Src/LAppLive2D)
# 3. 链接到你的可执行目标
target_link_libraries(your_app PRIVATE lapp_live2d)
```
**注意**LAppLive2D 内部引用 `<GL/glew.h>`(桌面)或 `<GLES2/gl2.h>`(嵌入式),需确保对应的头文件路径可用。参见父项目的 `CMakeLists.txt` 中如何为 `lapp_live2d` 补充平台 GL 头文件路径。
---
## 编写自定义窗口(以 Qt 为例)
```cpp
#include <QOpenGLWidget>
#include "LAppDelegate.hpp"
#include "GLRenderContext.hpp"
class MyGLCore final : public QOpenGLWidget
{
void initializeGL() override
{
// 1. 注入 IRenderContextOpenGL 实现)
LAppDelegate::GetInstance()->SetRenderContext(new GLRenderContext());
// 2. 注册窗口大小变更回调(模型加载时 LApp 通知你调整窗口)
LAppDelegate::GetInstance()->SetWindowResizeCallback([this](int w, int h) {
setFixedSize(w, h);
});
// 3. 初始化 LApp(传入当前窗口尺寸)
LAppDelegate::GetInstance()->Initialize(width(), height());
}
void resizeGL(int w, int h) override
{
LAppDelegate::GetInstance()->resize(w, h);
}
void paintGL() override
{
LAppDelegate::GetInstance()->update();
}
void mousePressEvent(QMouseEvent *ev) override {
LAppDelegate::GetInstance()->GetView()->OnTouchesBegan(ev->position().x(), ev->position().y());
}
void mouseMoveEvent(QMouseEvent *ev) override {
LAppDelegate::GetInstance()->GetView()->OnTouchesMoved(ev->position().x(), ev->position().y());
}
void mouseReleaseEvent(QMouseEvent *ev) override {
LAppDelegate::GetInstance()->GetView()->OnTouchesEnded(ev->position().x(), ev->position().y());
}
};
```
---
## 切换渲染后端
### 当前支持
| 后端 | 宏定义 | 说明 |
|------|--------|------|
| OpenGL ES2 | `RENDER_BACKEND_GLES2` | 嵌入式(RK3566 等) |
| OpenGL | `RENDER_BACKEND_OPENGL` | 桌面 Windows / Linux |
### 添加新后端
**第 1 步** — 在 `LAppOpenGL.hpp` 的宏分支中加一条:
```cpp
#elif defined(RENDER_BACKEND_VULKAN)
#define RENDERER_BACKEND_TAG Vulkan
```
**第 2 步** — 实现 `VulkanRenderContext`(继承 `IRenderContext`),并在其中实现 `Clear` / `SetViewport` / `CreateShaderProgram` 等方法。
**第 3 步** — CMake 中 `add_definitions(-DRENDER_BACKEND_VULKAN)`
**第 4 步** — 在你的窗口 `initializeGL()` 等价函数中创建 `VulkanRenderContext` 注入即可。
切换后端时 `LAppModel``LAppView` 中的 `CUBISM_RENDERER_TYPE` / `CUBISM_OFFSCREEN_TYPE` 宏会自动跟随编译宏切换对应的 Cubism SDK 后端类型。
---
## API 参考
### LAppDelegate(单例)
```cpp
// 初始化(必须在 OpenGL 上下文就绪后调用)
bool Initialize(int windowWidth, int windowHeight);
// 每帧调用
void update();
// 窗口尺寸变更
void resize(int width, int height);
// 注入渲染后端
void SetRenderContext(IRenderContext* ctx);
IRenderContext* GetRenderContext() const;
// 窗口尺寸回调(模型加载时LApp通知应用层调整窗口)
using WindowResizeFunc = std::function<void(int width, int height)>;
void SetWindowResizeCallback(WindowResizeFunc cb);
// 获取当前存储的窗口尺寸
int GetWindowWidth() const;
int GetWindowHeight() const;
// 获取 View / TextureManager
LAppView* GetView();
LAppTextureManager* GetTextureManager();
```
### IRenderContext
```cpp
class IRenderContext {
public:
virtual ~IRenderContext() = default;
virtual void Clear(float r, float g, float b, float a) = 0;
virtual void ClearDepth(float depth) = 0;
virtual void SetViewport(int x, int y, int w, int h) = 0;
virtual uintptr_t CreateShaderProgram() = 0;
virtual uintptr_t GetShaderProgram() const = 0;
virtual void InitializeGLState() = 0;
};
```
### ISpriteRenderer
```cpp
class ISpriteRenderer {
public:
virtual ~ISpriteRenderer() = default;
virtual void SetColor(float r, float g, float b, float a) = 0;
virtual void SetWindowSize(int w, int h) = 0;
virtual void RenderImmidiate(uintptr_t textureId, const float uvVertex[8]) const = 0;
virtual bool IsHit(float px, float py) const = 0;
virtual void ResetRect(float x, float y, float w, float h) = 0;
virtual uintptr_t GetTextureId() const = 0;
};
```
---
## 触摸事件映射
LApp 不依赖任何窗口事件系统,触摸由应用层主动调用:
```cpp
// 对应 QMouseEvent / SDL_MouseButtonEvent / GLFW 回调
LAppDelegate::GetInstance()->GetView()->OnTouchesBegan(x, y); // 按下
LAppDelegate::GetInstance()->GetView()->OnTouchesMoved(x, y); // 移动
LAppDelegate::GetInstance()->GetView()->OnTouchesEnded(x, y); // 释放
// 坐标需为窗口内像素坐标,原点在左上角
```
---
## 项目结构
```
LAppLive2D/
├── Inc/
│ ├── LAppDelegate.hpp ← 应用入口(单例)
│ ├── LAppView.hpp ← 渲染视图管理
│ ├── LAppModel.hpp ← 模型实例
│ ├── LAppSprite.hpp ← Sprite 绘制(继承 ISpriteRenderer
│ ├── LAppLive2DManager.hpp ← 模型集管理
│ ├── LAppTextureManager.hpp ← 纹理管理
│ ├── LAppPal.hpp ← 平台抽象(文件IO、时间)
│ ├── LAppAllocator.hpp ← 内存分配器
│ ├── LAppDefine.hpp ← 配置常量
│ ├── LAppWavFileHandler.hpp ← WAV文件解析
│ ├── TouchManager.hpp ← 触摸状态管理
│ ├── LAppOpenGL.hpp ← OpenGL 头文件 + 后端宏
│ │
│ ├── IRenderContext.hpp ← 渲染上下文抽象接口 ★
│ ├── ISpriteRenderer.hpp ← Sprite渲染抽象接口 ★
│ └── GLRenderContext.hpp ← OpenGL IRenderContext 实现 ★
├── Src/
│ ├── *.cpp ← 各模块实现
│ └── ...
├── CMakeLists.txt ← 子项目构建脚本
└── README.md ← 本文件
```