Kris

KTX2 実践:テクスチャ圧縮の正しい使い方

3D 圧縮テクスチャ圧縮KTX2Basis UniversalglTF

前回は GPU テクスチャフォーマット、Basis Universal、KTX2 の関係を整理しました。理論がわかったところで、今回は実践です。ETC1S と UASTC の選び方、使うツール、コマンドの書き方、エンジンでの読み込み方を解説します。

コピペしてそのまま試せる内容です。実際に手を動かしながら進めてください。

まずは最重要の選択:ETC1S か UASTC か

Basis は 2 種類の中間エンコーディングを提供します。間違った選択をすると「画質が悪い」だけでなく、法線マップが完全にぼやけてしまいます。以下の表を覚えておきましょう。

ETC1SUASTC
圧縮率非常に高い(JPEG に近い)中程度(高品質 PNG に近い)
画質色マップには十分元の品質に近い
メモリ(転码後)通常 4bpp(元の約 1/8)通常 8bpp(元の約 1/4)
エンコード速度遅い(調整可能)比較的速い
適用アルベド/拡散、エミッシブ法線、メタリック-ラフネス、データマップ
非適用法線、正確な数値が必要な画像色マップ(オーバースペック、サイズ大)

なぜ法線マップに ETC1S が使えないのか?法線マップは方向ベクトルを格納しており、各ピクセルの RGB 三チャンネルが相互に制約(ベクトル長 ≈ 1)を持ちます。ETC1S は「色が正しく見える」ように設計されたブロック圧縮で、単一チャンネルの精度に敏感ではありません。圧縮後、ベクトル方向がずれ、ライティングがすぐに不自然になります。特にハイライト部分や高周波詳細で顕著です。UASTC は数値の保持が優れており、こうした精度要求に耐えられます。

実用的なルール

  • 色マップ(アルベド、エミッシブ)→ ETC1S
  • データマップ(法線、ラフネス、メタリック、AO、厚み)→ UASTC
  • 迷ったら、まず ETC1S を試し、拡大してぼやけたら UASTC に変更

同じ画像、4 種類のフォーマット比較

2048×2048 のアルベドマップを基準とした比較(コミュニティの代表値、参考値):

フォーマットディスクサイズメモリ使用量(ミップマップ含む)GPU へのアップロードクロスプラットフォーム
PNG~5MB~22MB遅い
WebP~1MB~22MB遅い
KTX2 (ETC1S)~0.5-0.8MB~2.8MB速い
KTX2 (UASTC)~3-4MB~5.6MB速い

WebP のメモリ使用量は PNG と同じです。ディスクサイズが小さいだけで、VRAM では元のピクセルに展開されます。KTX2 の ETC1S はディスクと VRAM の両方を同時に低減できるため、価値があります。

ツールチェーン:3 つの方法

KTX2 を圧縮するツールは複数あります。「使いやすさ」順に紹介します。

1. toktx(公式、最も強力)

Khronos 公式ツールで、パラメータが最も充実。テクスチャ単体の処理に最適。

# PNG を ETC1S エンコードの KTX2 に変換
toktx --bcmp --uastc 0 albedo.ktx2 albedo.png

# PNG を UASTC エンコードの KTX2 に変換
toktx --uastc 1 normal.ktx2 normal.png

よく使うパラメータ:

# ETC1S + 品質レベル(1-255、デフォルト 128、高いほど画質良くサイズ大)
toktx --bcmp --uastc 0 --qlevel 200 albedo.ktx2 albedo.png

# UASTC + スーパー圧縮(Zstandard、ディスクサイズをさらに削減)
toktx --uastc 1 --zcmp 19 normal.ktx2 normal.png

# ミップマップ自動生成(強く推奨)
toktx --bcmp --genmipmap albedo.ktx2 albedo.png

# sRGB 色空間指定(色マップには必須)
toktx --bcmp --srgb albedo.ktx2 albedo.png

--bcmp は ETC1S モードのスイッチ(Basis Universal の基本モード)、--uastc 1 は UASTC モード。両者は排他です。

2. gltf-transform(最も簡単、強く推奨)

glTF/GLB モデル全体を扱う場合、gltf-transform を使えば 1 つのコマンドで全テクスチャを KTX2 に変換し、テクスチャの用途に応じて自動で ETC1S/UASTC を選択します。

# インストール
npm install -g @gltf-transform/cli

# モデルを一括圧縮
gltf-transform optimize model.glb model-optimized.glb \
  --texture-compress basisu

内部でテクスチャの用途を判断し、色マップに ETC1S、データマップに UASTC を適用し、自動で KHR_texture_basisu 拡張を書き込みます。90% のケースで最適な選択です。個別に toktx を叩く必要はありません。

3. オンラインツール(最も手軽)

環境構築が面倒な場合は、ブラウザベースのツールを利用:gltf.report(オンライン版 gltf-transform)、KTX2 Converter など。アップロード、ダウンロード、完了。初期テストや単発の作業に最適です。

完全なパイプライン:ソースファイルから公開まで

標準的なフローを整理します(フローチャート):

ソースファイル (PNG/JPG/PSD/TGA)
        │
        ├── [モデル全体] gltf-transform optimize model.glb → テクスチャ種別を自動認識
        │            └─ 色マップ → ETC1S
        │            └─ データマップ → UASTC
        │            └─ KHR_texture_basisu 拡張を書き込み
        │
        └── [単一テクスチャ] toktx → 手動で ETC1S/UASTC + 色空間 + ミップマップ指定
        │
        ▼
圧縮後の KTX2 / GLB
        │
        ▼
エンジン読み込み(Three.js / Babylon.js)── ランタイムトランスコード → GPU ネイティブフォーマット

Three.js で KTX2 を読み込む

Three.js は r129 から KTX2 をネイティブサポート。KTX2Loader(basis transcoder wasm パス指定、GPU 能力検出)を GLTFLoader に紐付け、KTX2 テクスチャを含む glTF を読み込むと自動でトランスコードされます。

const ktx2Loader = new KTX2Loader().setTranscoderPath("/basis/").detectSupport(renderer);
gltfLoader.setKTX2Loader(ktx2Loader); // Draco/MeshOpt も併用する場合は DRACOLoader / MeshoptDecoder も忘れずに

detectSupport(renderer) は必須です。これによりランタイムでどのネイティブフォーマットにトランスコードするかが決まります。また、色マップには texture.colorSpace = THREE.SRGBColorSpace を設定してください。設定を忘れると「画像全体が灰色になる」という典型的な落とし穴に陥ります(法線/ラフネスなどのデータマップはリニアのまま)。

Babylon.js で KTX2 を読み込む

Babylon.js はさらに簡単です。GLTFFileLoader はデフォルトで KHR_texture_basisu を有効にしており、CDN から basis transcoder を自動取得するため、KTX2 を含む glTF をそのまま読み込めます。オフライン/社内ネットワーク環境では、手動で BASISFileLoader.TranscoderModule を設定する必要があります。

圧縮パラメータのチューニング:品質とサイズのバランス

ETC1S のコアパラメータは --qlevel(1-255)です。結果への影響:

qlevelサイズ画質エンコード時間適用
128(デフォルト)十分ほとんどの場合
200-255やや大ほぼロスレス長い(数倍)高品質要求
60-100非常に小ブロックノイズあり速い遠景/小テクスチャ

UASTC のサイズは比較的固定で、主に --zcmp(Zstandard スーパー圧縮)でディスクサイズを調整します。VRAM には影響しません(展開後は 8bpp のまま)。

チューニングの推奨順序:

  1. デフォルトパラメータで一度圧縮し、サイズと画質を確認
  2. 不満なら --qlevel(ETC1S)を調整、または --zcmp(UASTC)を追加
  3. 法線マップがぼやける → UASTC を使っているか確認(ETC1S ではない)
  4. 色が暗くなる → 色空間設定を確認(sRGB フラグ、エンジン内の colorSpace)

よくある問題のトラブルシューティング

トランスコード失敗 / 読み込みエラー

  • transcoder wasm のパスが正しいか確認(Three.js では setTranscoderPath
  • エンジンのバージョンが現在の KTX2 バージョンをサポートしているか確認(古い basis エンコードは新しい transcoder でサポートされない)
  • コンソールに具体的なエラーが出ているので、キーワードで検索

色が暗い / 明るい

  • 色マップ(アルベド)に sRGB が設定されていない、または逆設定
  • toktx で --srgb を付け忘れ(色マップには必須)
  • データマップ(法線)に誤って sRGB を設定

ミップマップがなく、遠景でちらつく

  • 圧縮時に --genmipmap を付けなかった
  • エンジン内で texture.generateMipmaps が無効(Three.js では KTX2 はファイルに従うが、マテリアルの minFilter がミップマップモードである必要あり)

拡大時に法線の方向がおかしい

  • 法線マップに ETC1S を使用。UASTC に変更
  • 法線マップが OpenGL スタイル(緑チャンネルが上向き)か確認。DirectX スタイルの場合、一部のエンジンで G チャンネルを反転する必要あり

ファイルサイズが逆に増えた

  • 小さなテクスチャ(< 128×128)には KTX2 は不向き。ブロック圧縮に固定オーバーヘッドがあり、ブロック全体を埋めてしまう
  • 単色テクスチャは KTX2 にせず、マテリアルの色値を使うほうが省サイズ

次のステップ

テクスチャ圧縮の基本はこれでクリアです。しかし「ツールを使える」ことと「適切なツールを使える」ことは別です。次回はこれまでの全知識をまとめ、デスクトップ、モバイル、VR、ミニアプリなど、各シナリオに最適な組み合わせを選定するフレームワークを紹介します。

応援する