VRChatワールド「海月の回廊」を725MBから218MBへ — 見た目を落とさずに容量を7割減らした方法

VRChatのワールド「海月の回廊 - Jellyfish Observatory」の容量を、725.63MBから218.19MBへ、約7割減らしました。クラゲの数も形も動きも減らしていません。テクスチャーを荒くしたり、ポリゴンを間引いたりもしていません。この記事では、何が容量を使っていたのか、どうやって減らしたのか、そして「本当に見た目は落ちていないのか」を、比較画像と数字で確かめた結果までまとめます。
▶ 遊ぶ: 海月の回廊 - Jellyfish Observatory(VRChat)(PC版VRChat。最大32人、推奨16人)

海月の回廊とは
海月の回廊は、クラゲだけを集めた架空の水族館です。照明を落とした館内に、円形の大水槽と、小さな水槽が並ぶ12の展示室があります。展示項目は189。クラゲ専門の水族館の展示記録をもとにした177項目に、深海などの12項目を加えました。
- 大水槽のライトアップ: 光の柱の中を、たくさんのクラゲがゆっくり拍動しながら漂います。青・紫・虹色の演出に切り替わります。
- 12の展示室: 壁の丸窓の向こうに、1種ずつクラゲを展示しています。名前と学名のラベル付きです。
- 館内案内: 入口の案内板のボタンに触れると、各展示室へ移動できます。歩いても回れます。
- クラゲの動き: 傘を縮めて上がり、ゆるめて沈む動きを、種類ごとに付けています。拍動はおよそ3.4秒に1回。クシクラゲの仲間は漂うだけ、底生や固着する種類は動かしていません。




結果: 725.63MB → 218.19MB
容量は、VRChat SDKが作るビルドファイル(アップロードされるファイル)の大きさで比べています。作業は2段階に分けました。

| 中身(圧縮前の大きさ) | 最適化前 | 第1段階 | 第2段階 |
|---|---|---|---|
| メッシュ | 1685.06MB | 652.08MB | 560.03MB |
| テクスチャー | 88.19MB | 136.19MB | 136.19MB |
| フォント | 15.70MB | 0.99MB | 0.99MB |
| アニメーション | 6.14MB | 10.19MB | 6.15MB |
| 合計(圧縮前) | 1797.34MB | 801.72MB | 705.63MB |
| ビルドファイル(圧縮後) | 725.63MB | 261.71MB | 218.19MB |
テクスチャーが途中で増えているのは、案内ポスターを小さく縮めた状態から、元の解像度(1024×1536)に戻したためです。理由は後で説明します。
まず、何が容量を使っているかを調べた
最初に、ビルドのたびにUnityが残すビルドレポートを読み、素材ごとの大きさを一覧にしました。結果ははっきりしていて、圧縮前の1797MBのうち1685MB、約94%がクラゲのメッシュでした。テクスチャーは88MB、5%ほどしかありません。「最適化といえばテクスチャー圧縮」と思っていましたが、このワールドではそこを削っても効果は小さいことが分かりました。
クラゲのメッシュが大きい理由は、変形のデータです。189項目のクラゲは、それぞれ3段階の細かさ(LOD)を持ち、触手や口腕などの部位ごとに分かれています。全部で約800万頂点あります。そして各メッシュに、拍動(Pulse)と2種類の揺れ(FlowA・FlowB)という3つのブレンドシェイプが入っていました。
第1段階: 変形をブレンドシェイプからシェーダーへ(725MB → 262MB)
Unityのブレンドシェイプは、変形ごとに「位置・法線・接線の差分」を32bit浮動小数点で持ちます。3つの変形があると、元の頂点データの何倍もの差分データが載ります。しかもスキンメッシュとして、毎フレームCPUやGPUで変形を計算します。
そこで、変形の差分を半精度(16bit)の頂点データとしてメッシュに入れ、変形の計算をクラゲのシェーダーの中で行う形に置き換えました。式はUnityのブレンドシェイプと同じで、重みの上限も同じように扱います。
- SkinnedMeshRenderer 3,682個を、普通のMeshRendererに置き換えました。
- 変形の重みを動かしていたアニメーション567本を、マテリアルの値を動かす形に書き換えました。拍動のタイミングや速さは変わりません。
- 中身がまったく同じメッシュ361個は、1つにまとめて共有しました。
同じ段階で、ラベル用の日本語フォントも整理しました。Noto Sans CJK JPを丸ごと(16.5MB)入れていたので、ワールドで使っている481文字と、ひらがな・カタカナ・英数字などをまとめた部分セットにしました。1MBほどになり、表示される文字は変わりません。
変換の前後は、同じカメラ・同じ変形の重みで撮り比べました。

差分画像は、違いを8倍に強調しています。違いが出たのは細い触手の縁の1画素ほどで、輪郭の位置が画素の境目でわずかにずれたものです。6種類で比べて、画面全体の平均の差は255段階中0.014〜0.405でした。
第2段階: 頂点データを詰め直す(262MB → 218MB)
第1段階のあとも、容量の大部分はクラゲの頂点データでした。1頂点あたり80バイトです。ここからは、見た目に影響しない精度まで、データの表し方を変える作業です。
| データ | 変更前 | 変更後 | 最大の誤差 |
|---|---|---|---|
| 位置 | 32bit小数×3(12バイト) | 16bit整数×4(8バイト) | クラゲの半径の0.005%以下 |
| 法線・接線 | 32bit×3+16bit×4(20バイト) | 八面体エンコード 16bit×4(8バイト) | 法線0.06度、接線0.24度 |
| UV | 32bit小数×2(8バイト) | 16bit整数×2(4バイト) | 1024px画像で0.01画素以下 |
| 頂点カラー | 8bit×4 | そのまま | なし |
| 変形の差分 | 半精度×18(36バイト) | 半精度×18(36バイト、値を格子にそろえる) | 位置: 半径の0.03%以下、法線: 0.05度 |
| 1頂点あたり | 80バイト | 60バイト |
位置: メッシュごとに大きさをそろえてから16bitにする
16bit整数で位置を持つと、表せるのは−1〜1の範囲を65,535段階に分けた値だけです。そこで、メッシュごとに中心と大きさを計算し、変形した姿まで含めて−1〜1に収まるように正規化しました。元の大きさに戻す拡大と移動は、メッシュではなくGameObjectのTransform(3,682個)に持たせています。GPUが16bit整数を−1〜1の小数に戻すので、シェーダーで位置を復元する計算は要りません。
正規化には、もう1つ良い効果がありました。このワールドのクラゲのメッシュは、元データの単位の関係で0.1mm程度の極端に小さな座標で作られているものがあります。そのため変形の差分を半精度にすると、半精度で表せる範囲の下限に近づき、精度が落ちていました。正規化してから半精度にしたことで、変形の精度は以前より上がっています。
法線と接線: 八面体エンコード
法線は長さ1の向きなので、3つの値は要りません。球面を八面体に見立てて2つの値に写す「八面体エンコード」を使い、法線と接線をそれぞれ16bit×2で持たせました。シェーダーで元の向きに戻します。復元したときの向きのずれが最も小さくなる値を、候補を比べて選んでいます。
圧縮が効く形にそろえる
VRChatのビルドファイルは、全体をLZMAで圧縮しています。データの大きさを減らすだけでなく、圧縮しやすい値にそろえることも効きます。変形の差分を、見た目に出ない細かさの格子(位置は正規化後の2−15、法線は2−10)に丸めると、下位のビットがそろって圧縮率が上がります。サンプルのメッシュで実際にLZMAで圧縮して比べ、効果を確かめてから全体に適用しました。
アニメーションの重複を削除
第1段階では、念のため、書き換えたアニメーションに元のブレンドシェイプの曲線も残していました。置き換えが終わって使う場所がなくなったので、対応するマテリアルの曲線があることを1本ずつ確かめてから、7,446本を削除しました。
詰め直しの前後も、同じカメラで撮り比べました。下の比較は、後で説明する法線の修正を入れる前の、詰め直しだけの比較です。
9種類で比べて、平均の差は255段階中0.012〜0.393でした。差が出たのはやはり細い触手の縁です。極小メッシュの変形が以前より正確になった分のずれも含まれています。

途中で見つけた不具合: 変形すると法線が真上を向いていた
詰め直しのために全頂点のデータを調べていたところ、おかしな値に気付きました。約800万頂点のうち64.5%で、変形したときの法線が「真上(0,1,0)」になっていたのです。
原因は、Unityがブレンドシェイプの法線を取り込み時に計算する処理です。先ほど書いた極端に小さな座標のメッシュでは、計算の途中で値が小さくなりすぎて失敗し、法線が真上の向きになっていました。クラゲは静止した姿でも揺れの変形がかかっているので、普段からこの壊れた法線で描かれていたことになります。
クラゲのシェーダーは、法線と視線の角度で「縁ほど明るく、中央ほど透ける」表現をしています。法線が全部上を向くと、この効果が傘全体に均一にかかり、のっぺりと光る見た目になります。

修正では、取り込んだ元の法線を、変形による面の傾きに合わせて回転させる方法を使いました。修正の方法は3通りを試しました。そのうえで、実際に描いた見た目と、圧縮後の容量で比べて決めています。
- 壊れていた種類は、傘の丸みと縁の光り方、中の器官が見えるようになりました。
- もともと正しく計算できていた種類は、修正の前後で見た目がほぼ同じです。
- 回転方式は差分の値が小さく、圧縮もよく効きます。Unity方式で計算し直す場合と比べて、圧縮後の頂点データが2割ほど小さくなりました。

ただし、壊れていた種類のなかには印象が変わったものもあります。下のヒガサクラゲは、修正前は杯の内側に濃い影がありました。この影は真上を向いた法線が作っていたもので、修正後は元のモデルの法線に沿った、明るく滑らかな陰影になっています。

あえて変えなかったもの
案内ポスターは無圧縮のまま
案内ポスター9枚は1024×1536で、テクスチャー圧縮がかかっていませんでした。縦横が2の累乗でない画像にミップマップを付けると、UnityがBC7圧縮をしないためです。圧縮すべきか、形式ごとに一時的なアセットバンドルを作り、LZMA圧縮後の大きさと画質を実測しました。
| 形式 | 9枚の配布サイズ(LZMA後) | 画質(元画像とのPSNR) |
|---|---|---|
| 無圧縮(RGBA32・現在) | 2.57MB | 元画像と同一 |
| BC7(2の累乗に引き伸ばし) | 3.56MB | 30.4dB |
| DXT1(2の累乗に引き伸ばし) | 1.08MB | 29.9dB |
ポスターは色数の少ないイラストなので、無圧縮のほうがLZMAがよく効き、BC7より配布サイズが小さくなりました。DXT1なら1.5MBほど減りますが、画質が下がります(PSNRの低下には、2の累乗への引き伸ばしの影響も含まれます)。画質を落とさないという方針どおり、無圧縮のままにしました。そのぶんGPUのメモリーは1枚8MBを使います。
ライトマップは焼き直さない
Bakeryで焼いたライトマップは4096×4096が3枚で、BC6Hで圧縮済みです。調べると、画像の55〜73%が使われていない空き領域でした。ただ、空き領域は圧縮するとほとんど容量を取りません。1枚の配布サイズは約4MBでした。詰め直して焼き直しても配布サイズはあまり減らず、焼き直しで照明の見え方が変わるおそれもあるので、手を付けていません。
ポリゴンは減らしていない
クラゲの形は一切変えていません。頂点数は約800万のまま、遠くに行くと軽い形に切り替える3段階のLODもそのままです。
本当に品質は落ちていないのか
「容量を7割減らして見た目は同じ」と言われても、にわかには信じにくいと思います。確かめたことと、まだ確かめていないことを分けて書きます。
数字で確かめたこと
- データの誤差: 全2,121メッシュ・約800万頂点で、変換前後の値を比べて誤差の最大値を出しました。位置はクラゲの半径の0.005%以下(直径30cmのクラゲで10マイクロメートル以下)、法線は0.06度、UVは1024px画像で0.01画素以下です。
- 同じカメラでの撮り比べ: 第1段階で6種類、第2段階で9種類のクラゲを、同じ位置・同じ変形の重みで描いて画素ごとに比べました。違いは細い触手の縁の1画素程度で、平均の差は255段階中0.4以下です。
- ポスター: 無圧縮のままなので、元画像と画素単位で同じです。
- 館内全体: 入口、ホール、大水槽、展示室など8か所の確認画像を、作業前後で同じ視点から撮り比べました。

見た目が変わったところ
見た目が変わったのは、法線が壊れていたクラゲの陰影だけです。不具合を直した結果で、縁が光り中央が透ける、シェーダーが本来意図していた見え方になりました。上の大水槽の比較で、右の傘の縁が明るくなっているのがその違いです。データの圧縮による変化ではありません。
まだ確かめていないこと
比較画像はすべてUnityエディター上で描いたものです。VRChatへのアップロードは成功し、公開中のワールドはこの版に置き換わっています。一方で、VRChatのクライアント内、特にVRゴーグルでの最終的な見え方は、これからじっくり確かめます。SkinnedMeshRendererをなくしたことで描画の負荷も下がっているはずですが、フレームレートの計測はまだです。
まとめ
- 容量を減らす前に、ビルドレポートで中身を調べる。このワールドでは、テクスチャーではなくクラゲの変形データが94%を占めていました。
- ブレンドシェイプをシェーダーでの変形に置き換え、725MBから262MBへ。
- 頂点データを見た目に影響しない精度で詰め直し、圧縮しやすい値にそろえて、262MBから218MBへ。
- 「圧縮したほうが小さい」と決めつけず、形式ごとに実測する。ポスターは無圧縮のほうが小さくなりました。
- データを細かく調べたおかげで、64.5%の頂点で法線が壊れていた不具合も見つかりました。
ダウンロードが3分の1以下になったので、以前より気軽に入れるようになったと思います。光の中をゆっくり漂うクラゲを見に来てください。気づいたことがあれば、この記事のコメントで教えてください。
▶ 海月の回廊 - Jellyfish Observatory をVRChatで開く
関連: 長岡花火を3Dで鑑賞 / 奥只見湖を3Dで巡る / 上高地・河童橋を3D散策 / 京都・渡月橋を3Dで再現 / 新潟レインボータワーを3D・VRで再現
制作環境・クレジット
Unity 2022.3.22f1、VRChat SDK(Worlds)、UdonSharp。ライトマップはBakeryで作成。フォントはNoto Sans CJK JP(SIL Open Font License)を、使用文字に絞り込んで使っています。
クラゲのモデルとアニメーションは、この水族館のために作成しました。形は公開されている写真や資料からの推定を含み、学術的な種の同定や、実物の精密な再現を保証するものではありません。この記事の画像は、すべてUnityエディターで描いたワールドの画面です。
コメント(0件)
まだコメントはありません。最初のコメントを投稿してください!