KTX2 実践:テクスチャ圧縮の正しい使い方
前回は GPU テクスチャフォーマット、Basis Universal、KTX2 の関係を整理しました。理論がわかったところで、今回は実践です。ETC1S と UASTC の選び方、使うツール、コマンドの書き方、エンジンでの読み込み方を解説します。
コピペしてそのまま試せる内容です。実際に手を動かしながら進めてください。
まずは最重要の選択:ETC1S か UASTC か
Basis は 2 種類の中間エンコーディングを提供します。間違った選択をすると「画質が悪い」だけでなく、法線マップが完全にぼやけてしまいます。以下の表を覚えておきましょう。
| ETC1S | UASTC | |
|---|---|---|
| 圧縮率 | 非常に高い(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 のまま)。
チューニングの推奨順序:
- デフォルトパラメータで一度圧縮し、サイズと画質を確認
- 不満なら
--qlevel(ETC1S)を調整、または--zcmp(UASTC)を追加 - 法線マップがぼやける → UASTC を使っているか確認(ETC1S ではない)
- 色が暗くなる → 色空間設定を確認(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、ミニアプリなど、各シナリオに最適な組み合わせを選定するフレームワークを紹介します。