Meshopt frente a Draco: dos filosofías de compresión
Cómo funciona EXT_meshopt_compression con cuantización más filtros de bytes, por qué su decodificador cabe en 15 KB, y el criterio para elegir entre los dos compresores.
Cómo funciona meshopt
EXT_meshopt_compression viene de la biblioteca meshoptimizer, de Arseny Kapoulkine, y su diseño parte de una premisa distinta: la geometría, después de cuantizar, es un flujo de bytes con mucha estructura, y esa estructura se puede explotar con transformaciones baratísimas.
Etapa uno: cuantización. Igual que Draco. Posiciones a enteros de 14 o 16 bits, normales a 8 o 10, UVs a 12. Se apoya en KHR_mesh_quantization, y la conversión de vuelta a float la hace el hardware al leer el atributo, sin coste alguno. Esta etapa por sí sola reduce entre 2 y 3 veces.
Etapa dos: codificación de bytes. Los datos cuantizados pasan por dos codificadores muy simples. El vertex codec explota que los vértices vecinos tienen valores parecidos: guarda diferencias y agrupa los bytes por posición dentro del valor, de forma que los bytes altos, que casi siempre son iguales, se comprimen a casi nada. El index codec explota que los triángulos consecutivos comparten vértices y codifica la conectividad con un esquema de bytes.
Ninguna de las dos etapas hace codificación de entropía real ni recorridos de grafo. Ese es el motivo de todo lo demás.
Las consecuencias
El decodificador es diminuto. meshopt_decoder.module.js con su WASM ronda los 15 KB. El de Draco son unos 300 KB entre el wrapper y el .wasm. Sobre un modelo de 500 KB, esa diferencia de 285 KB se come buena parte de la ventaja de compresión de Draco.
La descompresión es rapidísima. El decodificador de meshopt procesa del orden de mil millones de bytes por segundo. En cifras comparables a las de la lección anterior:
| Vértices | Draco | Meshopt |
|---|---|---|
| 10 000 | ~8 ms | ~0.5 ms |
| 100 000 | ~60 ms | ~4 ms |
| 1 000 000 | ~600 ms | ~35 ms |
No reordena los vértices. Meshopt cuantiza y codifica en el orden que le llegue, así que preserva los índices. Y como gltfpack reordena primero para la caché post-transformada de la GPU, el modelo comprimido llega ya optimizado para dibujarse rápido, no solo para descargarse pequeño.
Se puede combinar con compresión de transporte. El flujo de bytes que produce meshopt tiene todavía redundancia que gzip o Brotli aprovechan. Un .glb con meshopt servido con Brotli se acerca bastante al tamaño de Draco, y esa compresión la hace el servidor y la deshace el navegador en código nativo, gratis. Draco, en cambio, ya ha exprimido la entropía y apenas se comprime más.
La comparación completa
Sobre un modelo de arquitectura de 180 000 triángulos con geometría desnuda de 12 MB:
| Sin comprimir | Draco | Meshopt | Meshopt + Brotli | |
|---|---|---|---|---|
| Geometría en disco | 12.0 MB | 1.1 MB | 2.4 MB | 1.5 MB |
| Decodificador a descargar | 0 | 300 KB | 15 KB | 15 KB |
| Total primera visita | 12.0 MB | 1.4 MB | 2.4 MB | 1.5 MB |
| Tiempo de descompresión | 0 | ~110 ms | ~7 ms | ~7 ms |
| Preserva orden de vértices | sí | no | sí | sí |
Con Brotli activado en el servidor —que deberías tener de todas formas— los tamaños quedan prácticamente empatados y meshopt gana en tiempo por quince a uno. Sin Brotli, Draco gana en tamaño por casi el doble.
El criterio de elección
Usa meshopt cuando tienes Brotli o gzip en el servidor, que es el caso normal; cuando la escena tiene muchas mallas y quieres que aparezcan rápido; cuando vas a manipular la geometría por código y necesitas índices estables; o cuando el objetivo es móvil, donde el tiempo de CPU es el recurso escaso.
Usa Draco cuando el modelo es muy grande —por encima de veinte megabytes de geometría—, cuando no controlas la compresión del servidor, o cuando el ancho de banda del usuario es el cuello de botella dominante y el tiempo de CPU no importa. El caso canónico es un escaneado 3D de alta densidad que se carga una vez.
No comprimas cuando la geometría total baja de unos 300 KB. El decodificador y la latencia extra no se amortizan.
Y hay una combinación que no existe: no se pueden usar los dos a la vez sobre la misma malla. Son extensiones mutuamente excluyentes en la codificación de los bufferViews. Sí puedes, en cambio, combinar cualquiera de las dos con KTX2 para las texturas, y de hecho es lo normal.
Los comandos
Con gltf-transform:
# Meshopt, con cuantizacion por defectogltf-transform meshopt modelo.glb salida.glb --level high# Dracogltf-transform draco modelo.glb salida.glb \ --method edgebreaker \ --quantize-position 14 --quantize-normal 10 --quantize-texcoord 12Con gltfpack, que es la herramienta nativa de meshoptimizer y se instala con npm install --global gltfpack:
# -c aplica meshopt; -cc usa el modo de mayor compresiongltfpack -i entrada.gltf -o salida.glb -cc# Anadiendo compresion de texturas a KTX2 en la misma pasadagltfpack -i entrada.gltf -o salida.glb -cc -tcEsos son exactamente los flags que el ejemplo oficial webgl_loader_gltf_compressed documenta en su código para generar coffeemat.glb: “gltfpack -i coffeemat/scene.gltf -o coffeemat.glb -cc -tc”, con el resultado usando EXT_meshopt_compression para geometría y KHR_texture_basisu para texturas.