Afinando um Modelo, Lição Um: As Três Armas da Compressão de Vértices
No artigo anterior, abrimos um arquivo GLB e vimos que as texturas consomem 80% do tamanho, enquanto os vértices ocupam apenas 10-20%. Então a compressão de vértices é irrelevante?
Bem pelo contrário. Quando as texturas de um modelo já estão comprimidas em KTX2 e os vértices são densos, os 20% restantes são vértices—e esses 20% podem ser cortados pela metade, ou até 90%. Mais importante ainda, a compressão de vértices é uma das poucas otimizações que é quase gratuita e funciona instantaneamente: adicione alguns comandos, troque um decoder e o arquivo fica mais leve.
Este artigo esclarece três pontos: como os dados de vértices realmente se parecem; o temperamento de cada uma das três abordagens (quantização, MeshOpt, Draco); e uma conclusão que o salvará de armadilhas—não existe solução "melhor", apenas a solução "mais adequada".
Quão grande é um vértice
Primeiro, o que há dentro de um vértice. No glTF, cada vértice é composto por vários atributos:
| Atributo | Função | Precisão padrão | Bytes por vértice |
|---|---|---|---|
| position | Coordenadas do vértice no espaço | 3 × float32 | 12 |
| normal | Determina a direção da iluminação | 3 × float32 | 12 |
| tangent | Cálculo do normal-map | 4 × float32 | 16 |
| texcoord_0 (UV) | Coordenadas de amostragem de textura | 2 × float32 | 8 |
| color (vertex color) | Sombreamento por vértice | 4 × float32 | 16 |
Um vértice com o conjunto completo de atributos PBR ocupa 48-64 bytes apenas para dados geométricos. Um modelo com 100.000 vértices tem 5-6MB só em vértices.
Note que quase tudo aqui usa float32 (floats de 32 bits). Esse é o padrão, e também é a superfície de ataque para a compressão de vértices—porque a grande maioria dos atributos simplesmente não precisa de precisão de 32 bits.
Arma um: Quantização
Quantização é o princípio fundamental de toda compressão de vértices; Draco e MeshOpt também a usam internamente.
Quantização (mapear floats de alta precisão para inteiros de baixa precisão) se resume a isto: para o float 3.14159265, lembrar 3.14 é suficiente. Para um conjunto de coordenadas dentro de um espaço, em vez de registrar cada casa decimal com precisão em 32 bits, você usa um inteiro com menor amplitude para representá-lo.
Original: position.x = 1.234567 (float32, 4 bytes)
Quantizado: position.x = 1234 (int16, 2 bytes) + uma escala/deslocamento para restaurar
Antes vs depois da quantização:
| Atributo | bytes float32 | Quantizado (16-bit) | Economia |
|---|---|---|---|
| position | 12 | 6 | 50% |
| normal | 12 | 6 (ou 4, usando int8 + octaedral) | 50-67% |
| tangent | 16 | 4-8 | 50-75% |
| texcoord | 8 | 4 | 50% |
Para aquele vértice de 48-64 bytes, a quantização basicamente o comprime para 16-24 bytes, mais da metade do tamanho.
Quando usar quantização
- Você quer apenas reduzir o tamanho e não precisa da máxima taxa de compressão
- Você quer zero dependências de decoder—um glTF quantizado usa a extensão padrão
KHR_mesh_quantization, suportada nativamente pelos principais engines, sem necessidade de trazer uma biblioteca decoder extra - Sua plataforma alvo é sensível ao tamanho do pacote (por exemplo, Mini Programas do WeChat, onde incluir um decoder Draco custa dezenas de KB)
Quando não usar
- O modelo é minúsculo e o detalhe é o diferencial (por exemplo, peças industriais com precisão milimétrica). A quantização se trai mais em modelos pequenos—texturas podem estar ok, mas um deslocamento de vértice de 0.1mm é visível em close-up.
O custo real da perda de precisão: uma cena de vitrine de joias quantizou um modelo de anel para 16 bits, e a borda metálica mostrou aliasing em close-ups. A causa não foi poucos vértices; a escala no espaço global era pequena demais para inteiros de 16 bits expressarem com precisão suficiente. A correção é reduzir a faixa de quantização (diminuir o bounding box de
position) ou aumentar a profundidade de bits para modelos pequenos.
Arma dois: MeshOpt
MeshOpt é a extensão oficial do glTF EXT_meshopt_compression, posicionada como "compressão razoável, decodificação ultrarrápida."
O que ele faz: primeiro quantiza os atributos (igual ao acima), depois aplica uma técnica chamada codificação entrópica (sem perdas) para recomprimir lossless os inteiros quantizados. Ou seja: quantização com perdas + codificação entrópica sem perdas = tamanho menor, mesma qualidade da quantização isolada.
- Taxa de compressão: outros 30-50% menores que a quantização isolada
- Velocidade de decodificação: extremamente rápida, C/JS puro, dezenas de milhões de vértices por segundo em uma thread
- Tamanho do decoder: minúsculo (~20-30KB gzipped)
- Compatibilidade: suportado nativamente por Three.js e Babylon.js, um padrão web de facto
Quando usar MeshOpt
- Você precisa de uma maior taxa de compressão mas não pode aceitar a decodificação mais lenta do Draco
- Web primeiro, mobile, WebXR—velocidade de decodificação afeta diretamente a experiência do primeiro carregamento
- O modelo é descomprimido frequentemente (por exemplo, níveis carregados dinamicamente)
Quando não usar
- Sua plataforma alvo não reconhece
EXT_meshopt_compression(raro, engines antigas) - Você só precisa que "funcione" e não se preocupa com uma diferença de 30%—nesse caso, a quantização simples é mais fácil e tem uma dependência a menos
Arma três: Draco
Draco é a solução de compressão da Google, posicionada para "taxa máxima de compressão."
A diferença fundamental dos outros dois: Draco altera a conectividade (topologia) dos vértices. A quantização apenas muda a representação numérica de cada vértice; MeshOpt adiciona codificação sem perdas por cima; Draco reorganiza a malha de triângulos e expressa "quais vértices formam triângulos" de forma mais compacta.
- Taxa de compressão: a maior das três, frequentemente redução de 90%+ em modelos com muitos vértices
- Velocidade de decodificação: a mais lenta das três, mas ainda assim rápida em termos absolutos
- Tamanho do decoder: maior (~100-200KB, geralmente carregado como wasm separado)
- Qualidade: ajustável, mas em taxas extremas você verá deformação visível
Quando usar Draco
- Modelos extremamente grandes, com super alta densidade de vértices (escaneamentos com milhões de vértices, terreno)
- Carregamento único, reutilizado por muito tempo após decodificação (decodificação mais lenta é aceitável)
- Tamanho do pacote não é o gargalo, velocidade de download é
Quando não usar
- Mobile + precisa de primeiro carregamento rápido—você tem que baixar tanto o decoder quanto o modelo, o que torna tudo mais lento
- Ambientes com tamanho de pacote rígido, como Mini Programas
- Modelos que precisam de animação esquelética, morph targets—o suporte do Draco para esses é fraco, e configuração incorreta causa problemas
Todos os três lado a lado: uma tabela de seleção
As taxas de compressão abaixo referenciam benchmarks da comunidade (testes do DeepKolos + discussões no Reddit r/threejs). Modelos diferentes variam, mas os relacionamentos relativos são estáveis:
| Opção | Taxa de compressão (vs float32) | Velocidade de decodificação | Tamanho do decoder | Com perdas? | Extensão glTF |
|---|---|---|---|---|---|
| Quantização simples | ~50% | Nativo, sem decodificação | 0 | Sim (precisão) | KHR_mesh_quantization |
| MeshOpt | ~25-35% | Extremamente rápida | ~25KB | Sim (precisão) | EXT_meshopt_compression |
| Draco | ~10-20% | Rápida (mais lenta das três) | ~100-200KB | Sim (precisão + topologia) | KHR_draco_mesh_compression |
Decoders e compatibilidade de plataforma:
| Plataforma | Quantização simples | MeshOpt | Draco |
|---|---|---|---|
| Web desktop | ✅ Nativo | ✅ Nativo | ✅ Precisa de configuração do decoder |
| Web mobile | ✅ Nativo | ✅ Nativo | ⚠️ Decoder é pesado |
| WebXR/VR | ✅ Nativo | ✅ Recomendado | ⚠️ Use com cautela |
| Mini Programa WeChat | ✅ Recomendado | ✅ Recomendado | ❌ Evite se possível |
Resumo em uma linha: quer algo fácil e sem dependências → quantização simples; quer equilíbrio → MeshOpt; quer taxa máxima e pode esperar → Draco.
Na prática: quantização e MeshOpt com gltfpack
gltfpack é a ferramenta oficial do glTF; um único comando cuida da quantização e MeshOpt.
Primeiro instale (binários nos releases do gltfpack):
# Quantize model.glb to 16-bit and add MeshOpt compression
gltfpack -i model.glb -o model-packed.glb -cc
# -cc = compress (adds EXT_meshopt_compression on top of default quantization)
Parâmetros comuns:
# Quantize only, no MeshOpt (lightest, zero decoder dependency)
# gltfpack quantizes vertices to 16-bit (KHR_mesh_quantization) by default,
# so no extra flag is needed
gltfpack -i model.glb -o model-quant.glb
# Quantize and enable MeshOpt
gltfpack -i model.glb -o model-meshopt.glb -cc
# With very many vertices, you can also simplify (reduces vertex count, alters the model)
gltfpack -i model.glb -o model-simplify.glb -cc -si 0.5
# -si 0.5 means simplify to roughly 50% of vertices
Sobre
-cc: é o botão de "comprimir" que aplica adicionalmenteEXT_meshopt_compression. Sem-cc, o gltfpack ainda quantiza por padrão—o que significa quegltfpack -i in.glb -o out.glbpor si só já é "quantização simples, zero dependência de decoder." (-vé a flag de log verboso—não confunda.)
Resultados típicos (um modelo PBR de 5MB com 120k vértices, apenas para referência):
| Tratamento | Tamanho do arquivo | Observações |
|---|---|---|
| Original (float32) | 5.0MB | Linha de base |
| Quantização simples (padrão) | 2.6MB | Metade, sem diferença visível |
MeshOpt (-cc) | 1.7MB | Mais 35% de redução, carregamento um pouco mais rápido |
Nota: a simplificação com
-sié uma operação com perdas que altera a geometria; não é a mesma coisa que compressão. Compressão tenta preservar a fidelidade visual; simplificação ativamente remove detalhes. As duas podem ser combinadas, mas depende se a cena permite.
Armadilhas comuns
- Direção da normal alterada após quantização: geralmente precisão muito baixa. Use pelo menos 16 bits para normais, ou codificação octaedral de 8 bits.
- Materiais perdidos após decodificação Draco: Draco comprime apenas malhas; materiais e texturas devem ser tratados separadamente. Na hora do carregamento, você deve configurar tanto o decoder Draco quanto as extensões KHR.
- Draco não carrega em Mini Programas: o decoder wasm é restrito em alguns runtimes; mudar para MeshOpt geralmente resolve.
- Modelo "desloca" após quantização: quando o modelo está longe da origem, a precisão de 16 bits não consegue expressar coordenadas grandes e detalhes pequenos ao mesmo tempo. A correção é mover o modelo para perto da origem antes de quantizar, ou aumentar a profundidade de bits.
Próximo passo
Vértices comprimidos—não comemore cedo demais. Conforme mencionado, texturas são 80% do tamanho do modelo. A seguir, mudamos de campo e examinamos por que PNG/JPG tradicional é um "voraz de VRAM" aos olhos da GPU, e como os formatos de textura nativos da GPU resolvem isso.