[Astro] #175 Draco まわりの不具合をまとめて修正 — 置き場所、VRM が壊れる原因、壊れても戻るアバター
はじめに
前回は、MMD のモーションを、ブラウザで VRM のアニメーションに変換するツールでした。
[Astro] #174 VMD to VRMA — MMD のモーションを、ブラウザで VRM のアニメーションに変換する
MMD のモーション(VMD)を、VRM 用のアニメーション(VRMA)に変換するツールを作りました。
lain-lab.com今回は、新しいツールではなく、不具合の修正です。きっかけは、GLB Compressor で Draco にチェックを入れると、エラーで落ちることでした。調べていくと、同じ根っこの問題が、3 つ出てきました。
- GLB Compressor の Draco が 404 になる(パスの問題)
- VRM Optimizer が、壊れた VRM を出力していた(VRM 拡張のテクスチャ番号のずれ)
- 壊れた VRM を読むと、アバターが復活しなかった(エラーの受け皿がなかった)
画面
GLB Compressor(Draco が使える状態)
Draco のモジュールが ready になり、14.1 MB が 1.4 MB(-90.2%)になりました。
VRM Optimizer(修正後の VRM がアバターとして動く)
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)。
| 圧縮前 | 圧縮後 | |
|---|---|---|
| nodes | 201 | 201 |
| meshes | 3 | 3 |
| materials | 24 | 24 |
| images | 38 | 38 |
| textures | 38 | 35 |
テクスチャだけが、3 つ減っていました。
原因
VRM Optimizer の Draco は、次の順で動いていました。
- VRM の拡張(
VRMなど)を、保存してから、ファイルから外す - gltf-transform で、Draco を掛ける
- 保存しておいた拡張を、元に戻す
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 を読んでも、落ちないようにしたのは、結果として、いちばん役に立ちました。壊れたものは、いつか、別の原因でも、出てきます。