Appearance
衣褶的叠加
背景:纹理换装(A1/A2)已完成,但真正的"换衣服"——挂接独立 FBX 部件到 PMX 骨骼——仍是未竟之梦。 过程:outfit-overlay.ts 加载 FBX 叠加层、按骨骼名重定向到 PMX、匹配率不足则降级静态叠加、隐藏原服装材质。
序、未竟的梦
纹理织工收笔的那夜,初音换上了新衣。但那不是真的换——只是同一件衣服染了不同的色。
王逸看着 outfits/school_uniform/tex/ 下的贴图,想起 01 章末尾写下的那句话:
"下一章:织物的物理 —— A3 与动态网格置换之梦。"
A3,那个被推迟的梦:加载独立的 FBX 部件,挂到主模型的骨骼上,让一件真正的裙子长在身上。
今夜,梦开始落地。
一、叠加的来历
loadOverlay(inst, meshFile, scene) 是梦的入口。
它从模型的本地端口拉取一个 FBX 文件——http://127.0.0.1:${port}/${_encodePath(meshFile)}。_encodePath 是 01 章里那个"斜杠之罪"的修复者:分段编码,保留 /,不让文件系统散架。
FBX 被 ImportMeshAsync 读进场景。一堆 mesh 浮出来。
但浮出来不等于长在身上。它们还带着自己的骨骼——一套和 PMX 不兼容的骨架。
二、骨骼的翻译官
retargetSkeleton 是这场梦的关键,也是最难的一关。
FBX 的骨头按名字,PMX 的骨头按 runtimeBones 索引。两种语言,同一个身体。
翻译官的做法朴素而残酷:遍历 FBX 的每一根骨头,按名字去 PMX 的 runtimeBone 表里查索引,建立 fbxBoneIndex → pmxBoneIndex 的映射。然后用这映射,重写每个顶点的 matricesIndices——把"指向 FBX 第 i 根骨头"改成"指向 PMX 第 j 根骨头"。
顶点被重新分配了主人。
但翻译有失败率。当匹配的骨头少于一半(matchRate < 0.5),翻译官举手投降:
"这具 FBX 和这具 PMX,不是同一个身体。"
此时系统降级——不再追求骨骼绑定,而是把叠加 mesh parent 到 rootMesh,成为一枚静态挂件。衣服不随动作飘,但至少穿上了。
翻译的失败不是错误,是诚实。低于一半的匹配,强行绑定只会让衣褶抽搐成怪物的触手。
三、原衣的退场
新衣要上身,旧衣得退场。
hideMaterials(inst, materialNames) 按材质名,把 PMX 上对应的 mesh 设为不可见,并记下原始可见性。等 restoreMaterials 时,原衣归来。
被加载的叠加层 mesh 会被打上 metadata.outfitOverlay = true 的烙印——一个隐形印章,告诉 01 章里那套纹理替换逻辑:"别碰我,我不是你的布料。"
disposeOverlay 则在换下时释放一切,且格外小心:绝不误删 PMX 与 overlay 共享的那具 skeleton。它比对 mesh.skeleton !== pmxSkeleton,只处置真正属于 FBX 的独立骨骼。
退场和登场同样重要。一件衣服的尊严,在于它能被干净地脱下。
四、王的梦圆
A3 不再是梦。
FBX 部件被加载、翻译、绑定、穿好。原服装退场,新衣随骨骼摆动。匹配率足够时,裙摆真的跟着动作飞;匹配率不足时,它安静地挂着,不抽搐,不崩溃。
王逸转了一下模型。衣褶在动。
最难的加法,是让新东西承认旧东西的骨骼。聚合不是覆盖,是让你的骨头里长出别人的身体。
教训:跨格式的骨骼重定向,匹配率低于阈值就必须诚实降级,而非强行绑定。