KTX2 en la práctica: cómo usar la compresión de texturas correctamente
El artículo anterior desenredó los formatos de textura de GPU, Basis Universal y KTX2. La teoría está clara; este es todo práctica: cómo elegir ETC1S vs UASTC, qué herramientas, qué comandos, cómo cargarlos en un motor.
Puedes seguirlo y hacerlo mientras lees.
Primero, la elección más importante: ETC1S o UASTC
Basis ofrece dos codificaciones intermedias. Elegir mal no es «no bastante bueno» — tu normal map se vuelve puré. Memoriza esta tabla:
| ETC1S | UASTC | |
|---|---|---|
| Razón de compresión | Muy alta (tipo JPG) | Media (tipo PNG de alta calidad) |
| Calidad | Suficiente para color maps | Casi calidad original |
| VRAM (tras transcode) | Normalmente 4bpp (~1/8 original) | Normalmente 8bpp (~1/4 original) |
| Velocidad de codificación | Lenta (nivel ajustable) | Más rápida |
| Uso | albedo/diffuse, emissive | normal, metalness-roughness, data maps |
| No usar | normals, imágenes que necesitan valores exactos | Color maps (desperdicio, tamaño grande) |
¿Por qué los normal maps no pueden usar ETC1S? Porque un normal map guarda vectores de dirección, y los canales RGB de cada píxel se constriñen mutuamente (longitud del vector ≈ 1). ETC1S es una compresión por bloque diseñada para que «los colores se vean bien»; es insensible a la precisión de un solo canal, así que tras la compresión la dirección del vector se desvía y la iluminación se ve mal de inmediato — sobre todo en posiciones de reflejos y detalle de alta frecuencia. UASTC preserva mejor los valores numéricos y aguanta esa exigencia de precisión.
Reglas prácticas:
- Color maps (albedo, emissive) → ETC1S
- Data maps (normal, roughness, metallic, AO, thickness) → UASTC
- Inseguro y queriendo ahorrar tamaño → prueba ETC1S primero; si un close-up se emborra, cambia a UASTC
Una misma imagen, cuatro formatos comparados
Usando un map albedo de 2048×2048 como base (valores típicos de comunidad, solo referencia):
| Formato | Tamaño en disco | Uso de VRAM (con mipmaps) | Subida a GPU | Multiplataforma |
|---|---|---|---|---|
| PNG | ~5 MB | ~22 MB | Lenta | ✅ |
| WebP | ~1 MB | ~22 MB | Lenta | ✅ |
| KTX2 (ETC1S) | ~0.5-0.8 MB | ~2.8 MB | Rápida | ✅ |
| KTX2 (UASTC) | ~3-4 MB | ~5.6 MB | Rápida | ✅ |
Nota que el uso de VRAM de WebP es igual que el de PNG — solo es pequeño en disco; en el VRAM se descomprime igualmente a píxeles crudos. El ETC1S de KTX2 lleva disco y VRAM a la vez muy bajos — eso es lo valioso.
Toolchain: tres caminos, todos llevan
Hay más de una herramienta para comprimir KTX2, ordenadas por «lo fácil que es cogerlas»:
1. toktx (oficial, el más potente)
La herramienta oficial de Khronos, con los parámetros más completos, adecuada para texturas individuales.
# Convertir PNG a un KTX2 con codificación ETC1S
toktx --bcmp --uastc 0 albedo.ktx2 albedo.png
# Convertir PNG a un KTX2 con codificación UASTC
toktx --uastc 1 normal.ktx2 normal.png
Parámetros comunes:
# ETC1S + nivel de calidad (1-255, por defecto 128; mayor = mejor calidad y mayor tamaño)
toktx --bcmp --uastc 0 --qlevel 200 albedo.ktx2 albedo.png
# UASTC + supercompresión (Zstandard, reduce más el tamaño en disco)
toktx --uastc 1 --zcmp 19 normal.ktx2 normal.png
# Generar mipmaps automáticamente (muy recomendado)
toktx --bcmp --genmipmap albedo.ktx2 albedo.png
# Especificar espacio de color sRGB (obligatorio para color maps)
toktx --bcmp --srgb albedo.ktx2 albedo.png
--bcmpes el conmutador del modo ETC1S (modo base de Basis Universal), y--uastc 1es el modo UASTC. Son mutuamente excluyentes.
2. gltf-transform (lo más cómodo, muy recomendado)
Si tienes un modelo glTF/GLB entero, usa gltf-transform para cambiar todas sus texturas a KTX2 en un comando, eligiendo automáticamente ETC1S/UASTC según el propósito de la textura.
# Instalar
npm install -g @gltf-transform/cli
# Comprimir todo el modelo de golpe
gltf-transform optimize model.glb model-optimized.glb \
--texture-compress basisu
Juzga internamente el propósito de la textura: tipos de color → ETC1S, tipos de datos → UASTC, y escribe la extensión KHR_texture_basisu automáticamente. Es la mejor opción para el 90 % de los casos — sin teclear toktx uno a uno.
3. Herramientas online (inicio más rápido)
Cuando no quieras montar entorno, usa una herramienta del navegador: gltf.report (gltf-transform online), KTX2 Converter, etc. Subir, descargar, listo. Bueno para experimentos tempranos o tareas de una sola vez.
Pipeline completo: del fichero fuente a producción
Aquí el flujo estándar (diagrama):
Fichero fuente (PNG/JPG/PSD/TGA)
│
├── [modelo entero] gltf-transform optimize model.glb → detecta tipo de textura
│ └─ color maps → ETC1S
│ └─ data maps → UASTC
│ └─ escribe la extensión KHR_texture_basisu
│
└── [textura única] toktx → especificar ETC1S/UASTC + espacio color + mipmap manualmente
│
▼
KTX2 / GLB comprimido
│
▼
Carga en motor (Three.js / Babylon.js) ── transcode en runtime → formato nativo de GPU
Cargar KTX2 en Three.js
Three.js soporta KTX2 nativamente desde r129, pero debes proporcionar un KTX2Loader y configurar el transcoder (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. Inicializar el transcoder KTX2 y detectar qué formato nativo soporta la GPU actual
const ktx2Loader = new KTX2Loader()
.setTranscoderPath("/basis/") // directorio del wasm del basis transcoder
.detectSupport(renderer); // hay que pasar renderer para detectar capacidades
// 2. Configurar GLTFLoader, adjuntando KTX2/Draco/MeshOpt
const gltfLoader = new GLTFLoader();
gltfLoader.setKTX2Loader(ktx2Loader);
gltfLoader.setDRACOLoader(
new DRACOLoader().setDecoderPath("/draco/")
);
gltfLoader.setMeshoptDecoder(MeshoptDecoder);
// 3. Cargar el modelo; las texturas se transcodifican automáticamente
gltfLoader.load("/models/model-optimized.glb", (gltf) => {
scene.add(gltf.scene);
});
Puntos clave:
detectSupport(renderer)es obligatorio — decide a qué formato nativo transcodificar en runtimesetTranscoderPathapunta a los archivos wasm del basis transcoder (copia desde el basis_transcoder release a tu directorio public)- Si el modelo también usa Draco, recuerda adjuntar también el DRACOLoader; si usa MeshOpt, adjunta MeshoptDecoder
Cargar una textura KTX2 individual:
const texture = await ktx2Loader.loadAsync("/textures/albedo.ktx2");
texture.colorSpace = THREE.SRGBColorSpace; // poner color maps en sRGB
material.map = texture;
La trampa más común está justo aquí: olvidar poner
colorSpace = SRGBColorSpaceen un color map hace que toda la imagen se vea gris y oscura. Los data maps (normal/roughness) en cambio se quedan enNoColorSpace(lineal) — no los inviertas.
Cargar KTX2 en Babylon.js
El enfoque de Babylon es parecido pero más automático — KHR_texture_basisu está activado por defecto en GLTFFileLoader, siempre que los archivos del transcoder se puedan encontrar.
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) => {
// Modelo cargado; KTX2 transcodificado automáticamente
}
);
Babylon obtiene automáticamente el basis transcoder desde un CDN; en entornos offline/intranet necesitas configurar manualmente BASISFileLoader.TranscoderModule.
Ajuste de parámetros de compresión: equilibrio entre calidad y tamaño
El parámetro central de ETC1S es --qlevel (1-255). Cómo afecta al resultado:
| qlevel | Tamaño | Calidad | Tiempo de codificación | Uso |
|---|---|---|---|---|
| 128 (por defecto) | Pequeño | Suficiente | Medio | La mayoría de casos |
| 200-255 | Más grande | Casi sin pérdida | Largo (varias veces) | Requisitos de alta calidad |
| 60-100 | Muy pequeño | Bloques visibles | Rápido | Lejanía/texturas pequeñas |
El tamaño de UASTC es relativamente fijo; afinas principalmente el tamaño en disco con --zcmp (supercompresión Zstandard), que no afecta al VRAM (tras descomprimir sigue siendo 8bpp).
Orden de ajuste recomendada:
- Primero comprimir una vez con los valores por defecto y revisar tamaño y calidad
- No satisfecho → ajustar
--qlevel(ETC1S) o añadir--zcmp(UASTC) - Normal map emborronado → confirmar que usas UASTC, no ETC1S
- Colores oscuros → revisar los ajustes de espacio de color (flag sRGB, colorSpace en el motor)
Solución de problemas comunes
Fallo de transcode / error de carga
- Comprueba que la ruta del wasm del transcoder es correcta (Three.js necesita
setTranscoderPath) - Comprueba si la versión del motor soporta la versión actual de KTX2 (codificaciones Basis viejas no son soportadas por transcoders nuevos)
- La consola suele tener un error concreto; busca por palabra clave
Colores demasiado oscuros / brillantes
- Un color map (albedo) no se puso en sRGB, o se puso al revés
- En toktx olvidaste
--srgb(los color maps lo necesitan) - A un data map (normal) se le dio sRGB por error
Faltan mipmaps, parpadeo a lo lejos
- Al comprimir no se añadió
--genmipmap - En el motor no se activó
texture.generateMipmaps(en Three.js, KTX2 sigue el archivo por defecto, pero elminFilterdel material aun necesita un modo mipmap)
Dirección de la normal incorrecta en close-up
- El normal map usó ETC1S; cambia a UASTC
- Confirma que el normal map es estilo OpenGL (canal verde arriba); el estilo DirectX necesita invertir el canal G en algunos motores
El archivo se hizo más grande
- Texturas pequeñas (< 128×128) no merecen KTX2 — la compresión por bloque tiene un coste fijo y rellena un bloque entero
- No comprimas a KTX2 una textura de color sólido; usar un valor de color del material es más barato
Siguiente paso
Con eso la compresión de texturas queda básicamente superada. Pero «saber usar las herramientas» no es lo mismo que «usar las herramientas correctas» — el próximo artículo agrupa todo el conocimiento de los cuatro anteriores en un marco de selección: escritorio, móvil, VR, Mini Programs — qué combinación exacta en cada escenario.