【Unity WebGL】PoseMirrorのパフォーマンス改善3つのアプローチ
投稿日: 2026年3月3日(2026年10月5日更新)
対象読者: Unity WebGL と JavaScript のライブラリを同じページで動かしていて、表示の遅さに困っているエンジニア
この記事はこんな方向け:
- Unity WebGL のページを PageSpeed Insights にかけたら点数が低かった方
- ページを開いてから操作できるまでが、スマホで遅いと感じている方
- 何を直すと効いて、何が効かないのかを先に知りたい方
解決方法
PoseMirror(Unity WebGL で動く3Dデッサン人形)のページで効いたのは、次の2つでした。
- 原因の部品を外して測り直し、本当に遅い部品を特定する。PoseMirror では Unity の起動だった
- 一部の人しか使わない重いファイル(写真解析のモデル 30.7MB)を、使う直前まで読まない。起動時の転送が 48.9MB から 15.8MB に減った
キャッシュの延長や、Unity の圧縮をやめる変更は、測ると効かないか悪化しました(③で数値を示します)。
はじめに
PoseMirror は、写真から MediaPipe(JavaScript)で体の関節の位置を取り、Unity WebGL の3D人形に反映するアプリです。1つのページに Unity 本体と MediaPipe のモデルが同居しています。
2026年9月28日に、このページの読み込みを測り直しました。その中で、ページを開いただけの人にも写真解析用のモデル 30.7MB を毎回ダウンロードさせていたことが分かりました。この記事では、そのとき測った数値と、効いた手と効かなかった手を順に書きます。
アプローチ①:部品を外して測り、遅い部品を特定する
PageSpeed Insights と同じ計測エンジンの Lighthouse で、デスクトップの点数を測ると、パフォーマンスは 82 前後でした。同じ条件でも1回ごとに 76〜89 と振れるので、比べるときは複数回の中央値を使っています。
減点の中身を見ると、表示の速さ(FCP 0.3秒、LCP 0.5秒)とレイアウトのずれ(CLS 0.02)は良好でした。点数を下げていたのは、操作を受け付けない時間の合計(TBT、Total Blocking Time)の 300〜400ms だけです。
TBT の原因が Unity かどうかは、Unity のファイルを読ませずに測り直すと分かります。Lighthouse の --blocked-url-patterns で特定の URL を遮断できます。
npx lighthouse@12 https://kabe-tech.com/poseMirror --preset=desktop \
--blocked-url-patterns="*unityweb*"
Unity を遮断すると点数は 100、TBT は 40ms になりました。遅さのほぼすべてが Unity の起動から来ています。
Lighthouse の診断には、時間のかかった処理がファイル名つきで並びます。ただし、この名義は原因と一致しないことがありました。billing.js の名義で約140ms の処理が出ていたので、課金に使う Firebase の読み込みを遅らせてみましたが、Firebase を読まなくても同じ処理が残りました。名義を見て原因を決める前に、その部品を外して測り直すのが確実です。
アプローチ②:一部の人しか使わない重いファイルは、使う直前まで読まない
PoseMirror のページは、開いた時点で MediaPipe の解析モデル(pose_landmarker_heavy.task 30.7MB)と wasm(2.6MB)を読み込んでいました。当時の Unity 本体(約15MB)の2倍以上の大きさです。ところが、写真を解析する人は訪問者の約34%しかいません。残りの約66%にとっては、使わないファイルの読み込みでした。
そこで、モデルは写真を選んだとき、または解析ボタンを押したときに初めて読み込むように変えました。
// 同時に呼ばれても取りに行くのは1回だけ。失敗したら次の呼び出しでやり直す
let modelPromise = null;
function ensureModel() {
if (!modelPromise) {
modelPromise = (async () => {
const vision = await FilesetResolver.forVisionTasks(WASM_URL);
return PoseLandmarker.createFromOptions(vision, {
baseOptions: { modelAssetPath: MODEL_URL, delegate: "GPU" },
runningMode: "IMAGE",
});
})();
modelPromise.catch(() => { modelPromise = null; });
}
return modelPromise;
}
回線を 10Mbps、CPU を4倍遅くした条件(Playwright で計測)で、起動時の転送量と起動時間は次のように変わりました。
| 変更前 | 変更後 | |
|---|---|---|
| ページを開いたときの転送量 | 48.9MB | 15.8MB |
| Unity の起動完了まで | 20.5秒 | 14.5秒 |
後回しにした分、写真を解析する人は待ち時間が増えます。これを2つの工夫で埋めました。
1つ目は、読み込みを始める時点を早めることです。写真選択ボタンを押した瞬間に、モデルの読み込みを始めます。スマホでは写真を探すのに数秒かかるので、その間に読み込みが進みます。ドラッグ&ドロップで写真を入れた場合はボタンを押さないので、写真が入った時点でも読み込みを始めます。
imageInput.addEventListener("click", () => { ensureModel().catch(() => {}); });
2つ目は、最初から表示しているサンプル写真の解析結果を、あらかじめファイル(4KB)に保存しておくことです。サンプル写真のまま解析ボタンを押した人には、モデルを読み込まずにその結果を出します。表示まで 0.8秒で、実際に解析すると初回は 4.5秒以上かかります。保存した結果と実際の解析結果の差は、最大でも 6×10⁻⁶ でした。
アプローチ③:効かなかった手も測ってから捨てる
速くなりそうに見えて、測ると効かなかった変更が3つありました。
圧縮を配信側に任せると、遅い回線で遅くなった
Unity のファイルを圧縮せずに置き、配信側(Firebase Hosting)に圧縮させる案です。Unity は、自分で圧縮したファイルをブラウザ上の JavaScript で展開します。配信側に任せればその手間が消えるので速くなる、という見込みでした。本番とは別のプレビュー用 URL に出して測ると、全体に悪化しました。
| Unity で圧縮(現状) | 圧縮なし+Firebase に任せる | |
|---|---|---|
| 転送量 | 10.33MB | 13.59MB |
| 起動時間(回線制限なし) | 1.32秒 | 0.87秒 |
| 起動時間(10Mbps) | 9.6秒 | 11.4秒 |
| Lighthouse の点数 | 82 前後 | 60 |
Firebase Hosting は大きいファイルを brotli ではなく gzip で返したため、転送量が32%増えました。速い回線では起動が速くなりますが、スマホの回線に近い 10Mbps では遅くなります。
Firebase を後から読んでも、点数は変わらなかった
アプローチ①に書いたとおり、Firebase の読み込みを使う直前まで遅らせても、点数は変わりませんでした。
キャッシュを1年に延ばしても、減ったのは問い合わせの往復だけ
Unity のファイルのキャッシュ期間を1年に延ばすと、2回目の訪問で約10MBを取り直さずに済むはずでした。実際に確かめると、以前の1時間の設定でも、期限が切れたあとの再取得は「変更なし(304)」の応答だけで、本文は0バイトでした。1年に延ばして減るのは、変更の有無を問い合わせる往復だけです。
今の状態と、残っている課題
いまの点数は 82 前後で、減点は Unity 起動の TBT だけです。同じ日にほかの Unity WebGL のサイトを測ると、ページを開いてすぐ Unity を起動する作りのものは 38〜63 でした。起動時から3D人形を見せる作りを変えない限り、TBT はこれ以上大きくは減らせないと考えています。
残っている課題は、Unity のファイルに正しい圧縮の情報(Content-Encoding: br)を付けて配信することです。ブラウザに展開を任せられるようになるので、ヘッダーを付けたローカルの試験サーバーでは、起動が 944ms から 677ms に縮みました。ただ、Firebase Hosting は設定に書いたこのヘッダーを無視するため、本番ではまだ実現できていません。
Unity のビルドそのものを小さくする方法(圧縮形式、テクスチャ、コードの削減)は、【Unity】Webアプリで表示するための軽量化方法にまとめています。
まとめ
遅いページを前にすると、思いつく高速化を順に足したくなります。PoseMirror で効いたのは、遅い部品を外して測ることと、一部の人しか使わない重いファイルを後回しにすることの2つでした。どちらも、先に測ったから選べた手です。キャッシュの延長や圧縮のやり方の変更は、もっともらしく見えましたが、測ると効かないか悪化しました。変更を本番に出す前に、回線を絞った条件で前後を比べることをおすすめします。
この記事で軽くしたページは、PoseMirror でそのまま試せます。
関連記事:
- 【Firebase】Unity WebGLを無料で公開する手順:Firebase Hosting での公開手順
- 【Unity】Webアプリで表示するための軽量化方法:Unity のビルドサイズの削減