Saltar al contenido

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.

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.

JS
// 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.