KTX2 en la práctica: la forma correcta de comprimir texturas
En el artículo anterior aclaramos la relación entre los formatos de textura de GPU, Basis Universal y KTX2. Ahora que entendemos la teoría, este artículo es puramente práctico: cómo elegir entre ETC1S y UASTC, qué herramientas usar, qué comandos ejecutar, y cómo cargar las texturas en los motores.
Puedes seguir los pasos y hacerlo mientras lees.
Primero, la decisión más importante: ¿ETC1S o UASTC?
Basis ofrece dos codificaciones intermedias. Elegir mal no es solo cuestión de «no es tan bueno», sino que los mapas de normales se arruinarán directamente. Memoriza esta tabla:
| ETC1S | UASTC | |
|---|---|---|
| Tasa de compresión | Muy alta (similar a JPG) | Media (similar a PNG de alta calidad) |
| Calidad de imagen | Suficiente para texturas de color | Cercana a la calidad original |
| Memoria de video (post-transcodificación) | Generalmente 4bpp (~1/8 del original) | Generalmente 8bpp (~1/4 del original) |
| Velocidad de codificación | Lenta (nivel ajustable) | Rápida |
| Aplicación | Albedo/difusa, emisiva | Normal, metalness-roughness, mapas de datos |
| No aplicable | Normal, mapas que requieren valores precisos | Mapas de color (matar moscas a cañonazos, tamaño grande) |
¿Por qué no se puede usar ETC1S para mapas de normales? Porque los mapas de normales almacenan vectores de dirección, y los tres canales RGB de cada píxel se restringen mutuamente (longitud del vector ≈ 1). ETC1S es una compresión por bloques diseñada para que «los colores se vean bien». No es sensible a la precisión de un solo canal, y después de la compresión, la dirección del vector se desvía, haciendo que la iluminación se vea incorrecta de inmediato, especialmente en los reflejos especulares y los detalles de alta frecuencia. UASTC conserva mejor los valores numéricos y puede soportar este requisito de precisión.
Regla práctica:
- Mapas de color (albedo, emisiva) → ETC1S
- Mapas de datos (normal, roughness, metallic, AO, thickness) → UASTC
- Si tienes dudas y quieres ahorrar tamaño → prueba primero con ETC1S; si se ve borroso al hacer zoom, cambia a UASTC.
Comparación de cuatro formatos con la misma imagen
Usando una textura de albedo de 2048×2048 como referencia (datos típicos de la comunidad, solo como referencia):
| Formato | Tamaño en disco | Memoria de video (con mipmap) | Subida a GPU | Multiplataforma |
|---|---|---|---|---|
| PNG | ~5MB | ~22MB | Lenta | ✅ |
| WebP | ~1MB | ~22MB | Lenta | ✅ |
| KTX2 (ETC1S) | ~0.5-0.8MB | ~2.8MB | Rápida | ✅ |
| KTX2 (UASTC) | ~3-4MB | ~5.6MB | Rápida | ✅ |
Observa que WebP ocupa la misma memoria de video que PNG: solo es más pequeño en disco, pero al cargarlo en la GPU se descomprime a píxeles originales. KTX2 con ETC1S reduce tanto el disco como la memoria de video al mínimo, y ahí radica su valor.
Herramientas: tres caminos que funcionan
No hay una sola herramienta para comprimir KTX2. Las ordenamos por «comodidad de uso»:
1. toktx (oficial, la más potente)
Herramienta oficial de Khronos, con la mayor cantidad de parámetros, ideal para procesar texturas individuales.
# Convertir PNG a KTX2 con codificación ETC1S
toktx --bcmp --uastc 0 albedo.ktx2 albedo.png
# Convertir PNG a KTX2 con codificación UASTC
toktx --uastc 1 normal.ktx2 normal.png
Parámetros comunes:
# ETC1S + nivel de calidad (1-255, por defecto 128, a mayor valor mayor calidad y tamaño)
toktx --bcmp --uastc 0 --qlevel 200 albedo.ktx2 albedo.png
# UASTC + supercompresión (Zstandard, reduce aún 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 mapas de color)
toktx --bcmp --srgb albedo.ktx2 albedo.png
--bcmpactiva el modo ETC1S (modo base de Basis Universal),--uastc 1activa el modo UASTC. Son mutuamente excluyentes.
2. gltf-transform (el más sencillo, muy recomendado)
Si tienes un modelo glTF/GLB completo, usa gltf-transform con un solo comando para convertir todas las texturas a KTX2, eligiendo automáticamente ETC1S/UASTC según el uso.
# Instalación
npm install -g @gltf-transform/cli
# Comprimir todo el modelo con un solo comando
gltf-transform optimize model.glb model-optimized.glb \
--texture-compress basisu
Internamente, determina el uso de la textura: color → ETC1S, datos → UASTC, y escribe automáticamente la extensión KHR_texture_basisu. Esta es la mejor opción en el 90% de los casos, sin necesidad de ejecutar toktx manualmente para cada textura.
3. Herramientas en línea (la más rápida para empezar)
Cuando no quieras instalar un entorno, usa herramientas en el navegador: gltf.report (versión en línea de gltf-transform), KTX2 Converter, etc. Subes, descargas, y listo. Ideal para pruebas rápidas o tareas puntuales.
Pipeline completo: desde el archivo fuente hasta la publicación
Resumamos el flujo estándar (diagrama de flujo):
Archivo fuente (PNG/JPG/PSD/TGA)
│
├── [Modelo completo] gltf-transform optimize model.glb → identifica automáticamente el tipo de textura
│ └─ Mapas de color → ETC1S
│ └─ Mapas de datos → UASTC
│ └─ Escribe la extensión KHR_texture_basisu
│
└── [Textura individual] toktx → especificar manualmente ETC1S/UASTC + espacio de color + mipmaps
│
▼
KTX2 / GLB comprimidos
│
▼
Carga en el motor (Three.js / Babylon.js) → transcodificación en tiempo de ejecución → formato nativo de GPU
Cargar KTX2 en Three.js
Three.js soporta KTX2 de forma nativa desde r129: asocia KTX2Loader (especificando la ruta del transcoder wasm, detectando la capacidad de la GPU) a GLTFLoader. Luego, al cargar un glTF con texturas KTX2, se transcodifican automáticamente.
const ktx2Loader = new KTX2Loader().setTranscoderPath("/basis/").detectSupport(renderer);
gltfLoader.setKTX2Loader(ktx2Loader); // Si el modelo también usa Draco/MeshOpt, recuerda cargar DRACOLoader / MeshoptDecoder
detectSupport(renderer) es obligatorio: determina a qué formato nativo se transcodificará en tiempo de ejecución. Además, las texturas de color deben tener texture.colorSpace = THREE.SRGBColorSpace. Omitir esto es el clásico error de «la imagen se ve grisácea» (los mapas de datos como normal/roughness deben permanecer lineales).
Cargar KTX2 en Babylon.js
Babylon es aún más sencillo: GLTFFileLoader tiene habilitada por defecto la extensión KHR_texture_basisu y descarga automáticamente el transcoder de Basis desde la CDN. Cargar un glTF con KTX2 funciona sin configuración adicional. En entornos sin conexión/intranet, hay que configurar manualmente BASISFileLoader.TranscoderModule.
Ajuste de parámetros de compresión: equilibrio entre calidad y tamaño
El parámetro clave de ETC1S es --qlevel (1-255). ¿Cómo afecta al resultado?
| qlevel | Tamaño | Calidad | Tiempo de codificación | Aplicación |
|---|---|---|---|---|
| 128 (por defecto) | Pequeño | Suficiente | Medio | La mayoría de los casos |
| 200-255 | Mayor | Casi sin pérdida | Largo (varias veces) | Requisitos de alta calidad |
| 60-100 | Muy pequeño | Bloques visibles | Rápido | Texturas lejanas/pequeñas |
El tamaño de UASTC es relativamente fijo, principalmente se ajusta con --zcmp (supercompresión Zstandard) para reducir el tamaño en disco, sin afectar la memoria de video (después de descomprimir sigue siendo 8bpp).
Orden sugerido para ajustar:
- Primero, comprime con parámetros por defecto y evalúa tamaño y calidad
- Si no es suficiente → ajusta
--qlevel(ETC1S) o añade--zcmp(UASTC) - Si el mapa de normales se ve borroso → confirma que estás usando UASTC, no ETC1S
- Si los colores se ven oscuros → verifica la configuración del espacio de color (bandera sRGB, colorSpace en el motor)
Solución de problemas comunes
Error de transcodificación / error al cargar
- Verifica que la ruta del transcoder wasm sea correcta (en Three.js,
setTranscoderPath) - Verifica que la versión del motor sea compatible con la versión actual de KTX2 (las codificaciones antiguas de Basis no son compatibles con transcodificadores modernos)
- La consola suele mostrar un error específico; busca por palabras clave
Colores más oscuros / más claros
- La textura de color (albedo) no tiene configurado sRGB, o está configurado al revés
- Faltó
--srgbal ejecutar toktx (debe añadirse para texturas de color) - Se añadió sRGB por error a un mapa de datos (normal)
Faltan mipmaps, parpadeo en la distancia
- No se añadió
--genmipmapdurante la compresión - En el motor,
texture.generateMipmapsno está activado (en Three.js, KTX2 lo trae por defecto, pero el material debe tener unminFilterque use mipmaps)
La dirección de las normales es incorrecta al hacer zoom
- El mapa de normales se comprimió con ETC1S; cámbialo a UASTC
- Confirma que el mapa de normales es de estilo OpenGL (canal verde hacia arriba); en algunos motores, los mapas de estilo DirectX requieren invertir el canal G
El archivo resultante es más grande
- Las texturas pequeñas (< 128×128) no se benefician de KTX2; la compresión por bloques tiene un costo fijo que llena todo el bloque
- Las texturas de un solo color no se deben comprimir a KTX2; es mejor usar directamente el valor de color del material
Siguiente paso
Con esto, la compresión de texturas está prácticamente dominada. Pero «saber usar la herramienta» no es lo mismo que «usar la herramienta correcta». En el próximo artículo, resumiremos todo el conocimiento de los 4 artículos anteriores en un marco de selección: escritorio, móvil, VR, mini programas web; para cada escenario, qué combinación usar.