Appearance
虚拟形象 Unity → Three.js 迁移
主导 B 站用户虚拟形象从 Unity WebGL 到 Three.js 的技术选型与迁移,机型覆盖率从 30% 拉到 93%,解决 WebGL 在移动端的性能天花板。
机型覆盖率30% → 93%
低端机帧率30fps
启动速度10s+ → <3s
背景
B 站用户虚拟形象 1.0 用 Unity WebGL 方案。问题在移动端集中爆发:编译体积大、启动 10 秒以上、内存占用高,机型覆盖率只有 30%——七成用户根本打不开自己的虚拟形象。2.0 的目标是覆盖更多低端机型,Unity WebGL 撞到了天花板。
问题
- 覆盖率是生死线:30% 意味着功能对大多数用户不存在,再优化细节都没意义。
- 启动慢:10s+ 的冷启动,用户等不到加载完就退了。
- 包体积大:10-20MB+ 的 WebGL 构建产物,移动端流量和加载时间都扛不住。
方案
技术选型
| 维度 | Unity WebGL | Three.js |
|---|---|---|
| 渲染效果 | ⭐⭐⭐⭐ | ⭐⭐⭐⭐(可塑性高) |
| 机型覆盖率 | 30% | 目标 90%+ |
| 启动速度 | 慢(10s+) | 快(< 3s) |
| 包体积 | 大(10-20MB+) | 小(< 2MB JS) |
| 开发效率 | 中(导出调试慢) | 高(HMR) |
| 生态 | 3D 游戏 | Web 原生 |
最终选 Three.js:渲染效果可塑性高,且 Web 原生——没有 Unity 那层运行时,启动和体积天然占优。
迁移路径
mermaid
flowchart TD
A["Unity 资产与逻辑"] --> B["模型转换<br>GLB/GLTF 导出"]
A --> C["渲染对齐<br>Shader / 光照 / 贴图"]
A --> D["面捕移植<br>解算器 C# → JS"]
B --> E["Three.js GLTFLoader"]
C --> F["MeshStandardMaterial<br>+ 手写卡通 Shader"]
D --> G["MorphTarget 驱动"]
E --> H["Three.js 虚拟形象 2.0"]
F --> H
G --> H原则:迁移不是重写。面捕核心算法直接平移,资产转换做成工具自动化,只有平台强相关的部分(Shader、加载、驱动接口)重写。
关键实现
1. 模型转换
- Unity 模型 → GLB/GLTF 导出 → Three.js
GLTFLoader加载; - Unity Animator → Three.js
AnimationMixer,BlendShape →MorphTarget; - 骨骼动画映射验证:抽检 20 个动画确保无变形。
模型侧的另一个配套工作是 Unity 运行时把 VRM 模型文件里的动画数据转成 AnimationClip(详见 GLB/GLTF/VRM 动画转 AnimationClip),让动画资产能在管线里标准化流转。
2. 渲染效果对齐
- Unity Standard Shader →
MeshStandardMaterial,PBR 参数逐项调优对齐; - Unity Toon Shader → 手写 Three.js 卡通渲染 Shader;
- 贴图格式 PNG → KTX2(GPU 原生压缩),显存占用显著下降。
3. 面捕接入
Mediapipe Web SDK → CompatibleFaceData 协议 → Three.js MorphTarget 驱动面捕解算器从 C# 移植到 JavaScript,核心算法(归纳 + 卡尔曼/低通滤波)保持一致——协议层 CompatibleFaceData 不变,所以驱动端只是换了个语言绑定。
4. 改 Three.js 底层源码
WebGLMotionVector 内存泄漏修复:
- 根因:换装部件切换时,旧 Mesh 的 WebGL 资源(纹理、Buffer)未正确释放;
- 修复:在
dispose()方法中增加gl.deleteTexture()和gl.deleteBuffer()的级联调用; - 验证:连续换装 100 次,内存稳定无泄漏。
5. 性能优化
FPS 从 11 提到 30、WebGL 覆盖率从 30% 提到 93% 的完整优化过程,见 Web 端 11→30 FPS 和 WebGL 覆盖率 30%→93%。
指标
| 指标 | Unity WebGL | Three.js |
|---|---|---|
| 覆盖率 | 30% | 93% |
| 低端机帧率 | — | 30fps |
| 启动速度 | 10s+ | < 3s |
| 包体积 | 10-20MB+ | < 2MB JS |
| 内存 | 高 | 可控 |
复盘
- 技术选型不是「哪个技术更好」,是「哪个技术更适合当前的用户设备分布」。Unity WebGL 在 PC 上没问题,但 B 站用户在移动端、在低端机上。
- 不要害怕改底层源码。Three.js 的 WebGLMotionVector 不改,内存泄漏就只能绕过不能根治;改了对上游提 issue,社区也受益。
- 协议层设计在迁移时兑现价值。CompatibleFaceData 与引擎解耦,面捕从 Unity 迁到 Web 只换了语言绑定,这是当初做通用协议时没想到的回报。