Any3DAny3D
·Any3D Team

KTX2 en pratique : la bonne façon de compresser les textures

3d-compressiontexture-compressionktx2basis-universalgltf

L'article précédent a démêlé les formats de texture GPU, Basis Universal et KTX2. La théorie est claire ; celui-ci est entièrement pratique : comment choisir ETC1S vs UASTC, quels outils, quelles commandes, comment les charger dans un moteur.

Vous pouvez suivre en faisant.

D'abord le choix le plus important : ETC1S ou UASTC

Basis offre deux encodages intermédiaires. Se tromper n'est pas « pas assez bon » — votre normal map se transforme en bouillie. Mémorisez ce tableau :

ETC1SUASTC
Ratio de compressionTrès élevé (type JPG)Moyen (type PNG haute qualité)
QualitéSuffisant pour les color mapsProche de la qualité d'origine
VRAM (après transcodage)Généralement 4bpp (~1/8 de l'original)Généralement 8bpp (~1/4 de l'original)
Vitesse d'encodageLent (niveau réglable)Plus rapide
Utilisationalbedo/diffuse, emissivenormal, metalness-roughness, data maps
Ne pas utilisernormals, images needing des valeurs exactesColor maps (surqualité, taille trop grande)

Pourquoi les normal maps ne peuvent pas utiliser ETC1S ? Parce qu'une normal map stocke des vecteurs de direction, et les canaux RGB de chaque pixel se contraignent mutuellement (longueur du vecteur ≈ 1). ETC1S est une compression par bloc conçue pour que « les couleurs aient l'air justes » ; elle est insensible à la précision d'un seul canal, donc après compression la direction du vecteur dérive et l'éclairage paraît immédiatement faux — surtout les positions de reflets et les détails haute fréquence. UASTC préserve mieux les valeurs numériques et tient cette exigence de précision.

Règles pratiques :

  • Color maps (albedo, emissive) → ETC1S
  • Data maps (normal, roughness, metallic, AO, thickness) → UASTC
  • Incertain et voulant économiser de la taille → essayez d'abord ETC1S ; si un gros plan devient boueux, passez à UASTC

Une même image, quatre formats comparés

Avec un map albedo 2048×2048 comme base (valeurs typiques de communauté, pour référence) :

FormatTaille disqueUtilisation VRAM (avec mipmaps)Upload vers GPUMultiplateforme
PNG~5 Mo~22 MoLent
WebP~1 Mo~22 MoLent
KTX2 (ETC1S)~0.5-0.8 Mo~2.8 MoRapide
KTX2 (UASTC)~3-4 Mo~5.6 MoRapide

Notez que l'utilisation VRAM de WebP est identique à celle de PNG — il n'est petit que sur le disque ; dans le VRAM il se décompresse toujours en pixels bruts. L'ETC1S de KTX2 amène disque et VRAM simultanément très bas — c'est ce qui le rend précieux.

Toolchain : trois chemins mènent au but

Il y a plus d'un outil pour compresser en KTX2, classés par « facilité de prise en main » :

1. toktx (officiel, le plus puissant)

L'outil officiel de Khronos, avec le plus de paramètres, adapté aux textures individuelles.

# Convertir un PNG en KTX2 à encodage ETC1S
toktx --bcmp --uastc 0 albedo.ktx2 albedo.png

# Convertir un PNG en KTX2 à encodage UASTC
toktx --uastc 1 normal.ktx2 normal.png

Paramètres courants :

# ETC1S + niveau de qualité (1-255, défaut 128 ; plus élevé = meilleure qualité et plus grande taille)
toktx --bcmp --uastc 0 --qlevel 200 albedo.ktx2 albedo.png

# UASTC + supercompression (Zstandard, réduit encore la taille disque)
toktx --uastc 1 --zcmp 19 normal.ktx2 normal.png

# Générer les mipmaps automatiquement (fortement recommandé)
toktx --bcmp --genmipmap albedo.ktx2 albedo.png

# Spécifier l'espace colorimétrique sRGB (obligatoire pour les color maps)
toktx --bcmp --srgb albedo.ktx2 albedo.png

--bcmp est le commutateur du mode ETC1S (mode de base de Basis Universal), et --uastc 1 le mode UASTC. Ils s'excluent mutuellement.

2. gltf-transform (le plus facile, fortement recommandé)

Si vous avez un modèle glTF/GLB entier, utilisez gltf-transform pour remplacer toutes ses textures par KTX2 en une commande, choisissant automatiquement ETC1S/UASTC selon l'usage de la texture.

# Installer
npm install -g @gltf-transform/cli

# Compresser tout le modèle d'un coup
gltf-transform optimize model.glb model-optimized.glb \
  --texture-compress basisu

Il juge en interne de l'usage de la texture : types couleur → ETC1S, types données → UASTC, et écrit l'extension KHR_texture_basisu automatiquement. C'est le meilleur choix pour 90 % des cas — pas besoin de taper toktx un par un.

3. Outils en ligne (démarrage le plus rapide)

Quand vous ne voulez pas installer d'environnement, utilisez un outil navigateur : gltf.report (gltf-transform en ligne), KTX2 Converter, etc. Téléverser, télécharger, fini. Bon pour les expériences précoces ou les tâches uniques.

Pipeline complet : de la source à la production

Voici le flux standard (diagramme) :

Fichier source (PNG/JPG/PSD/TGA)
        │
        ├── [modèle entier] gltf-transform optimize model.glb → détecte le type de texture
        │            └─ color maps → ETC1S
        │            └─ data maps → UASTC
        │            └─ écrit l'extension KHR_texture_basisu
        │
        └── [texture unique] toktx → spécifier ETC1S/UASTC + espace colorimétrique + mipmap manuellement
        │
        ▼
KTX2 / GLB compressé
        │
        ▼
Chargement moteur (Three.js / Babylon.js) ── transcodage à l'exécution → format natif GPU

Charger KTX2 dans Three.js

Three.js prend en charge KTX2 nativement depuis r129, mais vous devez fournir un KTX2Loader et configurer le transcodeur (wasm du basis transcoder).

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. Initialiser le transcodeur KTX2 et détecter quel format natif le GPU courant prend en charge
const ktx2Loader = new KTX2Loader()
  .setTranscoderPath("/basis/")            // répertoire du wasm du basis transcoder
  .detectSupport(renderer);                // doit passer le renderer pour détecter les capacités

// 2. Configurer GLTFLoader, en attachant KTX2/Draco/MeshOpt
const gltfLoader = new GLTFLoader();
gltfLoader.setKTX2Loader(ktx2Loader);
gltfLoader.setDRACOLoader(
  new DRACOLoader().setDecoderPath("/draco/")
);
gltfLoader.setMeshoptDecoder(MeshoptDecoder);

// 3. Charger le modèle ; les textures sont transcodées automatiquement
gltfLoader.load("/models/model-optimized.glb", (gltf) => {
  scene.add(gltf.scene);
});

Quelques points clés :

  • detectSupport(renderer) est obligatoire — il décide vers quel format natif transcoder à l'exécution
  • setTranscoderPath pointe vers les fichiers wasm du basis transcoder (copiez-les depuis la version basis_transcoder vers votre répertoire public)
  • Si le modèle utilise aussi Draco, n'oubliez pas d'attacher le DRACOLoader ; s'il utilise MeshOpt, attachez MeshoptDecoder

Charger une seule texture KTX2 :

const texture = await ktx2Loader.loadAsync("/textures/albedo.ktx2");
texture.colorSpace = THREE.SRGBColorSpace;   // mettre les color maps en sRGB
material.map = texture;

Le piège le plus courant est ici : oublier de mettre colorSpace = SRGBColorSpace sur un color map rend toute l'image grisâtre et sombre. Les data maps (normal/roughness) restent en NoColorSpace (linéaire) — ne pas inverser.

Charger KTX2 dans Babylon.js

L'approche de Babylon est similaire mais plus automatique — KHR_texture_basisu est activé par défaut dans GLTFFileLoader, pourvu que les fichiers du transcodeur puissent être trouvés.

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) => {
    // Modèle chargé ; KTX2 transcodé automatiquement
  }
);

Babylon récupère automatiquement le basis transcoder depuis un CDN ; pour les environnements hors ligne/intranet, vous devez configurer manuellement BASISFileLoader.TranscoderModule.

Réglage des paramètres de compression : équilibre qualité et taille

Le paramètre central d'ETC1S est --qlevel (1-255). Son effet sur le résultat :

qlevelTailleQualitéTemps d'encodageUtilisation
128 (défaut)PetiteSuffisanteMoyenLa plupart des cas
200-255Plus grandePresque sans perteLong (plusieurs fois)Exigences de haute qualité
60-100Très petiteBlocs visiblesRapideDistance/textures petites

La taille d'UASTC est relativement fixe ; vous réglez surtout la taille disque avec --zcmp (supercompression Zstandard), qui n'affecte pas le VRAM (toujours 8bpp après décompression).

Ordre de réglage recommandé :

  1. D'abord compresser une fois avec les valeurs par défaut et vérifier taille et qualité
  2. Insatisfait → ajuster --qlevel (ETC1S) ou ajouter --zcmp (UASTC)
  3. Normal map boueuse → confirmer que vous utilisez UASTC, pas ETC1S
  4. Couleurs sombres → vérifier les réglages d'espace colorimétrique (flag sRGB, colorSpace dans le moteur)

Dépannage courant

Échec de transcodage / erreur de chargement

  • Vérifier que le chemin du wasm du transcodeur est correct (Three.js a besoin de setTranscoderPath)
  • Vérifier si la version du moteur prend en charge la version KTX2 courante (les anciens encodages Basis ne sont pas pris en charge par les transcodeurs récents)
  • La console a généralement une erreur précise ; chercher par mot-clé

Couleurs trop sombres / trop claires

  • Un color map (albedo) n'a pas été mis en sRGB, ou a été inversé
  • En toktx omission de --srgb (les color maps en ont besoin)
  • Un data map (normal) a reçu sRGB par erreur

Mipmaps manquants, scintillement au loin

  • La compression n'a pas ajouté --genmipmap
  • Dans le moteur texture.generateMipmaps n'est pas activé (dans Three.js, KTX2 suit le fichier par défaut, mais le minFilter du matériau doit quand même être en mode mipmap)

Direction des normales incorrecte en gros plan

  • Le normal map a utilisé ETC1S ; passer à UASTC
  • Confirmer que le normal map est de style OpenGL (canal vert vers le haut) ; le style DirectX nécessite d'inverser le canal G dans certains moteurs

Le fichier est devenu plus grand

  • Les petites textures (< 128×128) ne valent pas le KTX2 — la compression par bloc a un coût fixe et remplit tout un bloc
  • Ne pas comprimer une texture de couleur unie en KTX2 ; utiliser une valeur de couleur de matériau est moins cher

Étape suivante

Cela règle en gros la compression des textures. Mais « savoir utiliser les outils » n'est pas « utiliser les bons outils » — le prochain article regroupe toute la connaissance des quatre précédents en un cadre de sélection : bureau, mobile, VR, Mini Programs — quelle combinaison exacte selon le scénario.

Nous soutenir