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

10 KiB
Raw Permalink Blame History

LAppLive2D — 独立 Live2D 渲染库

本文档是将相关代码投喂给AI所生成的,已经过人工审阅,未发现错误。

概述

lapp_live2d 是对 Live2D Cubism SDK for Native 的封装层。
它将原 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

# 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 为例)

#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 的宏分支中加一条:

#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 注入即可。

切换后端时 LAppModelLAppView 中的 CUBISM_RENDERER_TYPE / CUBISM_OFFSCREEN_TYPE 宏会自动跟随编译宏切换对应的 Cubism SDK 后端类型。


API 参考

LAppDelegate(单例)

// 初始化(必须在 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

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

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 不依赖任何窗口事件系统,触摸由应用层主动调用:

// 对应 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                   ← 本文件