[Astro] #175 Draco まわりの不具合をまとめて修正 — 置き場所、VRM が壊れる原因、壊れても戻るアバター

[Astro] #175 Draco まわりの不具合をまとめて修正 — 置き場所、VRM が壊れる原因、壊れても戻るアバター

はじめに

前回は、MMD のモーションを、ブラウザで VRM のアニメーションに変換するツールでした。

今回は、新しいツールではなく、不具合の修正です。きっかけは、GLB Compressor で Draco にチェックを入れると、エラーで落ちることでした。調べていくと、同じ根っこの問題が、3 つ出てきました。

  1. GLB Compressor の Draco が 404 になる(パスの問題)
  2. VRM Optimizer が、壊れた VRM を出力していた(VRM 拡張のテクスチャ番号のずれ)
  3. 壊れた VRM を読むと、アバターが復活しなかった(エラーの受け皿がなかった)

画面

GLB Compressor(Draco が使える状態)

GLB Compressor で Draco にチェックを入れて ready と表示され、14.1 MB が 1.4 MB になった画面

Draco のモジュールが ready になり、14.1 MB が 1.4 MB(-90.2%)になりました。

VRM Optimizer(修正後の VRM がアバターとして動く)

VRM Optimizer で 17.7 MB の VRM が 6.8 MB になった画面

17.7 MB が 6.8 MB(-61.8%)です。このファイルを、アバターに読み込んで、普通に動きました。

1. GLB Compressor の Draco が 404 になる

症状

Draco にチェックを入れると、draco_encoder.wasm、draco_decoder.wasm、draco_encoder.js、draco_decoder.js の 4 つが、404 になりました。

原因

ページが読んでいたパスは、./draco/… という、相対パスでした。相対パスは、ページの URL で、行き先が変わります。

ページURL./draco/ の行き先
GLB Compressor/glb_compressor/(末尾スラッシュあり)/glb_compressor/draco/(存在しない)
VRM Optimizer/vrm_optimizer(末尾スラッシュなし)/draco/(存在する)

同じ ./draco/ でも、末尾のスラッシュの有無で、動いたり動かなかったりしていました。VRM Optimizer は、たまたま動いていただけです。

もう 1 つ、紛らわしいことがありました。public/glb-compressor/ という、旧版の単体 HTML が、残っていました。Astro 版は glb_compressor(アンダースコア)で、フォルダは glb-compressor(ハイフン)です。名前が似ていて、取り違えやすい状態でした。

直し方

絶対パスにして、置き場所を 1 か所にしました。

  • Draco の 4 ファイルを public/tool/draco/ に集めた(ツール系は public/tool/ に寄せる方針に合わせた)
  • glb_compressor.astro、vrm_optimizer.astro、useWiredVRM.ts のパスを /tool/draco/ にした
  • 旧版の public/glb-compressor/(HTML と、重複していた draco/)を削除した

public/room/js/draco/ は、room 専用の別のコピーなので、触っていません。WiredAvatar.tsx と room2〜4 は、CDN の Draco を使っているので、これも触っていません。

2. VRM Optimizer が、壊れた VRM を出力していた

症状

Draco を有効にして圧縮した VRM を、アバターとして読み込むと、エラーになりました。

Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'extensions')

調べたこと

圧縮の前後の VRM の JSON を、比べました(SampleE.vrm、17.7 MB から 6.8 MB)。

圧縮前圧縮後
nodes201201
meshes33
materials2424
images3838
textures3835

テクスチャだけが、3 つ減っていました。

原因

VRM Optimizer の Draco は、次の順で動いていました。

  1. VRM の拡張(VRM など)を、保存してから、ファイルから外す
  2. gltf-transform で、Draco を掛ける
  3. 保存しておいた拡張を、元に戻す

gltf-transform は、glTF のマテリアルから参照されていないテクスチャを、捨てます。この VRM では、_SphereAdd と _OutlineWidthTexture の 3 枚(35、36、37 番)が、VRM 拡張(MToon の textureProperties)からだけ参照されていました。

そのため、Draco のあとには、テクスチャが 35 枚になりました。ところが、元に戻した VRM 拡張は、圧縮前の番号のままです。参照 120 個のうち、18 個が、存在しない番号か、別の画像を指していました。three-vrm が、存在しない番号のテクスチャを引いて、エラーになったと見ています(スタックまでは、追っていません)。

直し方

Draco のあとで、VRM 拡張の番号を、付け替えます。

  • 捨てられたテクスチャを、元の画像を指す形で、テクスチャの一覧に追加する
  • VRM 拡張の番号(0.x の materialProperties[].textureProperties と meta.texture、1.0 の VRMC_materials_mtoon)を、新しい番号に直す
  • ノード、メッシュ、マテリアルの名前の並びが、元と違っていたら、復元をやめて、Draco なしで出力する(壊れたものは出さない)

確かめたこと

壊れた出力を使って、修正後のロジックを、Node で通しました。VRM のテクスチャ参照 120 個が、すべて、元と同じ画像を指しました。範囲外は 0 でした。

ブラウザでも、修正後の VRM を、アバターとして読み込んで、動くことを確認しました。サイズは、7,097,444 バイトから 7,097,520 バイトへ、76 バイト増えました。復元したテクスチャ 3 枚の分です。

Draco なしの圧縮(WebP とリサイズだけ)には、この問題は、ありません。

3. 壊れた VRM を読むと、アバターが復活しない

症状

壊れた VRM を読むと、アバターの Canvas ごと、落ちました。そのあと、WebGL のコンテキストが失われて、戻りませんでした。しかも、壊れた VRM は IndexedDB に残るので、リロードしても、同じ VRM を読みに行って、また落ちます。

壊れた VRM が、手元になかったので、今まで、確かめられませんでした。VRM Optimizer が、壊れたものを出してくれたおかげで、再現できました。

直し方(WiredAvatar.tsx)

  • WiredAvatarCore を、エラーバウンダリで囲んだ。読み込みに失敗しても、Canvas が落ちない
  • 失敗したら、デフォルトのモデル(models.json の先頭)に切り替える。デフォルトも失敗したら、描画をやめる(無限ループの防止)
  • ローカルの VRM が壊れていたら、IndexedDB から、そのレコードを削除する。ただし、エラーメッセージに「今読んでいるモデルの URL」が入っているときだけ。アニメーションなど、別の原因で、正常なモデルを消さないため
  • 失敗したときに、wired-avatar-load-failed というイベントを、window に送る(親が、保存済みの選択を消すため)

壊れた VRM を読んだあと、デフォルトのアバターに戻ることを、確認しました。

寄り道:DevTools のゴミ箱

IndexedDB の壊れたレコードを、DevTools のゴミ箱で消そうとしたところ、選択した行とは別の行が、消えました。原因は、わかっていません。キャッシュだったので、実害はありませんでしたが、LOCAL_VRM_…(自分で読み込んだ VRM)が、巻き込まれると、戻せません。消すときは、コンソールで、キーを指定したほうが、安全です。

indexedDB.open('WiredAssetCache').onsuccess = e => {
  const tx = e.target.result.transaction('models', 'readwrite');
  tx.objectStore('models').delete('LOCAL_VRM_…');
};

確認できたこと

  • GLB Compressor:Draco が ready になり、圧縮できた(14.1 MB から 1.4 MB)
  • VRM Optimizer:修正後の VRM(6.8 MB)を、アバターとして読み込めた
  • 修正後のロジック:実際の壊れた VRM で、参照 120 個が、すべて元と同じ画像を指す
  • アバター:壊れた VRM を読んでも、デフォルトに戻る

未確認のこと・未対応のこと

  • VRM 1.0:VRMC_materials_mtoon 用の付け替えは、書いたが、実データで試していない
  • Draco を掛けた VRM が、他のアプリ(UniVRM、VRoid Hub など)で開けるかは、確かめていない
  • 自動削除は、エラーメッセージに URL が入らない失敗では、動かない(デフォルトへの切り替えは、動く)
  • useWiredVRM.ts の Draco ローダーは、VRM に Draco を使っていないので、実質使われていない

おわりに

Draco の 404 は、パスの間違いという、小さな問題でした。ところが、その先で、VRM に Draco を掛けると、参照がずれるという、別の問題が見つかりました。VRM は、glTF のうえに、独自の拡張を、番号で参照させています。ほかのツールが、ファイルを書き直すと、その番号が、ずれることがあります。

壊れた VRM を読んでも、落ちないようにしたのは、結果として、いちばん役に立ちました。壊れたものは、いつか、別の原因でも、出てきます。