Blender から公開まで:エンドツーエンド圧縮の実践
シリーズもここまで来て、ツール、原理、選定はすべて扱いました。最後のこの記事では、すべてを走るパイプラインに串刺しします:実在する Blender モデルを出発点に、一歩ずつ圧縮し、各ステップでファイルサイズ、VRAM、読み込み時間の変化を記録し、最終的に 50MB の重量級からスマホで瞬時に開ける 5MB のモデルになれるかを見ます。
対象読者:前 5 編を読み終え、実際に着手しようとしている人。新しい概念は出しません、コピーできるフロー、コマンド、スクリプトだけです。
出発点:実在する PBR モデル
非常に典型的な EC 表示モデルをサンプルにします:高精細な製品モデルに完全な PBR マップ付き。
| 初期指標 | 数値 |
|---|---|
| Blender ソースファイル | ~120MB(書き出さないハイポリ含む) |
| 書き出し GLB(float32 + PNG) | ~50MB |
| 頂点数 | 約 18 万 |
| マップ | 6 枚 4096×4096(albedo、normal、roughness、metallic、AO、emissive) |
| VRAM 使用量(6 枚すべて展開) | ~520MB |
| 目標 | ファイル ≤ 5MB、VRAM 制御可能、モバイルで瞬時に開く |
50MB のファイル、520MB の VRAM──このモデルはモバイルでは確実にクラッシュします。一歩ずつ進めましょう。
ステップ 0:Blender から正しく書き出す
圧縮の最初の関門は実は書き出しで、多くの人がここで血を流します。
Blender で glTF を書き出す際の重要な設定:
- フォーマット:
glTF Binary (.glb)(単一ファイル、転送しやすい) - ジオメトリ:
Normals、Tangentsをチェック(PBR 法線マップには接線が必要) - UV:確実に書き出す(デフォルトでオン)
- テクスチャ:
AutomaticまたはJPEG(ここのテクスチャ形式は問わない、後で再圧縮するが、書き出されていることは必須) - 圧縮:Blender 自前のメッシュ圧縮にはチェックを入れない、より専門的なツールを使う
- トランスフォーム:
+Y Up(glTF 標準) - データ:必要なものだけチェック(アニメーション、カメラ、ライトは不要なら書き出さない、サイズ削減)
書き出し後の model.glb:50MB、6 枚の PNG マップ、float32 頂点。これがベースラインです。
最初のよくある落とし穴はここ:Blender はデフォルトで未使用のメッシュや非表示の補助オブジェクトも一緒に書き出します。書き出し前に
File > Clean Up > Purge Orphansを実行し、アウトライナで書き出すオブジェクトだけを選択してください。
エンドツーエンドパイプライン全景
ライン全体を描いて頭に入れておきましょう:
Blender ソースファイル
│ .glb 書き出し(float32 + PNG) 50MB
▼
[1] 冗長削除 + 重複頂点のウェルド(gltf-transform) ~45MB
│
[2] 頂点圧縮:MeshOpt(gltfpack / gltf-transform) ~30MB
│
[3] テクスチャ圧縮:PNG → KTX2(ETC1S/UASTC) ~6MB
│
[4](任意)ジオメトリ簡略化 LOD(simplify) ~4-5MB
▼
最終 model-final.glb ~5MB
│
エンジン読み込み(Three.js / Babylon.js)→ 実行時トランスコード → 公開
各ステップの数値は下の表でリアルタイムに追跡します。
ツールチェーン:どれを選ぶか
圧縮ツールはいくつかあります、選び間違いを避けるために比較を:
| ツール | 強み | 弱み | 適する用途 |
|---|---|---|---|
| gltf-transform | 全能、テクスチャ+頂点を一本で、API 化・スクリプト化可能 | 極限圧縮率は専用工具に劣る | 主力推奨、圧倒的多数のシーン |
| gltfpack | 頂点圧縮に専門、MeshOpt ネイティブ対応 | テクスチャ圧縮が弱い | 頂点密集、MeshOpt 細制御 |
| toktx | テクスチャ圧縮で最も専門、パラメータ最豊富 | テクスチャしか処理できず、モデル全体は不可 | 単独テクスチャの精密調整 |
| gltf-pipeline | 古参、Draco 対応 | メンテナンス不活発、機能少なめ | 既存の Draco レガシープロジェクト |
| オンラインツール(gltf.report) | ゼロインストール | 自動化・大量処理に不向き | 試行、一回限りのタスク |
メイン推奨:gltf-transform で全フローを走り、必要に応じて gltfpack で頂点を、toktx で単独テクスチャを補う。以下すべてのステップは gltf-transform に基づきます。
ステップ 1:冗長削除 + ウェルド
モデルには重複頂点、未使用ノードやマテリアルがよくあります。まず一度掃除します。
gltf-transform optimize model.glb step1.glb --weld --prune
| ステージ | ファイルサイズ | VRAM | 変化 |
|---|---|---|---|
| ベースライン | 50MB | ~520MB | — |
| Step 1 冗長削除 | 45MB | ~520MB | -5MB(VRAM は不変、テクスチャがまだあるため) |
VRAM はほとんど動きません、想定通りです。冗長削除が主に節約するのは頂点と構造で、テクスチャこそが VRAM の大部分です。
ステップ 2:頂点圧縮 MeshOpt
gltf-transform optimize step1.glb step2.glb --meshopt --weld --prune
--meshopt は頂点を 16 ビットに量子化し MeshOpt の無損失符号化を適用、自動的に EXT_meshopt_compression 拡張を追加します。
| ステージ | ファイルサイズ | VRAM | 変化 |
|---|---|---|---|
| Step 1 | 45MB | ~520MB | — |
| Step 2 + MeshOpt | 30MB | ~520MB | -15MB(頂点部分) |
VRAM はまだ ~520MB?そうです──頂点は VRAM で小さな割合(10-20%)なので、頂点を削っても VRAM への影響は限定的です。真の VRAM の大物はテクスチャ、次で対処します。
ステップ 3:テクスチャ圧縮 PNG → KTX2
このステップは費用対効果の王です。
gltf-transform optimize step2.glb step3.glb \
--texture-compress basisu \
--meshopt --weld --prune
--texture-compress basisu は各マップを自動判定します:カラーマップ(albedo、emissive)は ETC1S、データマップ(normal、roughness、metallic、AO)は UASTC。
| ステージ | ファイルサイズ | VRAM | 変化 |
|---|---|---|---|
| Step 2 | 30MB | ~520MB | — |
| Step 3 + KTX2 | 6MB | ~70MB | -24MB ファイル / -450MB VRAM |
このステップがパイプライン全体の転換点です:
- ファイルが 30MB から 6MB へ下落
- VRAM が 520MB から約 70MB へ下落──6 枚の 4096 マップが「展開後の生ピクセル」から「ブロック圧縮」になり、各枚が ~87MB から ~11-14MB へ
VRAM が一桁下がり、これこそがモバイルで動くかどうかの鍵です。
ステップ 4:(任意)ジオメトリ簡略化
さらに小さくしたい、かつシーンが頂点精度の低下を許すなら、ジオメトリ簡略化を追加します。
gltf-transform optimize step3.glb final.glb \
--texture-compress basisu \
--meshopt \
--simplify --simplify-ratio 0.5 \
--weld --prune
--simplify-ratio 0.5 は約 50% の頂点を残すことを意味します。
| ステージ | ファイルサイズ | VRAM | 変化 |
|---|---|---|---|
| Step 3 | 6MB | ~70MB | — |
| Step 4 + 簡略化 0.5 | 4.5MB | ~70MB | -1.5MB(VRAM はほぼ不変) |
簡略化は主にファイルサイズを節約し、VRAM への影響は小さいです。代償はモデルのディテール低下──至近距離で視認されます。EC 商品ページでは通常過度の簡略化は非推奨、建築/大シーンでは非常に適しています。
効果追跡総表
四つのステップを重ねて全体像を見ます(上記サンプルに基づき、数値は桁の目安):
| ステップ | ファイルサイズ | VRAM | 累計削減率 |
|---|---|---|---|
| ベースライン(float32 + PNG) | 50MB | ~520MB | — |
| + 冗長削除&ウェルド | 45MB | ~520MB | -10% |
| + MeshOpt 頂点 | 30MB | ~520MB | -40% |
| + KTX2 テクスチャ | 6MB | ~70MB | -88% ファイル / -87% VRAM |
| + ジオメトリ簡略化(0.5) | 4.5MB | ~70MB | -91% ファイル |
結論:テクスチャ圧縮が容量と VRAM の収益の圧倒的多数を担う。頂点圧縮は锦上添花、テクスチャ圧縮こそ雪中送炭。これは第 1 篇の論断と完全に一致します──テクスチャが容量の 80% を占め、最適化の収益が最も高い。
一行コマンド版:楽ちん一発圧縮
ステップを追いたくないなら、すべての最適化を一度に:
gltf-transform optimize model.glb model-final.glb \
--texture-compress basisu \
--meshopt \
--simplify --simplify-ratio 0.5 \
--weld --prune
この一本のコマンド = 冗長削除 +ウェルド+頂点 MeshOpt +テクスチャ KTX2 +ジオメトリ簡略化。90% のシーンで十分、ステップ別は主に理解とパラメータ調整のためです。
自動化スクリプト:再利用可能
上記のフローをスクリプトにまとめてビルドに組み込めます。このスクリプトは対象プラットフォーム別に異なるバージョンを出力し、各ステップの効果を表示します。
// scripts/compress-model.mjs
import { optimize } from "@gltf-transform/functions";
import { NodeIO } from "@gltf-transform/core";
import { KHRONOS_EXTENSIONS } from "@gltf-transform/extensions";
import { filesize } from "filesize";
const io = new NodeIO().registerExtensions(KHRONOS_EXTENSIONS);
// プラットフォーム別の圧縮戦略
const PROFILES = {
mobile: {
textureCompression: "basisu",
meshCompression: "meshopt",
simplify: { ratio: 0.5 },
weld: true,
prune: true,
},
vr: {
textureCompression: "basisu",
meshCompression: "meshopt",
// VR の至近距離視聴、簡略化なし
simplify: null,
weld: true,
prune: true,
},
desktop: {
textureCompression: "webp",
meshCompression: "meshopt",
simplify: null,
weld: true,
prune: true,
},
};
async function compress(inputPath, profileName) {
const cfg = PROFILES[profileName];
const doc = await io.read(inputPath);
const before = Buffer.byteLength(await io.writeBinary(doc), "utf8");
await optimize(doc, {
textureCompression: cfg.textureCompression,
meshCompression: cfg.meshCompression,
simplify: cfg.simplify ?? undefined,
weld: cfg.weld,
prune: cfg.prune,
});
const bytes = await io.writeBinary(doc);
const outPath = inputPath.replace(/\.glb$/, `-${profileName}.glb`);
await io.write(outPath, doc);
const after = bytes.byteLength;
console.log(
`${profileName.padEnd(8)} ${filesize(before)} → ${filesize(after)} ` +
`(${Math.round((1 - after / before) * 100)}% smaller) → ${outPath}`
);
}
// 使い方: node scripts/compress-model.mjs path/to/model.glb
const input = process.argv[2];
for (const profile of Object.keys(PROFILES)) {
await compress(input, profile);
}
プロジェクトに組み込む:
node scripts/compress-model.mjs public/models/model.glb
# mobile 50MB → 4.5MB (91% smaller) → model-mobile.glb
# vr 50MB → 6.2MB (87% smaller) → model-vr.glb
# desktop 50MB → 11MB (78% smaller) → model-desktop.glb
フロントエンドは実行時にデバイス別に対応版を読み込むだけです。
このパイプラインを自前で構築したくない?Any3D のオンラインツールが上記すべてを一発で完了──GLB をアップロードするだけで自動的にテクスチャ KTX2 +頂点 MeshOpt を走らせ、プラットフォーム別に圧縮版を出力、ローカルのツールチェーン構築の手間を省けます。
よくある落とし穴 FAQ
圧縮後にモデルが真っ黒 / テクスチャが表示されない
- 99% は色空間:カラーマップの sRGB 設定漏れ。Three.js では
texture.colorSpace = THREE.SRGBColorSpace。 - toktx でカラーマップに
--srgbを忘れた。
法線マップ圧縮後に光照がおかしい
- 法線マップに ETC1S を使った、UASTC に切り替え。
- 法線マップが DirectX 形式(緑チャネルが下向き)、エンジンは OpenGL 形式を要求、G チャネルを反転させる必要。
モバイルで初回描画で止まる
- Draco デコーダ wasm を読み込んでいないか確認(追加リクエスト)。モバイルでは MeshOpt を優先。
- KTX2 トランスコーダのパス設定ミス、トランスコード失敗で CPU 展開にフォールバック。
圧縮後にファイルが逆に大きくなった
- マップが小さすぎる(< 128px)、KTX2 は割に合わない、ブロック圧縮には固定オーバーヘッドがある。
- モデルが既に一度圧縮済み、再圧縮は収益なし(むしろマイナス)。
簡略化後にモデルが割れる
--simplify-ratioが低すぎる、0.7-0.8 に引き上げる。- 簡略化はハードサーフェス(機械、建築)に優しく、有機曲面(キャラクター)は割れやすい。
KTX2 が一部ブラウザで読み込み失敗
- 旧 Safari / 旧 WebView は非対応。PNG/WebP のフォールバックを用意するか、
KHR_texture_basisuのfallbackフィールドでセーフティネットを提供。
シリーズ早見表
全 6 編のエッセンスを一つの表に凝縮、ブックマーク推奨。
容量構成
| 構成 | 割合 | 最適化ツール |
|---|---|---|
| テクスチャマップ | 70-85% | KTX2(最大収益) |
| 頂点データ | 10-20% | MeshOpt / 量子化 / Draco |
| アニメーションデータ | 0-15% | キーフレーム削減 / 圧縮 |
| その他 | < 2% | 冗長削除 |
VRAM 公式
従来形式の VRAM = 幅 * 高さ * 4バイト * 1.333(mipmap含む)
KTX2 ブロック圧縮 VRAM ≈ 上式 / 4 (ETC1S) または / 2 (UASTC)
頂点圧縮の選定
| シナリオ | 推奨 |
|---|---|
| 依存ゼロ、最もシンプル | 純量子化(KHR_mesh_quantization) |
| Web バランスの筆頭 | MeshOpt |
| 極限圧縮率、解凍を待てる | Draco |
| ミニプログラム / パッケージ敏感 | 純量子化 / MeshOpt、Draco は回避 |
テクスチャ圧縮の選定
| マップタイプ | 推奨エンコード |
|---|---|
| albedo / emissive(カラー) | KTX2 ETC1S |
| normal / roughness / metallic / AO(データ) | KTX2 UASTC |
| デスクトップ Web、ダウンロード速度重視 | WebP / AVIF |
| 小マップ(< 128px) | PNG のまま、KTX2 にしない |
一発コマンド
# 全套最適化(テクスチャ +頂点+簡略化)
gltf-transform optimize model.glb model-final.glb \
--texture-compress basisu --meshopt \
--simplify --simplify-ratio 0.5 --weld --prune
プラットフォーム早見
| プラットフォーム | テクスチャ | 頂点 |
|---|---|---|
| デスクトップ Web | WebP / KTX2 | MeshOpt |
| モバイル Web | KTX2 必須 | MeshOpt |
| VR | KTX2 必須 | MeshOpt + LOD |
| ミニプログラム | KTX2 / WebP | MeshOpt / 量子化 |
| 大シーン | KTX2 必須 | MeshOpt + Draco + LOD |
シリーズ振り返り
六編を歩き、串刺しすると一本の完全なチェーンになります:
- なぜこんなに重い:容量構成と VRAM の真実を把握
- 頂点圧縮の三つの武器:量子化、MeshOpt、Draco の原理と選択
- テクスチャの VRAM 問題:PNG/JPG が GPU の目に罪深い理由
- KTX2 実践:ETC1S/UASTC 選定、ツールチェーン、エンジン読み込み
- 選定ガイド:プラットフォーム/シーン別の意思決定フレームワーク
- 本記事:エンドツーエンドパイプライン、Blender から公開まで
コアは一文だけ:まずボトルネック(ダウンロード/VRAM/フレームレート)を考え、それからツールを選ぶ。テクスチャ圧縮の収益が最大、頂点圧縮は锦上添花。プラットフォームが違えば運命も違う、一刀切りしない。
この記事のスクリプトと早見表に従えば、モデルを 50MB から 5MB へ、VRAM を 520MB から 70MB へ持っていくのは再現可能な道のはずです。残るのはただ着手することです。