手元の Radeon で画像から 3D を作るモデルを技術検証していて、できたメッシュの要約に watertight: false と出ました。「どこかに穴が開いている」と読みたくなり、私たちもそう推測しましたが、誤りでした。閉じていないと判定される理由は穴だけではなく、分けて数えないと何を直せばよいかを取り違えます。
結論
- 辺に接する面の数で決める閉じ判定は、穴の縁が1本も無くても、3枚以上の面が集まる辺(非多様体の辺)があれば false になります。私たちの GLB は、境界辺が 0、非多様体の辺が 4 本でした。
- 穴(境界辺)・非多様体の辺・面積ゼロの面(つぶれた三角形)は、別々に数えます。つながり(連結成分)も一緒に見ておきます。
- 数えるときは、修復も頂点の結合もせず、元の頂点番号のままにします。テクスチャ付きで、UV の継ぎ目で頂点が分かれている GLB は、同じ位置の頂点をまとめてから数えます。
試した環境
- 対象: 以前 Hunyuan3D-2mini で画像1枚から作った、形だけの GLB(3,220,224 bytes、89,428 頂点、178,856 面)
- 検査: Python 3.13.13、numpy 2.4.4、scipy 1.18.0
何を分けて数えるか
閉じ判定が false になったとき、原因の候補を次の3つに分けて数えました。
- 境界辺: 面が1枚しか接していない辺です。穴の縁にあたります。
- 非多様体の辺: 3枚以上の面が接している辺です。
- 面積ゼロの面: 面積が 0 の三角形です。同じ頂点を2度使う三角形もここに入ります。頂点が3つとも違う面積ゼロの面は、辺の数え方による閉じ判定には響きません。響くのは同じ頂点を2度使う面です。
読み込みの段で形を変えない
最初に気をつけたのは、読み込みでメッシュを変えないことです。読み込む道具が頂点をまとめたり面を掃除したりすると、元のファイルにある欠けを数えられなくなります。そこで次のようにしました。
- GLB 2.0 の JSON と BIN のアクセサを自分で読みます。trimesh の処理や Blender への読み込みは通しません。
- すべてのノードの変換が単位行列であることを確かめます。
- 頂点の結合・修復・掃除・書き戻しはしません。辺は元の頂点番号のまま数えます。
- 面積は、float32 で保存された位置から float64 で計算します(三角形の外積の大きさ)。
- 連結成分は、辺を共有する面どうしをつないだグラフで数えます。
- 面の向き(巻き)は、ちょうど2枚の面に接する辺だけで見ます。2枚の向きが逆になっているかを確かめ、非多様体の辺は除いて別に数えます。

数えた結果
| 数えた物 | 値 |
|---|---|
| 頂点 | 89,428(すべて有限。どの面からも使われない頂点は 0) |
| 面 | 178,856(頂点番号はすべて範囲内) |
| 辺(重複を除く) | 268,279 |
| 境界辺(面が1枚だけの辺=穴の縁) | 0 |
| ちょうど2枚の面に接する辺 | 268,275 |
| 3枚以上の面に接する辺(非多様体) | 4 |
| 両端が同じ頂点の辺 | 1 |
| 同じ頂点を2度使う面 | 4 |
| 面積がちょうど 0 の面 | 4 |
| 面積がほぼ 0 の面(しきい値 5.3×10⁻¹²) | 14 |
| 同じ三角形の重複 | 0 |
| 連結成分(面のつながり) | 1 |
| 2枚の面の向きが食い違う辺 | 0 |
境界辺は 0 で、穴はありませんでした。閉じ判定を false にしていたのは、3枚以上の面が接する 4 本の辺です。4 本のうち 3 本は3枚の面に接し、残りの1本は4枚の面に接していました。そしてこの4枚に接する1本は、両端が同じ頂点という辺でした。4 本とも、ある1つの頂点に接しています。同じ頂点を2度使う面と、面積がちょうど 0 の面も、それぞれ 4 枚ありました。
辺ごとに数えた値を見ると、3 本の辺は面の辺として使われた回数が 4 なのに、接する面は 3 枚でした。両端が同じ頂点の辺は、使われた回数が 6 で面は 4 枚です。同じ頂点を2度使う面((a, v, v) の形)は、1枚で同じ辺を2回使います。この GLB の非多様体の辺 4 本は、1つの頂点に集まった、同じ頂点を2度使う 4 枚の面から生じていると読めます(その 4 枚を除いて数え直してはいません)。
つまり、この GLB の閉じていない所は、メッシュ全体に散らばった穴ではなく、1つの頂点のまわりに集まっていました。watertight: false の1行だけから穴だと思い込むと、まったく別の所を探すことになります。
面は全体で1つにつながっていました。2枚の面に接する辺では、向きの食い違いは 0 です。ただし非多様体の辺を除いて見ているので、メッシュ全体で向きがそろっているとまでは言い切れていません。
テクスチャ付きの GLB では、頂点をまとめてから数える
今回の GLB は形だけでした。色付き(テクスチャ付き)の GLB では、UV の継ぎ目で、同じ位置の頂点が別々の番号に分かれています。私たちがその後に画像から 3D を作るのに試した別のモデル(Pixal3D)の出力では、まず同じ位置の頂点をまとめ、継ぎ目で分かれた頂点を戻しました。そのうえで、境界辺・非多様体の辺・連結成分を数えました。試した物では、境界辺はすべて 0 でした。
形だけの GLB を、ファイルの状態のまま確かめたいときは元の番号のまま数えます。継ぎ目で頂点が分かれた GLB では、位置でまとめてから数えます。どちらで数えたかを、結果と一緒に残しておくと読み違えにくくなります。
確かめていないこと
- 自己交差や、部品どうしのめり込み
- 同じ位置にある別々の頂点(今回の数え方ではまとめていません)
- 自動の修復(かけていません)
- 色・テクスチャや、入力の絵への一致
おわりに
watertight: false が言っているのは「閉じていない」ということだけです。境界辺・非多様体の辺・面積ゼロの面を分けて数えると、どこを見ればよいかが分かります。


