Draco: qué comprime, cuánto y a qué precio
Cuantización, predicción por paralelogramo y codificación de entropía, los ratios reales que consigue, el coste de descompresión en workers, y los tres casos en que Draco es contraproducente.
Draco es el compresor de geometría de Google y el que más comprime de los disponibles en glTF. Su ratio típico de diez a uno sobre la malla desnuda hace que un modelo de treinta megabytes quepa en tres, y eso es transformador para una web. La contrapartida no está en el tamaño sino en el tiempo: descomprimir Draco es un algoritmo secuencial y comparativamente lento, y hay escenarios concretos en los que el tiempo que ahorras descargando lo pierdes con creces descomprimiendo.
- Describir las tres etapas del algoritmo de Draco y qué aporta cada una.
- Elegir los niveles de cuantización por atributo sin degradar el modelo visiblemente.
- Estimar el tiempo de descompresión de un modelo a partir de su número de vértices.
- Identificar los tres casos en que Draco no compensa.
Las tres etapas
Cuantización. Los floats de 32 bits se sustituyen por enteros de N bits dentro de la caja envolvente del atributo. Con 14 bits para posiciones, la caja se divide en 16384 pasos por eje: para un objeto de dos metros, un paso es de 0.12 milímetros. Esa sola etapa ya reduce a menos de la mitad, y es la que puede degradar el modelo si se exagera.
Predicción. En vez de guardar cada valor, se guarda su diferencia respecto a una predicción. Para la conectividad, Draco usa el algoritmo Edgebreaker, que recorre la malla codificando cómo se conecta cada triángulo nuevo con el anterior en un alfabeto de cinco símbolos. Para las posiciones, usa la predicción por paralelogramo: dado un triángulo ya decodificado y una arista compartida, el cuarto vértice se predice completando el paralelogramo, y solo se guarda el error respecto a esa predicción. En una malla suave, ese error es minúsculo.
Codificación de entropía. Los residuos, que se agrupan alrededor de cero, se codifican con ANS —asymmetric numeral systems—, que se acerca al límite teórico de Shannon.
El resultado combinado, para geometría típica:
| Contenido | Sin comprimir | Draco | Ratio |
|---|---|---|---|
| Personaje de 50 000 triángulos | 4.2 MB | 380 KB | 11× |
| Escaneado de 500 000 triángulos | 42 MB | 3.6 MB | 12× |
| Arquitectura, muchas caras planas | 8 MB | 1.1 MB | 7× |
| Malla con normales duras y muchas costuras | 6 MB | 1.4 MB | 4× |
La última fila muestra dónde Draco pierde fuelle: cada costura de UV o cada arista dura duplica vértices y rompe la conectividad que Edgebreaker explota.
Los niveles de cuantización
Al comprimir se elige cuántos bits por atributo, y esa es la decisión que puede estropear el modelo. Los valores por defecto y los seguros:
| Atributo | Defecto | Seguro | Qué se rompe si te pasas |
|---|---|---|---|
| Posición | 14 bits | 12 a 16 | facetado visible, grietas entre piezas |
| Normal | 10 bits | 8 a 10 | bandas en el sombreado suave |
| UV | 12 bits | 12 a 14 | texturas que se mueven, costuras |
| Color | 8 bits | 8 | bandas en degradados |
| Genérico | 12 bits | 12 | depende |
La regla que importa: la cuantización de posiciones es relativa a la caja envolvente del modelo entero. Un edificio de cien metros con un pomo de puerta de dos centímetros, cuantizado a 14 bits, da un paso de 6 milímetros: el pomo tiene tres pasos de resolución y se ve como una caja. La solución no es subir bits para todo, es partir el modelo en piezas de escala parecida y comprimirlas por separado.
El segundo síntoma clásico son las grietas entre piezas adyacentes. Dos mallas que compartían un borde exacto se cuantizan cada una a su propia caja envolvente, sus vértices caen en rejillas distintas, y aparece una rendija por la que se ve el fondo. Si tu modelo tiene piezas que deben encajar, o las fusionas antes de comprimir o subes los bits.
El coste de descompresión
Aquí está lo que decide si Draco compensa. La descompresión es secuencial por construcción: Edgebreaker reconstruye la malla triángulo a triángulo, y cada paso depende del anterior. No se paraleliza dentro de una malla.
Los órdenes de magnitud, en un portátil moderno:
| Vértices | Descompresión |
|---|---|
| 10 000 | ~8 ms |
| 100 000 | ~60 ms |
| 1 000 000 | ~600 ms |
En un móvil de gama media, entre dos y cuatro veces más. Un modelo de un millón de vértices puede tardar más de dos segundos solo en descomprimirse.
La buena noticia es que ese trabajo no bloquea el hilo principal. DRACOLoader crea un pool de hasta cuatro Web Workers —workerLimit = 4 por defecto— y reparte las mallas entre ellos. Con un modelo de veinte mallas, cuatro se descomprimen en paralelo y el hilo principal solo recibe los arrays tipados ya listos, transferidos sin copia.
Eso da la regla de reparto: Draco paraleliza entre mallas, no dentro de una malla. Un modelo con una sola malla enorme usa un worker y tarda lo que tarde; el mismo número de triángulos repartido en ocho mallas usa cuatro workers y tarda la mitad. Es un argumento a favor de no fusionar todo en una sola primitiva cuando el modelo es muy pesado.
// Si tu aplicacion hace otras cosas con workers, baja el limite.
draco.setWorkerLimit( 2 );
// Forzar el decodificador de JavaScript en vez del WASM
// solo tiene sentido para depurar: es varias veces mas lento.
draco.setDecoderConfig( { type: 'js' } );
Los tres casos en que Draco no compensa
Modelos pequeños. Por debajo de unos 200 KB de geometría, el ahorro absoluto es de unos 150 KB y el coste fijo de descargar el WASM del decodificador son unos 300 KB. Sales perdiendo en la primera visita. Si el modelo es lo único que carga tu página, el umbral está claramente por encima de medio megabyte.
Muchos modelos pequeños en secuencia. Cada malla paga latencia de worker y el pool tiene un tamaño fijo. Cincuenta modelos de 50 KB comprimidos con Draco tardan más en estar listos que sin comprimir, aunque descarguen menos.
Cuando ya usas KTX2 y el cuello son las texturas. En un modelo típico de producto, las texturas son el 80 o el 90 % del peso. Comprimir la geometría un 90 % de un 15 % del total ahorra un 13 % del peso, mientras que comprimir las texturas ahorra la mayor parte. Si solo vas a hacer una cosa, hazla en las texturas.
EXT_meshopt_compression comprime algo menos —ratios de 4 a 6 en lugar de 8 a 12— pero descomprime entre diez y veinte veces más rápido, con un WASM de unos 15 KB en lugar de 300. Para modelos medianos y para escenas con muchas mallas, esa diferencia suele ganar. Es el tema de la lección de meshopt.
Hay un efecto secundario de Draco que no aparece en ninguna documentación y que muerde en proyectos avanzados. Para que Edgebreaker funcione, el compresor reordena los vértices y los triángulos siguiendo su propio recorrido de la malla. El modelo que sale del decodificador es geométricamente idéntico al que entró, pero el vértice número 500 ya no es el mismo vértice. Eso rompe en silencio cualquier cosa que dependa de índices de vértice estables: un Float32Array de atributos personalizados que hubieras generado por separado y quisieras casar por índice, una tabla de correspondencias entre vértices y algo externo, un sistema de selección que guardaba índices, o los datos de un morph target generado fuera del pipeline. También invalida cualquier optimización previa del orden de índices para la caché post-transformada: Draco impone el suyo, que está optimizado para comprimir, no para dibujar rápido. En la práctica eso significa que el orden de las operaciones importa: si tu pipeline incluye gltf-transform reorder, que optimiza el orden para la caché de la GPU, tiene que ir antes de la compresión y aun así Draco lo va a deshacer parcialmente. Meshopt, por contraste, está diseñado alrededor de la premisa opuesta: su cuantización y su codificación de bytes preservan el orden, y de hecho gltfpack reordena deliberadamente para la caché de vértices y después comprime respetando ese orden. Es una de las razones de fondo por las que meshopt se ha impuesto en pipelines de juego mientras Draco domina en visualización de modelos estáticos: cuando lo único que haces es dibujar la malla tal cual, el reordenado de Draco es invisible; en cuanto tocas la geometría por código, deja de serlo.