Any3DAny3D
·Any3D Team

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

3d-compressiontexture-compressionktx2basis-universalgltf

前回の記事で GPU テクスチャ形式、Basis Universal、KTX2 の関係を整理しました。理論は分かった、今回はすべて実践です:ETC1S と UASTC の選び方、どのツールを使うか、コマンドの打ち方、エンジンでの読み込み方。

読みながらそのまま写せます。

まず解決すべき最も重要な選択:ETC1S か UASTC か

Basis は二つの中間エンコードを提供します。間違えると「ちょっと良くない」ではなく、法線マップがそのまま崩れます。まずこの表を覚えてください:

ETC1SUASTC
圧縮率極めて高い(JPG 的)中程度(高品質 PNG 的)
画質カラー系マップなら十分オリジナルに近い品質
VRAM(トランスコード後)通常 4bpp(オリジナルの約 1/8)通常 8bpp(オリジナルの約 1/4)
エンコード速度遅い(レベル調整可)やや速い
適する用途albedo/ディフューズ、emissivenormal、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_basisuGLTFFileLoader でデフォルト有効で、トランスコーダのファイルが見つかることだけ保証すれば 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)。

調整の順序の提案:

  1. まずデフォルトで一度圧縮し、容量と画質を確認
  2. 不満 → --qlevel(ETC1S)を調整、または --zcmp(UASTC)を追加
  3. 法線マップが崩れた → UASTC を使っているか確認、ETC1S でないか
  4. 色が暗い → 色空間の設定を確認(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、ミニプログラム、各シーンでどの組み合わせを使うべきか。

応援する