抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

前言

在之前搭建博客的文章里,我写过想把微光放进网页,后面接了一句“待更新”。现在终于可以更新了。

最开始我想用 Inochi2D 做看板娘。不过,我的 2D 模型还没做完,3D 模型倒是已经能在 VRChat 和 Warudo 里使用了。既然如此,为什么不直接把现成的模型用起来呢?于是便有了 StarLite。

如果你正在电脑上读这篇文章,应该已经看到旁边的微光了。他会看向光标,偶尔活动一下,戳他也会有回应。同一套模型组件还接到了 DSH(DeepSeek Harness),可以根据 Agent 的工作状态播放动作和显示气泡。

这篇文章主要记录我想实现什么、各部分如何工作,以及一些静态截图里看不出来的小问题。具体安装和配置会继续更新,请以对应版本的仓库文档为准。

我想要一个什么样的桌宠

现在已经有一些功能十分全面的虚拟角色项目,可以语音对话、调用大模型,甚至观察屏幕和操作游戏。我最初想做的事情要简单得多:让自己的角色在博客或桌面的一角待着,在适当的时候回应一下。

因此,StarLite 的核心可以概括为:把模型已有的动作,映射到一些交互和应用事件上。

比如点一下角色,挥手并打个招呼;Agent 工作时,切换到工作动作;需要我回答时,在气泡里显示选项。平时则安静地站着,有一点呼吸和眨眼。微光只是默认示例,更重要的是其他人也能放进自己的角色。

我希望它轻量、容易上手,而且不要占据太多注意力。气泡本身不调用大模型,博客中的句子也是提前写好的。DSH 里的实时内容来自 DSH,桌宠负责呈现。要让角色说什么、做什么,不需要再养一套推理服务。

当然,“功能简单”和“实现简单”是两回事……

从模型到可以交互的角色

模型、动作和程序是不同的东西

模型本身主要保存几何、贴图、骨骼和表情等信息。有了模型,并不代表程序自动知道怎样让它挥手。

这里采用 VRM,是因为它在 glTF 的基础上约定了人形骨骼、表情、视线和弹簧骨骼等信息。程序可以按“头部”“左上臂”这样的标准映射操作角色,不必为每个模型重新猜一遍骨骼名称。浏览器中的加载和渲染使用 Three.js 和 three-vrm。

用户导入标准人形 VRM 后,不必先制作一整套动作。StarLite 自带一个基础 Idle,提供放松站姿和轻呼吸,再配合眨眼、限角光标跟随。对应效果仍然需要模型正确配置:比如眨眼需要 VRM 的 Blink 表达绑定,眼球跟随需要合适的视线设置。给一个形态键起名叫“眨眼”,不等于已经完成了这些配置。

挥手、击掌之类的具体动作,则需要模型作者自己提供。动作可以内嵌在模型文件中,也可以使用外部 VRMA。微光的那套动作不会自动套在别人身上;不同角色的比例、手指和特殊肢体,仍然需要实际检查。

动作和句子需要一起考虑

如果分别随机选一个动作和一句话,就可能出现身体摔倒、气泡里却在说“做得好”的情况。因此,句库可以按动作绑定:先决定做什么,再从对应句子里选一句。

动作之间也需要衔接。循环、单次动作和静态姿势,处理方式并不一样;把一个静态姿势叫作“待机动画”,并不会让它自己呼吸。运行时可以处理有限的姿态过渡,但不能代替动作制作。模型穿模、权重不合适、手指没有正确映射这些问题,最后还是要回到模型或动作里修改。

项目是怎样组织的

现在主要有两个仓库:

  • StarLite:公共核心、模型后端和 Hexo 插件在同一个工作区,按包分开。
  • StarLite for DSH:DSH 事件接入和 Windows 桌宠,使用前面的模型运行时。

公共核心负责气泡、句库调度和播放契约;VRM 后端负责渲染、骨骼、表情、物理和动作;Hexo 插件负责配置、资源生成和页面生命周期。早期的 Inochi2D 实现也保留在独立后端里,目前主要完善的是 VRM。

所以,气泡不是 Hexo 独有的功能,DSH 也不需要再写一套模型播放器。修改句子不必碰渲染代码,把模型接到另一个应用时也不需要把博客一起带过去。

一些真正用起来才会发现的问题

切换文章,不应该换一只角色

Volantis 使用 PJAX 切换页面时,不会重新加载整个网页。如果每次切页都重新创建模型,既慢,也可能留下重复的画布和事件监听;但如果只保留角色而不更新上下文,他又会继续说上一篇文章的句子。

现在的做法是保留模型实例,只更新页面对应的句库。首次入场按标签页去重,不会每翻一篇文章就重新挥一次手。加载失败时先隐藏,再自动重试,成功后恢复;失败的角色也不应该挡住正文和链接。

耳朵为什么会突然飞起来

动作、视线和弹簧物理都可能修改骨骼。如果同一根骨骼在不同阶段被重复更新,或者上一帧的物理结果被当成下一帧的基准,就会出现很奇怪的抖动。微光的耳朵确实给这个项目找了不少事情做……

这里需要明确谁负责哪一部分、按什么顺序更新。类似地,正在控制头部的动作和光标跟随,也不能无条件地同时覆盖同一个旋转。看到耳朵异常时,一味调整弹簧力度并不能解决更新顺序的问题。

页面掉帧,也不一定是模型的问题

有一轮测试中,小窗口流畅,最大化之后却持续低帧。最后发现博客的全屏星空背景也在消耗渲染时间。角色和背景共用浏览器的资源,只盯着模型优化,很容易找错地方。

静态银河纹理可以预先生成,再做移动、旋转;流星和闪烁星星则单独更新。模型本身还要控制绘制分辨率,在页面隐藏时暂停动画和物理。角色看起来只占屏幕的一小块,不代表贴图、骨骼和浏览器合成就没有开销,因此目前我还不想给出一个脱离设备和模型的“性能很好”的结论。

给每篇文章写几句话

这次新加了页面专属句库。文章正文和属于这篇文章的句子,可以放在同一份 Markdown 里维护。在开头的 front matter 中直接写:

1
2
3
4
5
6
---
title: 一篇关于模型的文章
starlite:
- "嗨!这篇讲的是我怎么来到网页里的~"
- "看完之后,也可以试着把自己的角色放进来。"
---

默认点击和偶发互动都可以触发,按顺序读,读完再从第一句开始;它们使用全站入场动画作为配套动作。这里沿用的是动作,不会因为切换文章重新触发一次自动入场。

如果想单独指定动作或规则,可以改成对象形式:

1
2
3
4
5
6
7
8
starlite:
lines:
- "要击个掌吗?"
- "耶!"
animation: HighFive
triggers: [click]
mode: merge
order: random

这表示只在点击时触发,页面两句使用 HighFive,和全站点击句库合并后随机选择;全站候选仍保留各自的动作与句子绑定。HighFive 是微光的动作名,换成自己的模型时也要换成实际存在的名称。默认值还可以统一写在博客 _config.yml 的 starlite.pageDialogue 中,不必每篇重复配置。

这篇文章本身就用了最简写法,可以戳旁边的微光试试。切到其他文章后,他会恢复全站句库。

使用和后续

Hexo 插件的公开测试版安装入口是:

1
npm install hexo-starlite@alpha

不过,本文页面使用的是新的候选构建,页面专属句库和默认自动获取微光的流程尚未发布到 npm。公开版本的具体安装、模型导入方式请查看对应版本的 README;本轮验证通过后再同步发布。

程序采用 BSD-2-Clause 许可,微光模型采用单独的模型许可,两者不是一回事。将角色放进网页,意味着浏览器必须能下载运行所需的资源;只能在授权范围内托管,不能把“文件能下载到”理解为可以提取再利用。使用自己的模型,也要确认模型、贴图和动作的公开托管权限。

目前项目仍在 alpha 阶段,主要验收环境是桌面 Chrome 和 Edge,小屏默认不加载模型。我更想继续把导入、性能和交互这些基础部分做好,再根据实际使用反馈补功能。

从三视图、建模,到 VRChat、Warudo,再到现在的网页和桌面,同一个角色又多了一种呈现方式。对我来说,这也是继续完善他的一个理由。

如果你也有自己的角色,可以来试试。遇到问题或者有什么想法,欢迎在评论或仓库里告诉我。

给小狼留言

点击播放键加载歌单