画像から 3D の模型を起こす Pixal3D で、検証用に用意した人物を作り、12,000 三角形に減らして骨を付け、動きを付けられるかを技術検証しました。動きを付けるところまではできましたが、見た目は粗いままで、成功とは言いがたい出来でした。
この記事は、その途中の骨を付ける段で詰まった話です。Blender で Mixamo と同じ名前の骨を置いて自動ウェイトをかけると、警告が出て、腰のほかはどの骨にも重みが付きませんでした。
結論
- Blender で書き出した GLB を読み直すと、UV の継ぎ目で頂点が割れていました。境界の辺が 10,308 本、5,980 個の切れた島になっていて、bone heat がどの骨でも解けませんでした。
- 骨を付ける前に
bmesh.ops.remove_doublesで頂点を繋ぐと、警告が消え、腕・脚の骨にも重みが付きました。UV は面の角ごとに持っているので崩れません。 - 減らした後に小さなかけらや潰れた三角形が残っていても、全部の骨で失敗しました。一番大きい塊だけを残すと通りました。
- 重みが付いた後、腕を下ろすと襟にしわが寄り、上げると上着の裾が開きました。脇の下より下と胸の内側で腕の重みを切ると、どちらも直りました。
試した環境
- Blender 4.5 LTS:スクリプトを
blender -b(画面なし)で動かした - 模型:Pixal3D の人物を 12,000 三角形に減らし、GLB で書き出した物
- 骨:Mixamo の名前(
mixamorig:で始まる)の骨 25 本を置き、ARMATURE_AUTO(自動ウェイト)で親子付け
腰にしか重みが付かない
骨を付けるスクリプトでは、整えた後に書き出した GLB を読み込み、骨を置いて自動ウェイトをかけていました。1回目は次の警告が出ました。
Warning: Bone Heat Weighting: failed to find solution for one or more bones
骨ごとに重みの付いた頂点を数えると、腰(mixamorig:Hips)が 6,111 頂点で、背骨・腕・脚の骨はどれも 0 でした。重みの無い頂点を腰と頭に振る後処理を入れていたので、腰の分はその後処理で付いただけです。警告の文は「1本以上の骨で」ですが、実際にはどの骨でも解けていませんでした。
読み直した模型が、継ぎ目でばらばらだった
Blender で書き出した GLB を、読み直しただけの模型を数えました。
| 項目 | 値 |
|---|---|
| 頂点 | 12,150 |
| 面 | 12,000 |
| 境界の辺 | 10,308 |
| 多様体でない辺 | 10,308 |
| 位置の重なった頂点 | 6,170 |
| つながった塊 | 5,980 |
glTF では UV の継ぎ目で頂点が分けて持たれ、読み込むとそのまま継ぎ目で割れています。継ぎ目ごとに切れた島になり、bone heat がどの骨でも解けませんでした。自分で書き出した物を読み直しただけなので、ここが割れているとは疑いにくい所です。
骨を付ける前に、頂点を繋ぎました。UV は面の角ごとに持つので、頂点を繋いでも崩れません。
import bmesh
bm = bmesh.new(); bm.from_mesh(mesh.data)
bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=1e-6)
bm.to_mesh(mesh.data); bm.free()
これで警告が消え、腕・脚の骨にも重みが付きました。
減らした後のかけらでも、全部の骨で失敗した
その後、減らす前に中空の殻の中を塗りつぶすための升(3次元の格子)を細かく(7mm に)して減らすと、3 頂点だけのかけらが 15 個と、潰れた三角形が残りました。この模型では、bone heat が全部の骨で失敗しました。一番大きい塊だけを残すと通りました。頂点を繋いでも失敗するときは、小さなかけらが残っていないかも数えてみる値打ちがあります。

重みが付いた後の、襟と裾
重みが付いて腕が動くようになると、別の所が崩れました。腕を下ろすと、襟にしわが寄りました。

腕を上げると、上着の裾が開きました。

脇の下より下と、胸の内側で腕の骨の重みを切ると、どちらも直りました。


おわりに
自動ウェイトがどの骨でも解けないときは、骨より先に模型のつながりを数えると、原因が分かりました。試した Blender 4.5 LTS では、glTF を読み直した模型が UV の継ぎ目で割れていました。頂点を繋いで重みが付き、襟と裾の崩れも直せましたが、解けたのは骨の重みの問題までで、人物の見た目は粗いままでした。
※ Blender の glTF の読み込みと自動ウェイトの振る舞いは執筆時点で試した物で、いまは変わっている可能性があります。


