Any3DAny3D
·Any3D Team

Blender から公開まで:エンドツーエンド圧縮の実践

3d-compressionpipelinetexture-compressionvertex-compressiongltf

シリーズもここまで来て、ツール、原理、選定はすべて扱いました。最後のこの記事では、すべてを走るパイプラインに串刺しします:実在する 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)(単一ファイル、転送しやすい)
  • ジオメトリNormalsTangents をチェック(PBR 法線マップには接線が必要)
  • UV:確実に書き出す(デフォルトでオン)
  • テクスチャAutomatic または JPEG(ここのテクスチャ形式は問わない、後で再圧縮するが、書き出されていることは必須)
  • 圧縮:Blender 自前のメッシュ圧縮にはチェックを入れない、より専門的なツールを使う
  • トランスフォーム+Y Up(glTF 標準)
  • データ:必要なものだけチェック(アニメーション、カメラ、ライトは不要なら書き出さない、サイズ削減)

書き出し後の model.glb50MB、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 145MB~520MB
Step 2 + MeshOpt30MB~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 230MB~520MB
Step 3 + KTX26MB~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 36MB~70MB
Step 4 + 簡略化 0.54.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_basisufallback フィールドでセーフティネットを提供。

シリーズ早見表

全 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

プラットフォーム早見

プラットフォームテクスチャ頂点
デスクトップ WebWebP / KTX2MeshOpt
モバイル WebKTX2 必須MeshOpt
VRKTX2 必須MeshOpt + LOD
ミニプログラムKTX2 / WebPMeshOpt / 量子化
大シーンKTX2 必須MeshOpt + Draco + LOD

シリーズ振り返り

六編を歩き、串刺しすると一本の完全なチェーンになります:

  1. なぜこんなに重い:容量構成と VRAM の真実を把握
  2. 頂点圧縮の三つの武器:量子化、MeshOpt、Draco の原理と選択
  3. テクスチャの VRAM 問題:PNG/JPG が GPU の目に罪深い理由
  4. KTX2 実践:ETC1S/UASTC 選定、ツールチェーン、エンジン読み込み
  5. 選定ガイド:プラットフォーム/シーン別の意思決定フレームワーク
  6. 本記事:エンドツーエンドパイプライン、Blender から公開まで

コアは一文だけ:まずボトルネック(ダウンロード/VRAM/フレームレート)を考え、それからツールを選ぶ。テクスチャ圧縮の収益が最大、頂点圧縮は锦上添花。プラットフォームが違えば運命も違う、一刀切りしない。

この記事のスクリプトと早見表に従えば、モデルを 50MB から 5MB へ、VRAM を 520MB から 70MB へ持っていくのは再現可能な道のはずです。残るのはただ着手することです。

応援する