Skip to content

在浏览器里造开放世界,第 25 部分:一副骨架,所有装束

作者:Oleg Sidorkin,Cinevva CTO 和联合创始人

刚看到?看系列导览。那里解释了 spike 是什么,并链接了所有部分。

第 24 部分让世界变得持久,并给它的植被加上了风。这一部分又回到里面的人,但是从衣橱这一侧来看:一个创作者能不能选一个基础身体、换衣服、挑发型、播放任意动画,全部基于一个免费资源包,不做 rigging、不做动画制作?做一套自定义 rig 不是该 derisk 的东西。值得回答的问题是,一个 CC0 资源包能不能把整副担子都挑起来。

一个生来就拼得上的资源包

在新标签页打开 Spike 50 ↗ · 看源码

Quaternius 提供了四个对得齐到几乎让人起疑的资源:Universal Base Characters 含男性和女性身体,外加八种发型和两套眉毛;Modular Character Outfits 拆成身体、手臂、腿、脚几个部件,还带护肩、兜帽这样的配件;Universal Animation Library 第一包和第二包,共 262 个 clip,覆盖 locomotion、战斗、攀爬、耕作、钓鱼、idle 和社交动作。检查它们的 glTF 揭示了它们为什么能拼到一起:四个包里的每一个 mesh 都 rig 到同一副 65 关节的 UE5 风格骨架上,相同的关节名、相同的 bind pose,从 root 经 pelvis、spine 到 head,镜像的双臂每侧十五个手指关节,镜像的双腿。完全不用 retargeting。这跟 Spike 35 那个三骨架 retargeting 问题正好相反,那里一套 213 骨的脸部 rig 得跟一套 65 骨的 Mixamo rig 对话。这里四个包都是有意在动画库的骨架上制作的,绑定是精确的。

这把这次 spike 变成了一次绑定练习,而不是一次动画管线练习。基础层加载一个身体 glTF,用 SkeletonUtils.clone 克隆场景,这样每个角色拿到一份全新的骨架实例,而不是把骨骼变更喂回 loader 缓存的场景里,然后把那副 65 骨骨架连同作为动画 root 的 armature 对象一起缓存成规范版本。

把一件外套重新绑到身体上

让整件事能跑起来的诀窍是,你不能简单地把一件 outfit 的 mesh 重新挂进场景里,因为它仍然引用着自己那个 glTF 里的骨骼,而那些骨骼坐落在一棵不同的子树里。每个模块化部件,一具农夫身体或一条游侠的腿,都作为它自己的 SkinnedMesh 到达,带着它自己那份同一 armature 的克隆。绑定按源骨架骨骼的原始顺序遍历它们,按名在规范骨架里查找每一根,并从那些重新绑定的骨骼构建一个新的 Skeleton,复用源的 bind-inverse 矩阵,然后用源的单位 bind 矩阵调用 SkinnedMesh.bind。绑定之后,mesh 从它的源场景脱离,直接挂到角色 root 之下。因为 bind 矩阵捕获的是绑定时刻的世界变换,而 root 坐在单位变换上,所以 mesh 自己的世界变换永远不会影响输出,蒙皮完全通过现在共享的骨骼发生。

头发和眉毛也是 slot,它们纠正了一个错误的假设。文件夹结构上写着"rig 到 head 骨",这暗示头发会是一个静态 mesh 用一个烘焙好的偏移挂到头上。其实不是。每个头发文件都是一个 SkinnedMesh,权重分布在全部 65 个关节上,长发的权重渗进上 spine 和颈部,所以角色低头时头发会垂下来。所以头发走的是和任何 outfit 部件一样的按名重新绑定路径。唯一的特例是 outfit 的身体 slot:它附上时,基础身体 mesh 会被隐藏,这样 outfit 不会穿过它;而手臂、腿、脚和配件就直接堆叠,因为这个包在制作时就让它们能跟透出来的基础身体共存,凡是衣服露出皮肤的地方都让身体透出来。

262 个动画白送

一旦骨架共享,动画几乎是反高潮的。两个库 glb 加载进来,它们的 clip 摊平成一个带名字的列表,那些 clip 里的 track 名用 bone.positionbone.quaternionbone.scale 引用着确切的关节名。一个挂在角色 armature 上的 AnimationMixer 按名找到那些骨骼并直接驱动它们,切换时做 crossfade。查看器的右侧面板是一个可筛选的全部 262 个 clip 的列表,带一个来源标签,所以搜"fish"会浮现出两个库里所有的钓鱼动画。标准三点布光配柔和阴影、脚下一张极坐标网格、ACES 色调映射,让风格化的基础贴图保持可读,同时不把深色紧身衣的阴影压死。

这给产品 derisk 的东西是巨大的。avatar 定制功能可以基于一个 CC0 资源包发布,零 rigging 工作、零动画制作:放进这四个包,接一个衣橱 UI,开箱就有大约六套 outfit 乘八种发型乘两种性别乘 262 个动画的变化量。这副 65 关节的骨架小到 GPU 蒙皮开销可以忽略不计,而 Spike 45 的批量蒙皮工作已经能在这种规模的 rig 上处理 200 多个 avatar。CC0 不带任何使用限制,所以在一个付费的创作者平台上做商用没问题。剩下的开放问题是用户自己上传的 outfit,这是个 Phase 3 问题,靠上传时把它 retarget 到这副规范骨架上来解决,正好是 Spike 35 运行时 retargeting 的反向操作。资源体量在磁盘上大约 147 MB,那是原始 2K PNG 和未压缩动画 glb;生产环境用 KTX2 纹理压缩加一遍 gltf-transform 会把它缩到大约 30 MB。

本章涉及的技术

一个资源包共用一副规范骨架。 四个 Quaternius 包(基础身体、模块化 outfit、两个动画库)全部 rig 到一副完全相同的 65 关节 UE5 风格骨架上,相同的关节名和 bind pose,所以把它们组合起来不需要 retargeting。用 SkeletonUtils.clone 克隆基础身体,让每个角色拿到一份全新的骨架实例,而不是去改 loader 缓存的场景。

按名重新绑定骨架。 每个模块化部件带着它自己的 SkinnedMesh,引用它自己的 armature。重新绑定按顺序遍历源骨骼,按名把每一根映射进规范骨架,构建一个新的 Skeleton 复用源的 bind-inverse,并用单位 bind 矩阵调用 SkinnedMesh.bind,然后把 mesh 挂到共享的角色 root 之下,这样蒙皮完全通过共享骨骼运行。outfit 的身体 slot 会隐藏基础身体以避免穿模;其他 slot 直接堆叠。

头发作为一个蒙皮 slot。 头发不是一个挂在 head 骨上的静态附件,而是一个 SkinnedMesh,权重分布在全部 65 个关节上,长发的权重加到上 spine 和颈部,所以低头时会垂下来。它走的是和 outfit 部件一样的重新绑定路径。

按名键值的动画 retargeting 白送。 Animation Library 的 clip 按确切的名字引用关节,所以一个挂在共享 armature 上的 AnimationMixer 带 crossfade 直接驱动全部 262 个 clip,不用逐 clip retargeting。一个 CC0 许可证覆盖商用,而小关节数让 GPU 蒙皮足够便宜,可以复用 Spike 45 的批量蒙皮路径。见 GPU 驱动的 LOD


第 25 部分,共 29 部分。 上一篇:第 24 部分 - 保存一个世界,以及你能看见的风 下一篇:第 26 部分 - 在每个尺度上都立得住的水 系列导览:/zh-CN/blog/2026-02-25-open-world-browser-series-guide