KTX2 実践:テクスチャ圧縮の正しい使い方
前回の記事で GPU テクスチャ形式、Basis Universal、KTX2 の関係を整理しました。理論は分かった、今回はすべて実践です:ETC1S と UASTC の選び方、どのツールを使うか、コマンドの打ち方、エンジンでの読み込み方。
読みながらそのまま写せます。
まず解決すべき最も重要な選択:ETC1S か UASTC か
Basis は二つの中間エンコードを提供します。間違えると「ちょっと良くない」ではなく、法線マップがそのまま崩れます。まずこの表を覚えてください:
| ETC1S | UASTC | |
|---|---|---|
| 圧縮率 | 極めて高い(JPG 的) | 中程度(高品質 PNG 的) |
| 画質 | カラー系マップなら十分 | オリジナルに近い品質 |
| VRAM(トランスコード後) | 通常 4bpp(オリジナルの約 1/8) | 通常 8bpp(オリジナルの約 1/4) |
| エンコード速度 | 遅い(レベル調整可) | やや速い |
| 適する用途 | albedo/ディフューズ、emissive | normal、metalness-roughness、データマップ |
| 不向き | normal、精密な数値が必要な画像 | カラーマップ(過剰品質、サイズが大きくなる) |
なぜ法線マップに ETC1S が使えないのか?法線マップは方向ベクトルを格納し、各ピクセルの RGB 三チャネルが互いに拘束し合う(ベクトル長さ ≈ 1)ためです。ETC1S は「色が正しく見える」ように設計されたブロック圧縮で、単一チャネルの精度に鈍感です。圧縮後にベクトルの方向がずれ、光照がすぐに不自然になります──特にハイライトの位置と高周波ディテールで。UASTC は数値をより良く保持し、この精度要求に耐えられます。
実用ルール:
- カラーマップ(albedo、emissive)→ ETC1S
- データマップ(normal、roughness、metallic、AO、thickness)→ UASTC
- 迷って、かつ容量を省きたい → まず ETC1S を試し、接写で崩れたら UASTC に替える
同じ画像を四形式で比べる
2048×2048 の albedo マップをベースに(値はコミュニティの典型値、参考までに):
| 形式 | ディスクサイズ | VRAM 使用量(mipmap 含む) | GPU へのアップロード | クロスプラットフォーム |
|---|---|---|---|---|
| PNG | ~5MB | ~22MB | 遅い | ✅ |
| WebP | ~1MB | ~22MB | 遅い | ✅ |
| KTX2 (ETC1S) | ~0.5-0.8MB | ~2.8MB | 速い | ✅ |
| KTX2 (UASTC) | ~3-4MB | ~5.6MB | 速い | ✅ |
WebP の VRAM 使用量が PNG と同じことに注意──ディスクが小さいだけで、VRAM ではやはり生ピクセルに展開されます。KTX2 の ETC1S はディスクと VRAM を同時に非常に小さくします。それが価値のある理由です。
ツールチェーン:三つの道すべて通じる
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
# mipmap 自動生成(強く推奨)
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 のコマンド一本で中の全テクスチャを 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 + 色空間 + mipmap を手動指定
│
▼
圧縮後の KTX2 / GLB
│
▼
エンジン読み込み(Three.js / Babylon.js)── 実行時トランスコード → GPU ネイティブ形式
Three.js で KTX2 を読み込む
Three.js は r129 から KTX2 をネイティブサポートしますが、KTX2Loader を提供し、トランスコーダ(basis transcoder wasm)を設定する必要があります。
import * as THREE from "three";
import { KTX2Loader } from "three/examples/jsm/loaders/KTX2Loader.js";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader.js";
import { MeshoptDecoder } from "three/examples/jsm/libs/meshopt_decoder.module.js";
// 1. KTX2 トランスコーダを初期化し、現在の GPU がどのネイティブ形式をサポートするか検出
const ktx2Loader = new KTX2Loader()
.setTranscoderPath("/basis/") // basis transcoder wasm のあるディレクトリ
.detectSupport(renderer); // renderer を渡して能力検出が必須
// 2. GLTFLoader を設定、KTX2/Draco/MeshOpt をすべて取り付け
const gltfLoader = new GLTFLoader();
gltfLoader.setKTX2Loader(ktx2Loader);
gltfLoader.setDRACOLoader(
new DRACOLoader().setDecoderPath("/draco/")
);
gltfLoader.setMeshoptDecoder(MeshoptDecoder);
// 3. モデルを読み込み、テクスチャは自動的にトランスコード
gltfLoader.load("/models/model-optimized.glb", (gltf) => {
scene.add(gltf.scene);
});
いくつかのポイント:
detectSupport(renderer)は必須──実行時にどのネイティブ形式へトランスコードするかを決めますsetTranscoderPathは basis transcoder の wasm ファイルを指します(basis_transcoder release から public ディレクトリへコピー)- モデルが同時に Draco を使うなら DRACOLoader も取り付け、MeshOpt を使うなら MeshoptDecoder を取り付けます
単独の KTX2 テクスチャを読み込む:
const texture = await ktx2Loader.loadAsync("/textures/albedo.ktx2");
texture.colorSpace = THREE.SRGBColorSpace; // カラーマップは sRGB に設定
material.map = texture;
最もよくある落とし穴はこのステップ:カラーマップで
colorSpace = SRGBColorSpaceを忘れると、画像全体が灰色っぽく暗くなります。データマップ(normal/roughness)は逆にNoColorSpace(リニア)を維持してください、逆にしないように。
Babylon.js で KTX2 を読み込む
Babylon のやり方は似ていますが、より自動的──KHR_texture_basisu は GLTFFileLoader でデフォルト有効で、トランスコーダのファイルが見つかることだけ保証すれば OK です。
import { Scene } from "@babylonjs/core/scene";
import { Engine } from "@babylonjs/core/Engines/engine";
import { SceneLoader } from "@babylonjs/core/Loading/sceneLoader";
import { GLTFFileLoader } from "@babylonjs/loaders/glTF/glTFFileLoader";
import "@babylonjs/loaders/glTF/2.0/Extensions/KHR_texture_basisu";
const engine = new Engine(canvas);
const scene = new Scene(engine);
SceneLoader.ImportMesh(
"",
"/models/",
"model-optimized.glb",
scene,
(meshes) => {
// モデル読み込み完了、KTX2 は自動的にトランスコード済み
}
);
Babylon は basis transcoder を CDN から自動取得します。オフライン/イントラネット環境では 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)
よくあるトラブル対応
トランスコード失敗 / 読み込みエラー
- トランスコーダ wasm のパスが正しいか確認(Three.js は
setTranscoderPathが必要) - エンジンのバージョンが現在の KTX2 バージョンをサポートしているか確認(古い Basis エンコードは新しいトランスコーダでサポートされない)
- コンソールに具体的なエラーが出ることが多い、キーワードで検索
色が暗すぎる / 明るすぎる
- カラーマップ(albedo)が sRGB に設定されていない、または逆に設定された
- toktx で
--srgbを忘れた(カラーマップには必要) - データマップ(normal)に誤って sRGB を付けた
mipmap が無く、遠景でチラつく
- 圧縮時に
--genmipmapを付けなかった - エンジンで
texture.generateMipmapsが有効でない(Three.js では KTX2 はデフォルトでファイルに従うが、それでもマテリアルのminFilterを mipmap モードにする必要あり)
接写で法線方向がおかしい
- 法線マップに ETC1S を使った、UASTC に切り替え
- 法線マップが OpenGL 形式(緑チャネルが上向き)か確認。DirectX 形式は一部エンジンで G チャネルを反転させる必要あり
ファイルが逆に大きくなった
- 小サイズテクスチャ(< 128×128)に KTX2 は不向き、ブロック圧縮には固定オーバーヘッドがありブロック全体を埋める
- 単色テクスチャは KTX2 圧縮せず、マテリアルのカラー値を使うほうが安い
次のステップ
テクスチャ圧縮はここでほぼクリアです。でも「ツールの使い方が分かる」ことと「正しいツールを使える」ことは別です──次の記事は前 4 編の知識をすべて一つの選定フレームワークにまとめます:デスクトップ、モバイル、VR、ミニプログラム、各シーンでどの組み合わせを使うべきか。