一个 SDK,让 AI 从"只会打字"进化到"会走路、会说话、有表情"的 3D 数字人。这篇文章带你从零看懂它的能力边界,并用一个虚拟展厅案例把它榨干。

为什么写这篇

你有没有这种感觉——现在大部分 AI 应用,交互还停留在"文字进、文字出"的阶段。哪怕加了语音,也只是多了一个 TTS 通道,AI 还是"坐在那儿"不会动。

但如果 AI 能站起来走两步、说到产品时手一挥弹出一张 3D 模型图、你随时能打断它提问——这个体验完全是另一个量级的。

魔珐星云(xingyun3d.com)的具身驱动 SDK 干的就是这件事:基于文本输入,实时生成语音+表情+动作,驱动 3D 数字人。我花了半天通读了它的全部开发文档,然后用一个虚拟汽车展厅导购案例把它的 10 项核心能力全跑了一遍。

这篇文章先讲清楚 SDK 能干什么,再带你看案例里最有技术含量的几个点。


一、这个 SDK 到底能干什么

先说结论,星云 SDK(Web 版,基于 JS + WebGL)提供的能力可以用一句话概括:

给它一段 SSML 文本,它就能让一个 3D 数字人把这段话说出来——口型、表情、肢体动作全部实时同步,还能边走边说、边说边弹多媒体卡片。

拆开来看,核心能力有这些:

1. 实时 3D 渲染 + 口型同步

底层是 WebGL 2.0 做 GPU 加速渲染,端上完成计算。TTS 生成的语音和数字人口型是帧级同步的,不是那种"嘴皮子乱动"的假同步。支持流式分段发送,适配 LLM 的流式输出和长文本场景。

2. 状态行为控制

数字人有 5 种行为状态:

状态 含义
Idle 空闲,静默待机动画
Interactive Idle 交互空闲,表示"我可以被打扰"
Listen 聆听中
Think 思考中(适合接 LLM 的"正在生成"间隙)
Speak 说话中

状态之间可以自由切换,这就够你搭一个完整的对话状态机了。

3. 行走动画

这个很有意思——数字人可以在场景里水平移动。你提前配置好"停靠点"(walk_points),然后在 SSML 里嵌一个 <ue4event type="walk"> 标签,数字人说到那个位置就会走向目标点。边走边说是支持的。

注意:行走属于定制化角色功能,需要角色制作时预置行走动画数据。

4. Widget 组件 + 自定义渲染

数字人播报时,可以在画面上叠加图片、字幕、视频等 UI 组件。关键是——这些组件的触发时机由 SSML 文本中的位置决定,说到哪儿弹哪儿,和语音天然同步。

而且 SDK 提供了 proxyWidget 机制,你可以完全接管渲染,不用它内置的丑字幕,自己画卡片。

5. 客户端打断

用户不想听了?传统方案要通知服务端停止,一来一回有延迟。星云支持端上打断——enableClientInterrupt: true + avatar.interrupt('user'),零延迟停嘴。

6. 网络韧性和 WebGL 自愈

弱网下自动重连(最多 27 次),断线期间播放缓存的 Idle 动画保证画面不中断。GPU 资源被系统回收导致 WebGL 上下文丢失?SDK 会自动重建。这两点对移动端和长时间运行场景很重要。


二、案例:虚拟汽车展厅 AI 导购

看完文档我就在想,什么场景能把上面这些能力全部串起来而且不违和?

答案是:虚拟展厅导购
在这里插入图片描述

场景设计:一个数字人销售员站在展厅入口,用户点击"开始导览"后,数字人走向第一台车,到了之后开始流式讲解,讲解过程中自动弹出 3D 模型旋转、参数卡片、赛道视频等 Widget。用户随时可以打断提问"这台车多少钱"“续航多少”,数字人回答后继续讲解。走完 4 个站点结束。

这个场景用到的能力映射:

行走动画     → 站点间移动
流式语音     → 长讲解分段发送(is_start/is_end)
SSML uievent → 说到对应位置弹图片/3D模型/视频
proxyWidget  → 完全自定义渲染所有多媒体卡片
客户端打断    → 用户随时插话
状态机       → IDLE→WALK→SPEAK→LISTEN→THINK→SPEAK 闭环
动态布局      → 行走时全身、讲解时中景
网络韧性      → 断线自动重连 + 离线 Idle 动画
WebGL恢复    → GPU 回收自动重建
错误码分级    → 6大类40+错误码处理

10 项能力,一个案例全覆盖。

下图是整个案例的分层架构,从上到下分三层:应用层(你写的业务逻辑)、SDK 能力层(星云提供的 10 项核心能力)、SDK 底层(WebGL 渲染 + TTS + 行走引擎 + 通信)。
在这里插入图片描述

界面长这样(三栏布局):

  • 左栏:4 个展厅站点列表,当前站点高亮,行走时有动画指示
  • 中间:数字人 3D 渲染区 + 右上角 Widget 卡片流 + 底部行走轨道指示 + 字幕条
  • 右栏:对话区(可向导购员提问)+ 实时日志面板

三、技术含量最高的几个点

3.1 SSML 里的"事件触发"——语音和多媒体怎么同步的

这是我觉得最优雅的设计。看这段 SSML:

<speak>
  现在我们在
  <uievent>
    <type>show_image</type>
    <data>
      <image>https://example.com/car.jpg</image>
      <title>雷霆 EV · 全景</title>
    </data>
  </uievent>
  雷霆EV的展位前。这台车搭载双电机……
</speak>

数字人说"现在我们在"的时候,图片卡片就弹出来了,然后继续说"雷霆EV的展位前"。

同步的精度不是靠定时器,是靠 SSML 文本中的物理位置<uievent> 标签放在哪个词前面,事件就在哪个词被说出时触发。一条 SSML 可以嵌多个 <uievent>,依次触发。

下图直观展示了这个同步机制:左侧是 SSML 文本流(时间从上到下推移),<uievent> 标记夹在文本段落之间;右侧是 Widget 渲染区,说到 <uievent> 位置时对应卡片弹出。
在这里插入图片描述

支持的内置事件类型:

type 说明
show_image 图片卡片
show_video 视频播放
show_model3d 3D 模型(.glb)
show_text 文本/参数卡片
show_link 链接卡片
bgm_start 背景音乐

你还可以通过 proxyWidget 完全接管渲染逻辑。我在案例里注册了全部 6 种 renderer:

const avatar = new XmovAvatar({
  // ...
  proxyWidget: {
    show_image(data)    { /* 自己画图片卡片 */ },
    show_model3d(data)  { /* 自己画3D模型占位 */ },
    show_video(data)    { /* 自己接 <video> */ },
    show_text(data)     { /* 自己画参数表格 */ },
    show_link(data)     { /* 自己画链接卡 */ },
    bgm_start(data)     { /* 自己放 BGM */ },
    subtitle_on(data)   { /* 自己显示字幕 */ },
    subtitle_off()      { /* 自己隐藏字幕 */ },
  },
});

注册之后,SDK 内置渲染就不再生效了,所有 UI 都归你管。这意味着你可以保持自己产品的设计语言,不被 SDK 的默认样式绑架。

优先级规则:onWidgetEvent > proxyWidget > SDK 内置渲染。如果你同时定义了 onWidgetEvent,它会拦截所有事件,proxyWidget 就不触发了。

3.2 行走动画——SSML 里塞一条"走过去"指令

行走功能的配置和触发分两步:

第一步:构造时配置停靠点(X 坐标):

const avatar = new XmovAvatar({
  config: {
    walk_config: {
      walk_points: { A: 0, B: 213, C: 426, D: 640 },
      init_point: 0,
    },
  },
});

第二步:SSML 里嵌入行走指令:

<speak>
  请跟我来到下一个展位。
  <ue4event>
    <type>walk</type>
    <data><target>B</target></data>
  </ue4event>
  这里是幻影GT运动轿跑。
</speak>

数字人说完"请跟我来到下一个展位",就会走向 B 点,边走边说"这里是幻影GT"。onWalkStateChange 回调会告诉你 'walk_start''walk_end'

踩了两个坑记录一下:

  • changeWalkConfig() 只能在非行走状态调用,行走中调不生效,得等走完再重试
  • enableClientInterrupt = false 时,播报过程中无法执行行走指令——这两个功能有依赖关系

3.3 流式语音——对接 LLM 的关键

speak() 的完整签名是:

avatar.speak(ssml, is_start?, is_end?, extra?)
  • is_start = true:这是本次说话的第一段
  • is_end = true:这是本次说话的最后一段

为什么要分段?因为 TTS 生成有延迟。如果等整段 100 字的回答生成完再发给数字人,用户要干等好几秒。但如果你把它拆成 5 段,每段 20 字,第一段 TTS 完成数字人就能开口,后面的段在说话的同时继续生成——首字延迟从"等全文"降到"等第一段"

这正好匹配 LLM 的流式输出:LLM 一边吐 token,你一边 speak(chunk, is_start, is_end) 往 SDK 里灌。

const chunks = chunkText(answer, 40);  // 每段40字
chunks.forEach((chunk, i) => {
  const isStart = i === 0;
  const isEnd = i === chunks.length - 1;
  avatar.speak(`<speak>${chunk}</speak>`, isStart, isEnd);
});

3.4 客户端打断——零延迟停嘴

传统打断要走一次网络往返:通知服务端"停" → 服务端停止推送音频流 → 客户端停止播放。这个过程有延迟,用户点完打断按钮数字人还会嘟囔半秒。

星云的端上打断直接在客户端切断:

const avatar = new XmovAvatar({
  enableClientInterrupt: true,  // 构造时开启
});

// 用户点打断
const latency = avatar.interrupt('user');
console.log('打断耗时:', latency, 'ms');  // 通常个位数ms

interrupt() 返回执行耗时(毫秒),type 参数用于日志区分来源('user' / 'user_speaking' / 'speak')。

3.5 错误码体系——6 大类 40+ 错误码

SDK 的错误码是分段设计的,通过 onMessage 回调统一上报:

范围 分类 典型码
10001-10004 初始化 容器找不到、WebSocket 连不上、会话创建失败
20001-20010 处理 视频帧提取失败、Worker 初始化失败(SDK 自动容错)
30001-30007 资源 背景图加载失败、身体数据过期
40001-40007 解码 音频/视频/面数据解码失败
50001-50004 网络 断线→重连→恢复→重连耗尽
60001-60007 WebGL 上下文丢失→恢复超时→恢复成功→恢复失败

我在案例里按"致命/警告/通知"三级处理:

function handleSDKError(error) {
  const { code, message } = error;
  if (code >= 10000 && code < 20000) {
    // 初始化错误 — 致命,提示用户
  } else if (code >= 50000 && code < 60000) {
    if (code === 50001) showOfflineTip();      // 断线
    if (code === 50002) hideOfflineTip();      // 恢复
    if (code === 50004) showRefreshDialog();   // 重连耗尽
  } else if (code >= 60000 && code < 70000) {
    if (code === 60001) showGPULoading();      // 上下文丢失
    if (code === 60004) hideGPULoading();     // 恢复成功
    if (code === 60006) showRefreshDialog();   // 恢复失败
  }
}

值得说的是,处理类错误(20000s)和解码类错误(40000s)大部分 SDK 内部会自动容错——跳过坏帧、降级渲染——不需要你中断业务。但建议记日志用于排查。


四、状态机设计

案例里我自己搭了一个 6 态有限状态机来管理数字人行为:

IDLE ──► WALK ──► SPEAK ──► INTERACTIVE_IDLE ──► LISTEN ──► THINK ──► SPEAK
                                                                ▲
                                                                │
                                                          (用户提问)

上行是导览循环(IDLE→WALK→SPEAK→INT_IDLE,绿色虚线回流到 WALK 表示"走向下一站"),下行是问答循环(INT_IDLE→LISTEN→THINK→SPEAK’→INT_IDLE,红色虚线)。两条循环共享 INTERACTIVE_IDLE 枢纽节点。interrupt() 可以在 SPEAK 状态直接打断回到 LISTEN。
在这里插入图片描述

转换前校验合法性,非法转换直接拒绝。为什么不用 SDK 内置的状态?因为 SDK 的 AvatarStatus(online/offline/visible/invisible/close)管的是连接和可见性,而行为状态(idle/listen/think/speak/walk)是应用层语义,需要自己管。

两者的关系是:SDK 状态是底层"在线/离线"层面的,应用状态机是业务层面的,二者正交。


五、上手只需要 5 分钟

文档的快速开始写得挺清楚,核心就 4 步:

Step 1:去 xingyun3d.com 应用中心创建驱动应用,选角色/音色/表演风格,拿到 App ID 和 App Secret。

Step 2:引入 SDK(一个 script 标签,无需构建工具):

<script src="https://media.xingyun3d.com/xingyun3d/general/litesdk/xmovAvatar@latest.js"></script>

Step 3:准备容器 + 初始化:

<div id="avatar-container" style="width: 360px; height: 640px;"></div>
<script>
  const avatar = new XmovAvatar({
    containerId: '#avatar-container',
    appId: 'your-app-id',
    appSecret: 'your-app-secret',
    gatewayServer: 'https://nebula-agent.xingyun3d.com/user/v1/ttsa/session',
    onMessage(error) { console.error(error.code, error.message); },
    onStatusChange(status) { console.log('状态:', status); },
  });

  avatar.init({
    onDownloadProgress(p) { console.log('加载:', p + '%'); },
  }).then(() => {
    setTimeout(() => avatar.speak('<speak>你好!</speak>'), 3000);
  });
</script>

Step 4:页面卸载时销毁(很重要,否则会话泄漏):

window.addEventListener('beforeunload', () => {
  avatar.destroy('page_unload');
});

环境要求:Chrome 94+ / Edge 94+ / Safari 15+ / Firefox 100+,需要 WebGL 2.0 支持。


六、环境支持

  • 浏览器:Chrome 94+ / Edge 94+ / Safari 15+ / Firefox 100+(推荐 Chrome)
  • WebGL:需要 2.0 支持
  • 网络:WebSocket 长连接
  • 芯片:x86 没问题;ARM 架构 RK3588 建议 1080P,RK3566 建议 720P
  • 移动端:除了 Web SDK,还有 Android 原生 SDK

七、哪些场景适合用

聊几个我觉得有价值的落地方向:

1. 虚拟展厅/博物馆导览
数字人边走边讲,到展品前弹出 3D 模型和历史图片。行走 + Widget 同步是这个场景的刚需,目前市面上能做到"边走边说边弹卡片"的方案不多。

2. 电商直播/产品发布会
数字人主播介绍产品时弹出产品图、规格表、购买链接。show_link 事件可以直接引导下单。比真人主播成本低,24 小时不断播。

3. 智能客服/政务大厅
数字人站在虚拟大厅里,用户走近触发交互,提问后流式回答。打断功能在客服场景是刚需——用户不想听完一长段废话。

4. 教育培训
数字人教师边走边讲,配合 3D 模型展示零件结构、实验器材。show_model3d 支持 .glb 格式,可以接 Three.js 生态。

5. 对接 LLM 做对话数字人
这是最有想象力的方向。LLM 流式输出 → speak(chunk, is_start, is_end) 灌进去 → 数字人实时说话 + 口型同步 + 表情动作。把"只会打字的 ChatGPT"变成"会说话会动的人"。


八、我的体感

通读完文档 + 写完案例,说几点真实感受:

做得到位的:

  • SSML 的事件触发机制设计得很优雅,语音和多媒体的同步精度是文本级而非定时器级,可靠性高
  • 双模式(proxyWidget 完全接管 / SDK 内置渲染)给了足够的灵活性
  • 错误码体系完整,6 大类覆盖了初始化到 WebGL 的全链路,对线上排查友好
  • 自动重连 27 次 + WebGL 上下文自愈,移动端和长时间运行场景的韧性到位了
  • 端上打断是真零延迟,体验明显好于服务端打断

需要注意的:

  • 行走是定制化功能,不是所有角色都能走,得角色制作时预置动画数据
  • enableClientInterrupt 和行走指令有依赖关系,关闭打断时播报中无法行走
  • ARM 芯片性能有上限,RK3566 建议 720P,别硬上 1080P
  • SDK 通过 <script> 引入,挂载到 window.XmovAvatar,Vue/React 项目里记得在 mounted/useEffect 里初始化、在 beforeDestroy/cleanup 里销毁

整体评价: 如果你的项目需要"AI 有身体"——不只是文字交互,而是 3D 可视化、有动作、有表情、能在场景里移动——星云 SDK 目前是 Web 端最完整的方案之一。5 分钟跑通基础渲染,半天能做出一个像样的交互场景。


送书福利

所有本地跑通Demo的用户都可以参与送书活动,每人随机赠送一本AI相关技术书籍,全新包邮。
demo接入教程
魔珐星云数字人SDK接入教程

详细规则如下:
每天体验5分钟以上,连续两天调用SDK。

将注册手机号和收货地址填入下方问卷即可。
https://docs.qq.com/form/page/DRUZOaGx6Q2pHYXJV

Logo

一座年轻的奋斗人之城,一个温馨的开发者之家。在这里,代码改变人生,开发创造未来!

更多推荐