Any3DAny3D
·Any3D Team

Poner a dieta un modelo, lección 1: las tres armas de la compresión de vértices

3d-compressionvertex-compressionmeshoptdracogltf

En el artículo anterior abrimos un archivo GLB y vimos que las texturas devoran el 80 % del tamaño, mientras que los vértices solo representan el 10-20 %. ¿Es entonces irrelevante la compresión de vértices?

Todo lo contrario. Cuando las texturas de un modelo ya están comprimidas a KTX2 y los vértices son densos, ese 20 % restante son los vértices, y ese 20 % se puede recortar a la mitad, o incluso un 90 %. Más aún, la compresión de vértices es una de las pocas optimizaciones casi gratuitas y de efecto inmediato: unos cuantos comandos, cambiar un decoder, y el archivo adelgaza.

Este artículo aclara tres cosas: cómo es realmente la información de vértices; el temperamento de cada uno de los tres enfoques (cuantización, MeshOpt, Draco); y una conclusión que te ahorrará dolores — no existe la solución «la mejor», solo la «más adecuada».

Qué tamaño tiene un vértice

Primero, qué hay dentro de un vértice. En glTF, cada vértice se compone de varios atributos:

AtributoPropósitoPrecisión por defectoBytes por vértice
position (posición)Coordenadas del vértice en el espacio3 × float3212
normal (normal)Determina la dirección de iluminación3 × float3212
tangent (tangente)Cálculo de normal maps4 × float3216
texcoord_0 (UV)Coordenadas de muestreo de textura2 × float328
color (color de vértice)Sombreado por vértice4 × float3216

Un vértice con el conjunto completo de atributos PBR ocupa 48-64 bytes solo en geometría. Un modelo de 100.000 vértices son 5-6 MB solo en vértices.

Casi todo aquí usa float32 (coma flotante de 32 bits). Es la configuración por defecto y también la superficie de ataque de la compresión de vértices — porque la gran mayoría de atributos no necesitan en absoluto precisión de 32 bits.

Arma uno: Cuantización

La cuantización es el principio subyacente de toda la compresión de vértices; Draco y MeshOpt la usan internamente.

Cuantización (mapear floats de alta precisión a enteros de baja precisión) se resume en: para el float 3.14159265, recordar 3.14 basta. Para un conjunto de coordenadas en un espacio, en lugar de registrar con precisión cada decimal en 32 bits, se usa un entero de rango menor para representarlo.

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

Antes vs. después:

Atributobytes float32Cuantizado (16 bits)Ahorro
position12650 %
normal126 (o 4, con int8 + octahedral)50-67 %
tangent164-850-75 %
texcoord8450 %

Para ese vértice de 48-64 bytes, la cuantización lo comprime básicamente a 16-24 bytes, más que reduciéndolo a la mitad.

Cuándo usar cuantización

  • Solo quieres reducir tamaño y no necesitas la máxima razón de compresión
  • Quieres cero dependencias de decoder — un glTF cuantizado usa la extensión estándar KHR_mesh_quantization, soportada nativamente por los motores principales, sin necesitar una librería de decoder extra
  • Tu plataforma objetivo es sensible al tamaño del paquete (p. ej. WeChat Mini Programs, donde incluir un decoder de Draco cuesta decenas de KB)

Cuándo no usarla

  • El modelo es diminuto y el detalle es el argumento de venta (p. ej. piezas industriales a nivel milimétrico). La cuantización se delata sobre todo en modelos pequeños — las texturas pueden aguantar, pero un desplazamiento de vértice de 0,1 mm es visible en un close-up.

El coste real de la pérdida de precisión: en una escena de joyería, un modelo de anillo se cuantizó a 16 bits y el borde metálico mostró aliasing en close-ups. La causa no eran pocos vértices; el espacio mundo era demasiado pequeño para que los enteros de 16 bits lo expresaran con suficiente finura. La solución es reducir el rango de cuantización (achicar el bounding box de position) o subir la profundidad de bits en modelos pequeños.

Arma dos: MeshOpt

MeshOpt es la extensión oficial de glTF EXT_meshopt_compression, posicionada como «razón de compresión decente, decodificación ultrarrápida».

Lo que hace: primero cuantiza los atributos (igual que arriba), luego aplica una técnica llamada codificación de entropía (lossless, sin pérdida) para recomprimir sin pérdida los enteros cuantizados. Es decir: cuantización con pérdida + codificación de entropía sin pérdida = menor tamaño, misma calidad que la cuantización sola.

  • Razón de compresión: otro 30-50 % más pequeño que la cuantización sola
  • Velocidad de decodificación: extremadamente rápida, pura C/JS, decenas de millones de vértices por segundo en un hilo
  • Tamaño del decoder: minúsculo (~20-30 KB tras gzip)
  • Compatibilidad: soporte nativo de Three.js y Babylon.js, un estándar de facto en la web

Cuándo usar MeshOpt

  • Necesitas mayor razón de compresión pero no puedes aceptar la decodificación más lenta de Draco
  • Web/móvil/WebXR en el centro — la velocidad de decodificación afecta directamente al first paint
  • El modelo se decodifica con frecuencia (p. ej. niveles cargados dinámicamente)

Cuándo no usarlo

  • Tu plataforma ni siquiera reconoce EXT_meshopt_compression (raro, motores viejos)
  • Solo necesitas «que funcione» y no te importa un 30 % de diferencia — entonces la cuantización pura es más simple y tiene una dependencia menos

Arma tres: Draco

Draco es la solución de compresión de Google, posicionada para «máxima razón de compresión».

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

  • Razón de compresión: la más alta de las tres, a menudo 90 %+ de reducción en modelos densos en vértices
  • Velocidad de decodificación: la más lenta de las tres, pero en términos absolutos sigue siendo rápida
  • Tamaño del decoder: mayor (~100-200 KB, normalmente cargado como wasm aparte)
  • Calidad: ajustable, pero en razones extremas se ve deformación visible

Cuándo usar Draco

  • Modelos extremadamente grandes y superdensos en vértices (scans de millones de vértices, terreno)
  • Carga única, reutilizado mucho tiempo tras la decodificación (decodificación más lenta aceptable)
  • El tamaño del paquete no es el cuello de botella, la velocidad de descarga sí

Cuándo no usarlo

  • Móvil + necesitas un first paint rápido — hay que descargar tanto el decoder como el modelo, lo que frena
  • Entornos estrictos de tamaño como Mini Programs
  • Modelos que necesitan animación esquelética, morph targets — el soporte de Draco para esto es débil y la mala configuración causa problemas

Los tres juntos: una tabla de selección

Las razones de abajo referencian benchmarks de la comunidad (tests de DeepKolos + discusiones en Reddit r/threejs). Los modelos varían, pero las relaciones relativas son estables:

OpciónRazón de compresión (vs float32)Velocidad decodeTamaño decoder¿Con pérdida?Extensión glTF
Cuantización pura~50 %Nativa, sin decode0Sí (precisión)KHR_mesh_quantization
MeshOpt~25-35 %Extremadamente 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

Decoders y compatibilidad de plataforma:

PlataformaCuantización puraMeshOptDraco
Web escritorio✅ Nativa✅ Nativa✅ Necesita config decoder
Web móvil✅ Nativa✅ Nativa⚠️ Decoder pesado
WebXR/VR✅ Nativa✅ Recomendado⚠️ Con cautela
WeChat Mini Program✅ Recomendado✅ Recomendado❌ Evitar si posible

Resumen de una línea: fácil y sin dependencias → cuantización pura; equilibrado → MeshOpt; máxima razón y se puede esperar → Draco.

Práctica: cuantización y MeshOpt con gltfpack

gltfpack es la herramienta oficial de glTF; un comando gestiona cuantización y MeshOpt.

Primero instala (binarios en gltfpack releases):

# Cuantizar model.glb a 16 bits y añadir compresión MeshOpt
gltfpack -i model.glb -o model-packed.glb -cc

# -cc = compress (añade EXT_meshopt_compression sobre la cuantización por defecto)

Parámetros comunes:

# Solo cuantizar, sin MeshOpt (lo más ligero, cero dependencia de decoder)
# gltfpack cuantiza los vértices a 16 bits (KHR_mesh_quantization) por defecto,
# así que no hace falta flag extra
gltfpack -i model.glb -o model-quant.glb

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

# Con muchísimos vértices, puedes también simplificar (reduce el nº de vértices, altera el modelo)
gltfpack -i model.glb -o model-simplify.glb -cc -si 0.5
# -si 0.5 significa simplificar a aproximadamente el 50 % de los vértices

Sobre -cc: es el conmutador «compress» que aplica además EXT_meshopt_compression.Sin -cc, gltfpack cuantiza por defecto — es decir, gltfpack -i in.glb -o out.glb por sí solo ya es «cuantización pura, cero dependencia de decoder». (-v es el flag de log verboso — no los confundas.)

Resultados típicos (un modelo PBR de 5 MB, 120k vértices, solo referencia):

TratamientoTamaño archivoNotas
Original (float32)5.0 MBBase
Cuantización pura (por defecto)2.6 MBReducido a la mitad, sin diferencia visible
MeshOpt (-cc)1.7 MBOtro 35 % menos, carga algo más rápida

Nota: la simplificación con -si es una operación con pérdida que altera la geometría; no es lo mismo que compresión. La compresión intenta preservar la fidelidad visual; la simplificación elimina detalles activamente. Ambos se pueden apilar, pero depende de si la escena lo permite.

Trampas comunes

  • Dirección de la normal cambiada tras cuantizar: normalmente precisión demasiado baja. Usa al menos 16 bits para normales, o codificación octahedral de 8 bits.
  • Materiales perdidos tras decodificar Draco: Draco solo comprime mallas; los materiales y texturas deben tratarse aparte. Al cargar hay que configurar tanto el decoder de Draco como las extensiones KHR.
  • Draco no carga en un Mini Program: el wasm del decoder está restringido en algunos runtimes; cambiar a MeshOpt suele arreglarlo.
  • El modelo «deriva» tras cuantizar: cuando el modelo está lejos del origen, la precisión de 16 bits no puede expresar a la vez coordenadas grandes y detalle pequeño. Solución: mover el modelo cerca del origen antes de cuantizar, o aumentar la profundidad de bits.

Siguiente paso

Los vértices están comprimidos — no celebres demasiado pronto. Como se ha dicho, las texturas son el 80 % del tamaño del modelo. A continuación cambiamos de campo de batalla y vemos por qué el PNG/JPG tradicional es un «tragón de VRAM» a los ojos de la GPU, y cómo los formatos de textura nativos de GPU lo resuelven.

Apóyanos