Skip to content

feat: VRM 3D Avatar System #18

@gxxk-dev

Description

@gxxk-dev

feat: VRM 3D Avatar System

Summary

为 Study with Miku 添加 VRM 3D 虚拟角色系统。用户可导入自己的 VRM 模型(VRoid Studio / VRoid Hub / Booth.pm),角色作为全屏背景渲染,替代当前的视频背景模式。角色根据番茄钟状态变化表情和姿态。

Motivation

利用 VRM 生态让用户自带模型,零美术成本。3D 角色响应专注状态(工作时专注、休息时微笑、空闲时打瞌睡),比静态视频循环更有陪伴感。


核心设计

背景模式切换

视频和 Avatar 是两种互斥的全屏背景,用户在设置中切换:

Image

canvas 与当前 <video> 共用相同的 CSS 定位和 z-index,overlay 和所有 UI 照常叠加在上方。切换视频按钮在 Avatar 模式下隐藏。

3D 场景构成

角色身处一个可自定义的 3D 场景中,由低面数 GLB 模型搭建:

Image

  • 场景预设:内置 3 套场景——书房(桌椅、书架、台灯、窗户)、咖啡厅(圆桌、咖啡杯、植物、暖光)、卧室(床、月光窗、小夜灯)
  • 自定义场景:用户可导入自己的 .glb 场景文件
  • 相机预设:半身特写 / 全身 / 桌面视角(3 种,设置中选择)
  • 氛围灯光:白天(暖白光)/ 黄昏(橙色调)/ 夜晚(冷紫调),影响整体光照色温
  • 场景物件:均为低面数模型,保证性能;通过 OPFS 存储自定义场景文件

角色放置与防穿模

场景通过锚点系统定义角色的位置、朝向和姿态。GLB 场景文件中内嵌一个名为 CharacterAnchor 的空物体(Blender Empty),携带以下元数据:

锚点属性 说明 示例
position 角色脚底/臀部世界坐标 (0, 0.45, -0.5)
rotation 角色朝向 (0, 180°, 0) 面向相机
pose 姿态预设名 sitting-desk / sitting-casual / standing
seatHeight 座面高度(用于臀部 Y 对齐) 0.45
deskHeight 桌面高度(用于手臂放置) 0.75
scale 角色缩放建议值 1.0

姿态预设定义各关节旋转(VRM 骨骼标准化,所有模型通用):

预设 关键骨骼 适用场景
sitting-desk 髋/膝弯曲 90°,手臂前伸搁桌面,上身微前倾 书房
sitting-casual 髋/膝弯曲,手放腿上,身体靠后 咖啡厅、卧室
standing 自然站姿,手臂下垂 通用

防穿模策略

  1. 参数化对齐——根据 VRM 模型实际骨骼长度 + 锚点的 seatHeight / deskHeight 计算偏移,让臀部落在座面、手臂搁在桌面
  2. 容差建模——场景物件建模时留缝隙(桌椅稍薄),比精确贴合更不易穿模
  3. 用户微调——设置中提供 Y 轴偏移滑块,应对个别模型比例特殊的情况
  4. 自定义场景 fallback——无 CharacterAnchor 的 GLB 文件默认居中站姿,用户可手动调整

角色状态响应

角色根据番茄钟状态自动切换表情姿态基底由场景锚点决定(坐姿/站姿不变),在此基础上叠加上半身动作变化,过渡动画 ~500ms ease-out:

番茄钟状态 角色表情 叠加动作(上半身) 常驻动画
空闲 放松、微笑 轻微歪头,手臂自然 呼吸 + 眨眼
专注中 专注、严肃 头微前倾,姿态端正 呼吸 + 眨眼(频率降低)
休息中 开心、放松 歪头,伸懒腰 呼吸 + 眨眼
暂停 困倦 头微低,趴桌(坐姿时) 呼吸 + 慢速眨眼

常驻动画(呼吸 + 随机眨眼)在所有状态下持续运行,SpringBone 提供头发/衣服物理摆动。


设置界面设计

新增 Avatar 设置 Tab

在 SettingsSidebar 中 "媒体" 和 "账号" 之间新增 "Avatar" tab,图标 lucide:person-standing

Tab 内容布局

Image

模型导入流程

点击"导入 VRM"卡片 → 文件选择器(accept=.vrm)→ 校验扩展名 + 大小上限 100MB → 进度条 → 存入 OPFS → 列表新增卡片 → 自动设为活跃并渲染。

模型卡片交互

每个模型卡片显示模型名称(VRM 元数据或文件名)、添加日期、活跃标记。点击切换活跃,长按/右键删除确认。


技术架构

新增文件

src/
  avatar/
    constants.js          ← 枚举 (AvatarState) 和配置常量
    VRMRenderer.js        ← Three.js + VRM 渲染引擎
    SceneManager.js       ← 场景预设加载 + 自定义场景管理
    animations.js         ← 状态表情/姿态预设 + 过渡插值
    performanceMonitor.js ← FPS 监控,自动降级画质
  services/
    vrmStorage.js         ← OPFS 存储 VRM + GLB 文件
  composables/
    useAvatar.js          ← 单例 Composable (参照 usePlayer.js)
  components/
    AvatarCanvas.vue      ← 全屏 canvas 背景组件
    settings/tabs/
      TabAvatar.vue       ← Avatar 设置页

需修改的文件

  • src/App.vue — 条件渲染 <video><AvatarCanvas>(懒加载)
  • src/config/constants.js — 新增 STORAGE_KEYS 和 DATA_TYPES
  • src/components/SettingsModal.vue — 注册 TabAvatar
  • src/components/settings/SettingsSidebar.vue — 添加导航项
  • src/composables/useDataSync.js — 同步 avatar 偏好
  • package.json — 添加 three + @pixiv/three-vrm
  • vite.config.js — manualChunks 分包 + PWA 缓存规则

新增依赖

bun add three @pixiv/three-vrm

VRM 文件存储

OPFS 存储(参照现有音频文件存储模式),元数据存 localStorage:

OPFS: /vrm-models/1707123456_abc123.vrm    ← 角色模型
OPFS: /scene-models/1707123456_abc123.glb   ← 自定义场景(可选)
localStorage: swm_avatar_models             ← [{ id, name, fileName, addedAt }]
localStorage: swm_avatar_settings           ← { scenePreset, cameraPreset, ambiance }

性能策略

设备能力 画质 策略
高端 HIGH 全分辨率 + SpringBone + 阴影
中端 MEDIUM pixelRatio=1 + SpringBone
低端 LOW pixelRatio=1 + 跳过 SpringBone + 简化灯光

启动后 5 秒自动检测 FPS,低于 30 自动降级。

云同步

仅同步偏好设置(不含 VRM/GLB 文件):

{ enabled, activeModelName, scenePreset, cameraPreset, ambiance, qualityLevel }

风险

风险 应对
Three.js 包体积 ~600KB gzipped defineAsyncComponent 懒加载 + 分包
VRM/GLB 文件大(10-100MB) OPFS 存储 + 文件大小校验
内置场景模型的托管 预设场景 GLB 打包到 public/ 或按需从 CDN 加载
移动端性能 自动降级画质
内存泄漏 显式 dispose 所有 Three.js 资源
角色穿模 锚点参数化对齐 + 容差建模 + 用户 Y 轴微调
自定义场景无锚点 fallback 居中站姿,用户手动调整
OPFS 不支持 检测后隐藏 Avatar 功能

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions