Kris

Primera lección para adelgazar modelos: los tres trucos de compresión de vértices

Compresión 3DCompresión de vérticesMeshOptDracoglTF

En el artículo anterior abrimos un archivo GLB y vimos que las texturas ocupan el 80% del volumen, mientras que los vértices solo el 10-20%. ¿Entonces la compresión de vértices parece irrelevante?

Todo lo contrario. Cuando un modelo ya tiene las texturas comprimidas a KTX2 y los vértices son densos, ese 20% restante son los vértices, y ese 20% se puede reducir a la mitad o incluso en un 90%. Más importante aún: la compresión de vértices es una de las pocas optimizaciones que funcionan casi sin coste y de forma inmediata: añades unas cuantas líneas de comando, cambias un decodificador, y el archivo se vuelve más ligero.

Este artículo aclara tres cosas: cómo son realmente los datos de vértices; la personalidad de la cuantización, MeshOpt y Draco; y una conclusión que te ahorrará dolores de cabeza: no existe la «mejor» solución, solo la «más adecuada» .

¿Cuánto ocupa realmente un vértice?

Primero, veamos qué contiene un vértice. En glTF, cada vértice está compuesto por varios atributos:

AtributoFunciónPrecisión por defectoBytes por vértice
position (posición)Coordenadas del vértice en el espacio3 × float3212
normal (normal)Determina la dirección de la luz3 × float3212
tangent (tangente)Cálculo de mapas de normales4 × float3216
texcoord_0 (UV)Coordenadas de muestreo de textura2 × float328
color (color de vértice)Color a nivel de vértice4 × float3216

Un vértice con atributos PBR completos necesita 48-64 bytes solo de datos geométricos. Un modelo de 100.000 vértices supone 5-6 MB solo de vértices.

Observa que casi todo usa float32 (coma flotante de 32 bits). Esta es la configuración por defecto, y también el punto de entrada para la compresión de vértices, porque la mayoría de los atributos no necesitan 32 bits de precisión.

Primer truco: Cuantización

La cuantización es el principio subyacente de toda compresión de vértices, y tanto Draco como MeshOpt la utilizan internamente.

cuantización (mapear números de coma flotante de alta precisión a enteros de baja precisión) : en esencia, del número 3.14159265 te basta con recordar 3.14. Para un conjunto de coordenadas espaciales, ya no almacenas cada decimal con 32 bits, sino que usas un entero de rango más pequeño.

Original:   position.x = 1.234567   (float32, 4 bytes)
Cuantizado: position.x = 1234       (int16,   2 bytes) + un scale/offset para restaurar

Comparación antes y después de cuantizar:

AtributoBytes float32Después de cuantizar (16 bits)Ahorro
position12650%
normal126 (o 4, con int8 + octaédrica)50-67%
tangent164-850-75%
texcoord8450%

Para el vértice de 48-64 bytes original, tras cuantizar se reduce a unos 16-24 bytes, justo la mitad o más.

¿Cuándo usar cuantización?

  • Solo quieres reducir el tamaño, no necesitas una compresión extrema.
  • Prefieres dependencia cero de decodificadores: el glTF cuantizado usa la extensión estándar KHR_mesh_quantization, soportada de forma nativa por los motores principales, sin necesidad de bibliotecas adicionales.
  • La plataforma objetivo es sensible al tamaño del paquete (por ejemplo, aplicaciones ligeras integradas en ecosistemas como Telegram Mini Apps, donde añadir un decodificador Draco supone decenas de KB).

¿Cuándo evitarla?

  • El modelo es muy pequeño y los detalles son su punto fuerte (por ejemplo, piezas industriales de precisión milimétrica). La cuantización se nota especialmente en modelos pequeños: las texturas pueden aguantar, pero un desplazamiento de 0,1 mm en la posición de un vértice es visible en primeros planos.

Trampa real de pérdida de precisión por cuantización: en una escena de joyería, al cuantizar un anillo a 16 bits, los bordes metálicos presentaban dientes de sierra en primer plano. La causa no era la falta de vértices, sino que el sistema de coordenadas mundial era demasiado pequeño y el rango de 16 bits no era suficiente. La solución: reducir el rango de cuantización (acotar la caja envolvente de position) o usar más bits para modelos pequeños.

Segundo truco: MeshOpt

MeshOpt es la extensión oficial de glTF EXT_meshopt_compression, diseñada para ofrecer «buena compresión y decodificación rapidísima».

Su enfoque: primero cuantiza los atributos (como arriba) y luego aplica una codificación entrópica sin pérdida (LISS) para comprimir aún más los enteros cuantizados. En otras palabras: cuantización con pérdida + codificación entrópica sin pérdida = tamaño más pequeño, misma calidad visual que la cuantización.

  • Ratio de compresión: entre un 30-50% más pequeño que la cuantización simple.
  • Velocidad de decodificación: muy rápida, implementación pura en C/JS, decodifica decenas de millones de vértices por segundo en un solo hilo.
  • Tamaño del decodificador: muy pequeño (~20-30 KB tras gzip).
  • Compatibilidad: soporte nativo en Three.js y Babylon.js, es un estándar de facto en la web.

¿Cuándo usar MeshOpt?

  • Necesitas mayor compresión que la cuantización, pero no puedes aceptar la decodificación más lenta de Draco.
  • Principalmente en web, móvil o WebXR: la velocidad de decodificación afecta directamente a la experiencia de carga inicial.
  • El modelo se descomprime con frecuencia (por ejemplo, niveles cargados dinámicamente).

¿Cuándo evitarlo?

  • Tu plataforma objetivo no reconoce EXT_meshopt_compression (motores antiguos muy específicos).
  • Solo necesitas que «funcione», sin importarte una diferencia del 30%: la cuantización pura es más simple y tiene una dependencia menos.

Tercer truco: Draco

Draco es el esquema de compresión de Google, orientado a «compresión extrema».

La diferencia fundamental con los anteriores: Draco modifica la conectividad de los vértices (topología) . La cuantización solo cambia la representación numérica de cada vértice; MeshOpt añade codificación sin pérdida sobre eso; Draco reorganiza la malla triangular para expresar de forma más compacta «qué vértices forman triángulos».

  • Ratio de compresión: el más alto de los tres, a menudo logra reducciones superiores al 90% en modelos con vértices densos.
  • Velocidad de decodificación: la más lenta de los tres, pero sigue siendo rápida (solo es relativamente más lenta).
  • Tamaño del decodificador: mayor (~100-200 KB, normalmente hay que cargar un wasm aparte).
  • Calidad visual: ajustable, pero con ratios de compresión extremos puede haber deformaciones visibles.

¿Cuándo usar Draco?

  • Modelos enormes, con muchos vértices (millones de vértices en escaneos 3D, terrenos).
  • Carga única, reutilización prolongada tras descomprimir (la decodificación más lenta es aceptable).
  • El cuello de botella no es el tamaño del paquete, sino la velocidad de descarga.

¿Cuándo evitarlo?

  • Móvil + necesidad de carga rápida inicial: tanto el decodificador como el modelo tienen que descargarse, lo que ralentiza.
  • Entornos con restricciones de tamaño de paquete, como Telegram Mini Apps o aplicaciones web progresivas (PWA) muy ligeras.
  • Modelos que requieren animación por esqueleto (skinning) o morph targets: Draco tiene soporte limitado para estos, y una configuración incorrecta puede causar problemas.

Comparativa: una tabla para elegir

Los ratios de compresión siguientes provienen de benchmarks de la comunidad (como análisis en Reddit r/threejs y foros de Three.js en español), pueden variar según el modelo, pero la relación relativa se mantiene estable:

EsquemaRatio de compresión (respecto a float32)Velocidad de decodificaciónTamaño del decodificador¿Con pérdida?Extensión glTF
Cuantización pura~50%Nativo, sin necesidad0Sí (precisión)KHR_mesh_quantization
MeshOpt~25-35%Muy rápida~25 KBSí (precisión)EXT_meshopt_compression
Draco~10-20%Rápida (la más lenta de las tres)~100-200 KBSí (precisión + topología)KHR_draco_mesh_compression

Compatibilidad de plataformas:

PlataformaCuantización puraMeshOptDraco
Web de escritorio✅ Nativo✅ Nativo✅ Requiere decodificador
Web móvil✅ Nativo✅ Nativo⚠️ Decodificador pesado
WebXR / VR✅ Nativo✅ Recomendado⚠️ Precaución
Telegram Mini Apps / PWA✅ Recomendado✅ Recomendado❌ Mejor evitarlo

Resumen en una frase: si quieres simplicidad y cero dependencias → cuantización pura; si quieres equilibrio → MeshOpt; si necesitas compresión extrema y puedes esperar → Draco.

En la práctica: usar gltfpack para cuantizar y aplicar MeshOpt

gltfpack es la herramienta oficial de glTF. Con una línea de comando haces cuantización y MeshOpt.

Instálala (puedes descargar el binario desde gltfpack release):

# Cuantiza model.glb a 16 bits y aplica compresión MeshOpt
gltfpack -i model.glb -o model-packed.glb -cc

# -cc = compress (superpone EXT_meshopt_compression sobre la cuantización por defecto)

Parámetros comunes:

# Solo cuantizar, sin MeshOpt (lo más ligero, sin dependencia de decodificador)
# gltfpack por defecto cuantiza los vértices a 16 bits (KHR_mesh_quantization), sin necesidad de parámetros adicionales
gltfpack -i model.glb -o model-quant.glb

# Cuantizar y aplicar MeshOpt
gltfpack -i model.glb -o model-meshopt.glb -cc

# Cuando hay muchos vértices, se puede simplificar (reduce el
Apóyanos