Files
product/AVATAR_3D_REFACTOR.md
T
2026-03-27 17:10:41 +08:00

5.8 KiB
Raw Blame History

3D 数字人驱动重构方案

1. 目标变更

原链路:

ASR -> LLM -> TTS -> MuseTalk/图片或视频驱动 -> WebRTC 视频轨 -> 前端播放

新链路:

ASR -> LLM -> TTS -> 音频特征/viseme/blendshape 控制数据 -> 前端 3D 渲染引擎 -> 浏览器展示

核心变化:

  • 后端不再负责生成数字人视频帧
  • 后端保留实时语音、时序仲裁、字幕、音频下行
  • 后端新增 3D 控制流输出能力
  • 3D 模型渲染放到前端或独立渲染端

2. 为什么要这样改

MuseTalk 这类方案适合“2D 说话视频合成”,但不适合 3D 模型系统:

  • 输出形态是视频帧,不是骨骼或 blendshape 控制数据
  • 一旦换 3D 模型,原本的视频生成结果无法直接复用
  • 每轮都做视频推理,延迟、GPU 占用、链路复杂度都偏高
  • 3D 系统通常需要的是时间序列控制参数,而不是渲染后的像素

因此,3D 化的正确边界是:

  • 后端负责“说什么、怎么说、何时说”
  • 渲染端负责“模型如何动、如何显示”

3. 本次代码重构内容

当前代码已调整为:

  • WebRTC 下行仅保留音频轨
  • 新增 /ws/animation WebSocket,下发 3D 动画控制数据
  • services/avatar.py 改为音频到控制参数的驱动服务
  • /avatar/schema 暴露控制协议说明
  • 前端采用 Three.js + GLTFLoader + VRM loader,直接在浏览器渲染 3D 模型
  • 支持加载本地 VRM/GLB 文件,也支持加载 URL 形式的 VRM/GLB/GLTF
  • 若未加载模型,页面会回退到一个调试用的 3D 头像,便于联调口型和头部姿态

当前控制协议字段包括:

  • jawOpen
  • mouthClose
  • mouthFunnel
  • mouthPucker
  • viseme_aa
  • viseme_ee
  • viseme_oh
  • headYaw
  • headPitch
  • headRoll

输出包结构示例:

{
  "type": "animation_chunk",
  "driver": "arkit-blendshape-v1",
  "mode": "blendshape_stream",
  "schema": "arkit",
  "chunk_index": 0,
  "total_chunks": 3,
  "sample_rate": 24000,
  "fps": 25,
  "text": "你好,欢迎使用系统。",
  "frame_count": 18,
  "duration_ms": 720,
  "frames": [
    {
      "seq": 102,
      "time_ms": 0,
      "speaking": true,
      "controls": {
        "jawOpen": 0.41,
        "mouthClose": 0.66,
        "mouthFunnel": 0.27,
        "mouthPucker": 0.19,
        "viseme_aa": 0.41,
        "viseme_ee": 0.24,
        "viseme_oh": 0.31,
        "headYaw": 0.004,
        "headPitch": 0.009,
        "headRoll": 0.001
      }
    }
  ]
}

4. 当前实现的定位

当前实现是“联调级骨架”,作用是:

  • 先把系统边界改对
  • 让前后端可以并行开发
  • 让 3D 引擎接入方尽快有稳定协议可以消费

它不是最终口型算法,原因很明确:

  • 目前控制值来自音频能量和频谱特征
  • 没有做 phoneme 强制对齐
  • 没有做语言相关 viseme 映射
  • 没有结合 3D 模型自身 rig 约束做校准

所以这版适合:

  • 打通链路
  • 调接口
  • 验证时序
  • 校验驱动字段

不适合直接作为最终高保真口型方案。

5. 推荐的生产化方案

方案 A:前端 Three.js / React Three Fiber

适合:

  • 浏览器直接渲染 WebGL 角色
  • glTF/VRM 模型
  • ARKit blendshape 或自定义 morph target

建议做法:

  • 前端加载 3D 模型
  • 建立 controlsKey -> morphTargetInfluence 映射表
  • /ws/animation 按时间戳缓冲一小段再播放
  • 音频继续使用 WebRTC 远端音频轨
  • 前端用远端音频播放时间作为动画时钟基准

当前仓库已经按这个方向落地了第一版:

  • 静态页面通过 import map 从 CDN 引入 threeGLTFLoader@pixiv/three-vrm
  • VRM 优先使用 expressionManager 写入 aa/ee/oh/ou
  • 普通 glTF 则尝试把控制字段映射到 morph target
  • 头部姿态优先驱动 head/neck 骨骼,找不到时回退调试头像

方案 BUnity/Unreal 渲染端

适合:

  • 更复杂的材质、灯光、骨骼、表情系统
  • 要求更强的 3D 表现力

建议做法:

  • 浏览器只做 UI 和音频交互
  • Unity/Unreal 作为单独渲染客户端消费 /ws/animation
  • 浏览器与渲染端通过流媒体或共享画面集成

方案 C:专用音频驱动服务

适合:

  • 追求更准的嘴型
  • 后端可以接受增加一个模型服务

可以考虑接入:

  • phoneme 对齐模型
  • viseme 预测模型
  • NVIDIA Audio2Face 类服务
  • 自研 audio -> blendshape 网络

推荐协议保持不变:

  • 外部模型服务可以替换 services/avatar.py
  • 对外仍输出统一 frames[].controls

6. 推荐的下一步实施顺序

  1. 先确定 3D 模型和渲染端技术栈
  2. 固定一份 blendshape 映射表
  3. 把当前前端调试面板替换成真实 3D 场景
  4. 增加时间同步与缓冲策略
  5. 再替换掉启发式控制算法,接入更强的 viseme 模型

7. 接口约束建议

为了后续可替换,建议保持这些约束不变:

  • 下行动画通道继续使用 /ws/animation
  • 每条消息包含 frame_countfpsframes
  • 每帧保留 seqtime_ms
  • controls 使用稳定字段名,不轻易改动
  • 新增字段时只追加,不破坏已有字段

8. 风险与注意事项

  • 真实 3D 模型的 blendshape 命名不一定和当前字段一致,需要做一次映射层
  • 如果模型是骨骼嘴型而不是 morph target,需要把 controls 转成骨骼驱动参数
  • 浏览器渲染时要注意音频时钟和动画时钟一致,否则会出现“听起来对,但嘴型慢半拍”
  • 如果后续改成 phoneme/viseme 对齐,建议在包里增加 phoneme 片段和置信度字段

9. 结论

这次重构的重点不是“把 2D 数字人继续硬改下去”,而是把系统边界调整到适合 3D 模型的形态。

现在的系统已经从“视频合成型数字人”切到了“控制流型数字人”,后续无论接 Three.js、Unity、Unreal,还是专门的 Audio2Face/viseme 服务,都可以在这个边界上继续演进。