KTX2 en pratique : le bon usage de la compression de textures
L'article précédent a clarifié les relations entre les formats de textures GPU, Basis Universal et KTX2. La théorie est claire, passons à la pratique : comment choisir entre ETC1S et UASTC, quels outils utiliser, quelles commandes taper, et comment charger le tout dans votre moteur.
Vous pouvez suivre pas à pas, en copiant les commandes au fur et à mesure.
La question cruciale : ETC1S ou UASTC
Basis propose deux encodages intermédiaires. Un mauvais choix ne signifie pas « pas optimal », mais littéralement une normal map floue. Mémorisez ce tableau :
| ETC1S | UASTC | |
|---|---|---|
| Taux de compression | Très élevé (type JPG) | Moyen (type PNG haute qualité) |
| Qualité | Suffisant pour les textures de couleur | Proche de la qualité originale |
| Mémoire GPU (après transcodage) | Généralement 4bpp (≈ 1/8 de l'original) | Généralement 8bpp (≈ 1/4 de l'original) |
| Vitesse d'encodage | Lente (niveau réglable) | Rapide |
| Usage adapté | albedo/diffuse, emissive | normal, metalness-roughness, textures de données |
| Usage inadapté | normal, textures nécessitant des valeurs précises | textures de couleur (surdimensionné, volume plus important) |
Pourquoi ne pas utiliser ETC1S pour les normal maps ? Parce qu'une normal map stocke des vecteurs directionnels : les trois canaux RGB de chaque pixel sont contraints entre eux (longueur du vecteur ≈ 1). ETC1S est une compression par blocs conçue pour « que les couleurs paraissent correctes ». Elle n'est pas sensible à la précision d'un canal unique, et après compression, la direction des vecteurs dévie — l'éclairage devient immédiatement incorrect, surtout sur les reflets spéculaires et les détails haute fréquence. UASTC préserve mieux les valeurs numériques et supporte cette exigence de précision.
Règle pratique :
- Textures de couleur (albedo, emissive) → ETC1S
- Textures de données (normal, roughness, metallic, AO, thickness) → UASTC
- En cas de doute, en privilégiant le volume → essayez d'abord ETC1S, passez à UASTC si les gros plans sont flous
Même image, quatre formats comparés
Prenons une texture albedo 2048×2048 comme référence (valeurs typiques de la communauté, à titre indicatif) :
| Format | Taille disque | Mémoire GPU (avec mipmaps) | Upload GPU | Multi-plateforme |
|---|---|---|---|---|
| PNG | ~5 Mo | ~22 Mo | Lent | ✅ |
| WebP | ~1 Mo | ~22 Mo | Lent | ✅ |
| KTX2 (ETC1S) | ~0,5-0,8 Mo | ~2,8 Mo | Rapide | ✅ |
| KTX2 (UASTC) | ~3-4 Mo | ~5,6 Mo | Rapide | ✅ |
Notez que WebP a la même empreinte mémoire GPU que PNG — il n'est plus léger que sur le disque, mais une fois en mémoire, il est décompressé en pixels bruts. Le KTX2 en ETC1S réduit à la fois le poids disque et la mémoire GPU : c'est là sa vraie valeur.
Outils : trois voies possibles
Plusieurs outils compressent en KTX2, classés par praticité :
1. toktx (officiel, le plus puissant)
L'outil officiel de Khronos, avec le plus de paramètres, idéal pour traiter des textures individuellement.
# Convertir un PNG en KTX2 encodé ETC1S
toktx --bcmp --uastc 0 albedo.ktx2 albedo.png
# Convertir un PNG en KTX2 encodé UASTC
toktx --uastc 1 normal.ktx2 normal.png
Paramètres courants :
# ETC1S + niveau de qualité (1-255, défaut 128, plus haut = meilleure qualité mais plus gros)
toktx --bcmp --uastc 0 --qlevel 200 albedo.ktx2 albedo.png
# UASTC + super-compression (Zstandard, réduit encore le poids disque)
toktx --uastc 1 --zcmp 19 normal.ktx2 normal.png
# Génération automatique des mipmaps (fortement recommandé)
toktx --bcmp --genmipmap albedo.ktx2 albedo.png
# Spécifier l'espace colorimétrique sRGB (obligatoire pour les textures de couleur)
toktx --bcmp --srgb albedo.ktx2 albedo.png
--bcmpactive le mode ETC1S (le mode de base de Basis Universal),--uastc 1active le mode UASTC. Les deux sont mutuellement exclusifs.
2. gltf-transform (le plus simple, fortement recommandé)
Si vous avez un modèle glTF/GLB complet, gltf-transform convertit toutes ses textures en KTX2 en une seule commande, en choisissant automatiquement ETC1S/UASTC selon l'usage de chaque texture.
# Installation
npm install -g @gltf-transform/cli
# Compression du modèle en une commande
gltf-transform optimize model.glb model-optimized.glb \
--texture-compress basisu
Il détecte en interne l'usage des textures : ETC1S pour les couleurs, UASTC pour les données, et écrit automatiquement l'extension KHR_texture_basisu. C'est le meilleur choix dans 90 % des cas — pas besoin de taper toktx manuellement pour chaque texture.
3. Outils en ligne (prise en main immédiate)
Pas envie d'installer un environnement ? Utilisez directement un outil navigateur : gltf.report (version en ligne de gltf-transform), KTX2 Converter, etc. Uploadez, téléchargez, c'est terminé. Idéal pour des essais rapides ou des tâches ponctuelles.
Pipeline complet : du fichier source à la mise en ligne
Voici le flux standard (schéma) :
Fichier source (PNG/JPG/PSD/TGA)
│
├── [Modèle entier] gltf-transform optimize model.glb → détection automatique du type de texture
│ └─ Texture de couleur → ETC1S
│ └─ Texture de données → UASTC
│ └─ Écriture de l'extension KHR_texture_basisu
│
└── [Texture individuelle] toktx → spécification manuelle ETC1S/UASTC + espace colorimétrique + mipmaps
│
▼
KTX2 / GLB compressé
│
▼
Chargement moteur (Three.js / Babylon.js) ── transcodage runtime → format natif GPU
Charger du KTX2 dans Three.js
Three.js supporte nativement KTX2 depuis la r129 : attachez KTX2Loader (en spécifiant le chemin du wasm du transcodeur basis, détection des capacités GPU) au GLTFLoader, puis le chargement d'un glTF contenant des textures KTX2 se transcode automatiquement.
const ktx2Loader = new KTX2Loader().setTranscoderPath("/basis/").detectSupport(renderer);
gltfLoader.setKTX2Loader(ktx2Loader); // Si le modèle utilise aussi Draco/MeshOpt, pensez à attacher DRACOLoader / MeshoptDecoder
detectSupport(renderer) est indispensable — il détermine le format natif de transcodage runtime. De plus, les textures de couleur doivent définir texture.colorSpace = THREE.SRGBColorSpace ; l'oublier, c'est le classique « toute l'image est grise » (les textures de données comme normal/roughness restent en linéaire).
Charger du KTX2 dans Babylon.js
Babylon est plus simple — GLTFFileLoader active KHR_texture_basisu par défaut et récupère automatiquement le transcodeur basis depuis un CDN. Le chargement d'un glTF contenant du KTX2 fonctionne out-of-the-box. En environnement hors-ligne/réseau interne, configurez manuellement BASISFileLoader.TranscoderModule.
Réglage des paramètres de compression : équilibre qualité/volume
Le paramètre clé d'ETC1S est --qlevel (1-255). Son impact :
| qlevel | Volume | Qualité | Temps d'encodage | Usage |
|---|---|---|---|---|
| 128 (défaut) | Petit | Suffisant | Moyen | La plupart des cas |
| 200-255 | Plutôt gros | Quasi sans perte | Long (plusieurs fois) | Exigences qualité élevées |
| 60-100 | Très petit | Artefacts en blocs visibles | Rapide | Plans lointains / petites textures |
Le volume UASTC est relativement fixe ; on ajuste surtout --zcmp (super-compression Zstandard) pour le poids disque, sans impact sur la mémoire GPU (toujours 8bpp après décompression).
Ordre de réglage recommandé :
- Compressez d'abord avec les paramètres par défaut, observez volume et qualité
- Insatisfait → ajustez
--qlevel(ETC1S) ou ajoutez--zcmp(UASTC) - Normal map floue → vérifiez que vous utilisez UASTC, pas ETC1S
- Couleurs trop sombres → vérifiez l'espace colorimétrique (drapeau sRGB, colorSpace du moteur)
Résolution des problèmes courants
Échec de transcodage / erreur de chargement
- Vérifiez le chemin du wasm du transcodeur (Three.js nécessite
setTranscoderPath) - Vérifiez que la version du moteur supporte la version KTX2 actuelle (les anciens encodages basis ne sont pas supportés par les nouveaux transcodeurs)
- La console affiche généralement une erreur précise, cherchez par mots-clés
Couleurs trop sombres / trop claires
- La texture de couleur (albedo) n'a pas de sRGB, ou il est inversé
--srgboublié dans toktx (à ajouter pour les textures de couleur)- sRGB ajouté par erreur sur une texture de données (normal)
Mipmaps manquantes, scintillement au loin
--genmipmapnon ajouté à la compressiontexture.generateMipmapsdésactivé dans le moteur (dans Three.js, KTX2 est fourni avec ses mipmaps, mais leminFilterdu matériau doit utiliser un mode mipmap)
Direction des normales incorrecte en gros plan
- La normal map utilise ETC1S, passez à UASTC
- Vérifiez que la normal map est au style OpenGL (canal vert vers le haut) ; le style DirectX nécessite parfois d'inverser le canal G dans certains moteurs
Le fichier est plus gros qu'avant
- Les petites textures (< 128×128) ne valent pas le coup en KTX2 : la compression par blocs a un coût fixe qui remplit tout le bloc
- Les textures unies ne doivent pas être compressées en KTX2, utilisez directement une valeur de couleur de matériau
Prochaine étape
La compression de textures est maintenant bien maîtrisée. Mais « savoir utiliser l'outil » ne signifie pas « savoir choisir le bon outil » — le prochain article synthétisera toutes les connaissances des 4 précédents dans un framework de sélection : desktop, mobile, VR, mini-apps, quelle combinaison utiliser pour chaque scénario.