Kris

Primeira Lição de Dieta para Modelos 3D: Os Três Golpes da Compressão de Vértices

Compressão 3DCompressão de vérticesMeshOptDracoglTF

No artigo anterior, abrimos um arquivo GLB e vimos que as texturas consomem 80% do volume, enquanto os vértices ocupam apenas 10-20%. Então, será que a compressão de vértices é irrelevante?

Muito pelo contrário. Quando a textura de um modelo já está comprimida com KTX2 e os vértices estão bem densos, os 20% restantes são os vértices — e esses 20% podem ser reduzidos pela metade ou até 90%. Mais importante ainda: a compressão de vértices é uma das poucas otimizações que custam quase zero e têm efeito imediato: adicione alguns comandos, troque um decodificador, e o arquivo emagrece.

Este artigo esclarece três coisas: como os dados de vértice realmente são; as personalidades de Quantização, MeshOpt e Draco; e uma conclusão para você evitar armadilhas — não existe solução "melhor", apenas a "mais adequada" para cada caso.

Qual o Tamanho de um Vértice?

Primeiro, vejamos o que um vértice contém. No glTF, cada vértice é composto por vários atributos:

AtributoFunçãoPrecisão PadrãoBytes por Vértice
position (posição)Coordenadas no espaço3 × float3212
normal (normal)Define a direção da iluminação3 × float3212
tangent (tangente)Cálculo do mapa de normais4 × float3216
texcoord_0 (UV)Coordenadas de amostragem2 × float328
color (cor)Coloração em nível de vértice4 × float3216

Um vértice com todos os atributos PBR completos precisa de 48-64 bytes só para dados geométricos. Um modelo com 100 mil vértices significa 5-6MB apenas em vértices.

Note que quase tudo aqui usa float32 (ponto flutuante de 32 bits). Essa é a configuração padrão e também o ponto de partida para a compressão de vértices — porque a maioria dos atributos não precisa de precisão de 32 bits.

Primeiro Golpe: Quantização

A quantização é o princípio fundamental por trás de toda compressão de vértices; Draco e MeshOpt também a utilizam internamente.

A quantização (mapear números de ponto flutuante de alta precisão para inteiros de baixa precisão) funciona assim: para o número 3.14159265, você só precisa lembrar 3.14. Para um conjunto de coordenadas em um espaço, você não registra cada decimal com 32 bits; em vez disso, usa um inteiro de faixa menor.

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

Comparação antes e depois da quantização:

AtributoBytes float32Após quantização (16 bits)Economia
position12650%
normal126 (ou 4, com int8 + octahedral)50-67%
tangent164-850-75%
texcoord8450%

Para aquele vértice de 48-64 bytes, após a quantização, ele geralmente cai para 16-24 bytes, reduzindo o volume pela metade ou mais.

Quando Usar Quantização

  • Você só quer reduzir o tamanho, sem precisar de taxa de compressão extrema
  • Você quer zero dependência de decodificador — glTF quantizado usa a extensão padrão KHR_mesh_quantization, suportada nativamente pelos principais engines, sem precisar de bibliotecas extras
  • A plataforma alvo é sensível ao tamanho do pacote (como mini-apps, onde um decodificador Draco extra custa dezenas de KB)

Quando Evitar

  • O modelo é muito pequeno e os detalhes são o diferencial (como peças industriais em escala milimétrica). A quantização tende a falhar visivelmente em modelos pequenos — a textura até vai bem, mas um deslocamento de 0,1mm na posição do vértice é perceptível em close-ups.

Armadilha real de perda de precisão na quantização: Em uma cena de exibição de joias, após quantizar o modelo de anel para 16 bits, surgiram serrilhados nas bordas de metal em close-ups. O motivo não era a falta de vértices, mas sim o mundo coordenado pequeno demais — a faixa de 16 bits não era suficiente para expressar detalhes finos. A solução é reduzir a faixa de quantização (diminuir o bounding box do position) ou usar maior profundidade de bits para modelos pequenos.

Segundo Golpe: MeshOpt

O MeshOpt é a extensão oficial do glTF EXT_meshopt_compression, com o objetivo de "boa taxa de compressão e decodificação rápida".

Ele primeiro quantiza os atributos (como acima) e depois usa uma técnica chamada LISS (codificação de entropia sem perdas) para comprimir ainda mais os inteiros quantizados sem perdas. Em outras palavras: quantização com perdas + codificação de entropia sem perdas = tamanho menor, qualidade igual à quantização.

  • Taxa de compressão: 30-50% menor que apenas quantização
  • Velocidade de decodificação: extremamente rápida, implementação pura em C/JS, decodifica dezenas de milhões de vértices por segundo em um único thread
  • Tamanho do decodificador: pequeno (~20-30KB após gzip)
  • Compatibilidade: suporte nativo em Three.js e Babylon.js, é um dos padrões de fato na web

Quando Usar MeshOpt

  • Precisa de maior compressão, mas não aceita a decodificação mais lenta do Draco
  • Foco em web, mobile e WebXR — a velocidade de decodificação impacta diretamente a experiência de carregamento inicial
  • Modelos que precisam ser descomprimidos com frequência (como fases carregadas dinamicamente)

Quando Evitar

  • Sua plataforma alvo não suporta EXT_meshopt_compression (engines antigas e raras)
  • Você só precisa que "funcione" e não se importa com a diferença de 30% — nesse caso, a quantização pura é mais simples e tem uma dependência a menos

Terceiro Golpe: Draco

O Draco é a solução de compressão do Google, focada em "taxa de compressão extrema".

A diferença fundamental entre ele e os outros dois: o Draco altera a conectividade dos vértices (topologia). A quantização apenas muda a representação numérica de cada vértice; o MeshOpt adiciona codificação sem perdas por cima; mas o Draco reorganiza a malha triangular, expressando "quais vértices formam triângulos" de forma mais compacta.

  • Taxa de compressão: a mais alta entre os três; modelos com vértices densos frequentemente alcançam redução de mais de 90%
  • Velocidade de decodificação: a mais lenta entre os três, mas ainda rápida (apenas relativamente mais lenta)
  • Tamanho do decodificador: maior (~100-200KB, geralmente precisa carregar wasm separadamente)
  • Qualidade: ajustável, mas em taxas de compressão extremas pode haver deformações visíveis

Quando Usar Draco

  • Modelos enormes, com vértices ultra densos (modelos escaneados com milhões de vértices, terrenos)
  • Carregamento único, reutilização por longo tempo após descompressão (a decodificação mais lenta é aceitável)
  • O tamanho do pacote não é o gargalo; a velocidade de download é

Quando Evitar

  • Mobile + necessidade de carregamento inicial rápido — o decodificador e o modelo precisam ser baixados, o que acaba atrasando
  • Ambientes rigorosos com tamanho de pacote, como mini-apps
  • Modelos que precisam de animação de esqueleto (skinning) ou morph targets — o Draco tem suporte fraco para isso e pode causar problemas se configurado incorretamente

Comparação Final: Uma Tabela para Escolher

As taxas de compressão abaixo são referências de benchmarks da comunidade (avaliações do DeepKolos no Zhihu + discussões no Reddit r/threejs); podem variar entre modelos, mas a relação relativa é estável:

SoluçãoTaxa de compressão (vs float32)Velocidade de decodificaçãoTamanho do decodificadorCom perdas?Extensão glTF
Quantização pura~50%Nativa, sem decodificação0Sim (precisão)KHR_mesh_quantization
MeshOpt~25-35%Muito rápida~25KBSim (precisão)EXT_meshopt_compression
Draco~10-20%Rápida (mais lenta dos três)~100-200KBSim (precisão + topologia)KHR_draco_mesh_compression

Compatibilidade de decodificador e plataforma:

PlataformaQuantização puraMeshOptDraco
Web Desktop✅ Nativo✅ Nativo✅ Precisa decod.
Web Mobile✅ Nativo✅ Nativo⚠️ Decod. pesado
WebXR/VR✅ Nativo✅ Recomendado⚠️ Cautela
Mini-apps (WeChat)✅ Recomendado✅ Recomendado❌ Evitar

Resumo em uma frase: Para simplicidade e zero dependências → quantização pura; para equilíbrio → MeshOpt; para taxa de compressão extrema e se puder esperar → Draco.

Na Prática: Usando gltfpack para Quantização e MeshOpt

O gltfpack é a ferramenta oficial do glTF; um comando resolve quantização e MeshOpt.

Primeiro, instale (os binários podem ser baixados do gltfpack release):

# Quantiza model.glb para 16 bits e aplica compressão MeshOpt
gltfpack -i model.glb -o model-packed.glb -cc

# -cc = compress (adiciona EXT_meshopt_compression sobre a quantização padrão)

Parâmetros comuns:

# Apenas quantização, sem MeshOpt (mais leve, zero dependência de decodificador)
# O gltfpack já quantiza vértices para 16 bits por padrão (KHR_mesh_quantization), sem parâmetros extras
gltfpack -i model.glb -o model-quant.glb

# Quantização + MeshOpt
gltfpack -i model.glb -o model-meshopt.glb -cc

# Com muitos vértices, pode simplificar (reduz o número de vértices, altera o modelo)
gltfpack -i model.glb -o model-simplify.glb -cc -si 0.5
# -si 0.5 significa simplificar para cerca de 50% dos vértices

Sobre o -cc: é o interruptor "compress", que adiciona EXT_meshopt_compression por cima. Sem -cc, o gltfpack já faz quantização por padrão — ou seja, o comando gltfpack -i in.glb -o out.glb já é "quantização pura, zero dependência de decodificador". (-v é o interruptor de verbose/log detalhado, não confunda.)

Efeito típico (um modelo PBR de 5MB com 120 mil vértices, apenas referência):

ProcessamentoTamanho do arquivoDescrição
Original (float32)5.0MBLinha de base
Quantização pura (padrão)2.6MBRedução pela metade, sem diferença visual significativa
MeshOpt (-cc)1.7MBMais 35% de economia, carregamento um pouco mais rápido

Nota: a simplificação com -si é uma operação com perdas que altera a geometria do modelo, diferente de compressão. A compressão tenta manter a fidelidade visual; a simplificação reduz detalhes ativamente. Ambas podem ser combinadas, mas depende se o cenário permite.

Armadilhas Comuns

  • A direção da normal mudou após a quantização: provavelmente usou precisão muito baixa. Use pelo menos 16 bits para normal, ou codificação octaédrica de 8 bits.
  • Materiais sumiram após decodificar com Draco: o Draco comprime apenas a malha; materiais e texturas precisam ser tratados separadamente. Ao carregar, configure o decodificador Draco e a extensão KHR corretamente.
  • Draco não carrega em mini-apps: o wasm do decodificador pode ter restrições em certos runtimes; trocar para MeshOpt geralmente resolve.
  • Modelo "deriva" após quantização: quando o modelo está muito longe da origem das coordenadas, 16 bits não são suficientes para expressar coordenadas grandes + pequenos detalhes. A solução é mover o modelo para perto da origem antes de quantizar, ou aumentar a profundidade de bits.

Próximos Passos

Vértices comprimidos, mas não comemore ainda — como dissemos, as texturas ocupam 80% do volume do modelo. No próximo artigo, vamos mudar de campo e ver por que PNG/JPG tradicionais são um "devorador de memória" aos olhos da GPU, e como os formatos de textura nativos da GPU resolvem esse problema.

Apoie-nos