Mettere a dieta un modello, lezione 1: le tre armi della compressione dei vertici
Nell'articolo precedente abbiamo aperto un file GLB e visto che le texture divorano l'80% della dimensione, mentre i vertici rappresentano solo il 10-20%. La compressione dei vertici è quindi irrilevante?
Tutt'altro. Quando le texture di un modello sono già compresse in KTX2 e i vertici sono fitti, il restante 20% sono i vertici — e quel 20% può essere dimezzato, o persino ridotto del 90%. Cosa più importante, la compressione dei vertici è una delle poche ottimizzazioni quasi gratuite e immediatamente efficaci: qualche comando, un cambio di decoder, e il file dimagrisce.
Questo articolo chiarisce tre cose: come sono fatti davvero i dati dei vertici; il temperamento di ciascuno dei tre approcci (quantizzazione, MeshOpt, Draco); e una conclusione che vi risparmierà cadute — non esiste la soluzione «migliore», solo la soluzione «più adatta».
Quanto è grande un vertice
Prima, cosa c'è dentro un vertice. In glTF, ogni vertice è composto da più attributi:
| Attributo | Scopo | Precisione predefinita | Byte per vertice |
|---|---|---|---|
| position (posizione) | Coordinate del vertice nello spazio | 3 × float32 | 12 |
| normal (normale) | Determina la direzione di illuminazione | 3 × float32 | 12 |
| tangent (tangente) | Calcolo delle normal map | 4 × float32 | 16 |
| texcoord_0 (UV) | Coordinate di campionamento texture | 2 × float32 | 8 |
| color (colore vertice) | Ombreggiatura per vertice | 4 × float32 | 16 |
Un vertice con il set completo di attributi PBR occupa 48-64 byte solo per i dati geometrici. Un modello da 100.000 vertici è 5-6 MB solo di vertici.
Quasi tutto qui usa float32 (virgola mobile a 32 bit). È la configurazione predefinita e anche la superficie di attacco della compressione dei vertici — perché la stragrande maggioranza degli attributi non ha affatto bisogno di precisione a 32 bit.
Arma uno: Quantizzazione
La quantizzazione è il principio sottostante di tutta la compressione dei vertici; Draco e MeshOpt la usano internamente.
Quantizzazione (mappare float ad alta precisione verso interi a bassa precisione) si riduce a questo: per il float 3.14159265, ricordare 3.14 basta. Per un insieme di coordinate in uno spazio, invece di registrare a 32 bit ogni decimale con precisione, si usa un intero di range minore.
Originale: position.x = 1.234567 (float32, 4 byte)
Quantizzato: position.x = 1234 (int16, 2 byte) + uno scale/offset per ripristinare
Prima vs. dopo:
| Attributo | byte float32 | Quantizzato (16 bit) | Risparmio |
|---|---|---|---|
| position | 12 | 6 | 50% |
| normal | 12 | 6 (o 4, con int8 + octahedral) | 50-67% |
| tangent | 16 | 4-8 | 50-75% |
| texcoord | 8 | 4 | 50% |
Per quel vertice di 48-64 byte, la quantizzazione lo comprime sostanzialmente a 16-24 byte, più che dimezzandolo.
Quando usare la quantizzazione
- Vuoi solo ridurre le dimensioni e non ti serve il massimo rapporto di compressione
- Vuoi zero dipendenze da decoder — un glTF quantizzato usa l'estensione standard
KHR_mesh_quantization, supportata nativamente dai motori principali, senza libreria decoder extra - La tua piattaforma target è sensibile alla dimensione del pacchetto (es. WeChat Mini Program, dove includere un decoder Draco costa decine di KB)
Quando non usarla
- Il modello è minuscolo e il dettaglio è il punto di vendita (es. componenti industriali al millimetro). La quantizzazione si tradisce soprattutto sui modelli piccoli — le texture possono reggere, ma uno spostamento di vertice di 0,1 mm è visibile in un close-up.
Il vero costo della perdita di precisione: in una scena di gioielleria, un modello di anello è stato quantizzato a 16 bit e il bordo metallico ha mostrato aliasing nei close-up. La causa non erano pochi vertici; lo spazio mondo era troppo piccolo perché gli interi a 16 bit lo esprimessero abbastanza finemente. La soluzione è ridurre il range di quantizzazione (ridurre il bounding box di
position) o aumentare la profondità di bit per i modelli piccoli.
Arma due: MeshOpt
MeshOpt è l'estensione glTF ufficiale EXT_meshopt_compression, posizionata come «buon rapporto di compressione, decodifica fulminea».
Cosa fa: prima quantizza gli attributi (come sopra), poi applica una tecnica chiamata codifica di entropia (lossless, senza perdita) per ricomprimere senza perdita gli interi quantizzati. In altre parole: quantizzazione con perdita + codifica di entropia senza perdita = dimensione minore, qualità identica alla sola quantizzazione.
- Rapporto di compressione: ancora 30-50% più piccolo della sola quantizzazione
- Velocità di decodifica: estremamente veloce, puro C/JS, decine di milioni di vertici al secondo su un thread
- Dimensione del decoder: minuscola (~20-30 KB zippato)
- Compatibilità: supporto nativo di Three.js e Babylon.js, uno standard de facto del web
Quando usare MeshOpt
- Ti serve un rapporto di compressione più alto ma non puoi accettare la decodifica più lenta di Draco
- Web/mobile/WebXR al centro — la velocità di decodifica influisce direttamente sulla prima paint
- Il modello è decodificato di frequente (es. livelli caricati dinamicamente)
Quando non usarlo
- La tua piattaforma target non riconosce nemmeno
EXT_meshopt_compression(raro, motori vecchi) - Ti basta «che giri» e non ti importa del 30% di differenza — allora la quantizzazione pura è più semplice e ha una dipendenza in meno
Arma tre: Draco
Draco è la soluzione di compressione di Google, posizionata per «massimo rapporto di compressione».
La differenza fondamentale con gli altri due: Draco cambia la connettività (topologia) dei vertici. La quantizzazione cambia solo la rappresentazione numerica di ciascun vertice; MeshOpt aggiunge codifica senza perdita; Draco riorganizza la mesh triangolare ed esprime «quali vertici formano triangoli» in modo più compatto.
- Rapporto di compressione: il più alto dei tre, spesso 90%+ di riduzione su modelli densi di vertici
- Velocità di decodifica: la più lenta dei tre, ma in assoluto sempre veloce
- Dimensione del decoder: maggiore (~100-200 KB, di solito caricato come wasm separato)
- Qualità: regolabile, ma a rapporti estremi si vede deformazione
Quando usare Draco
- Modelli estremamente grandi e super-densi di vertici (scansioni da milioni di vertici, terreni)
- Caricamento una tantum, lungo riutilizzo dopo la decodifica (decodifica più lenta accettabile)
- La dimensione del pacchetto non è il collo di bottiglia, la velocità di download sì
Quando non usarlo
- Mobile + prima paint veloce necessaria — bisogna scaricare sia decoder che modello, il che frena
- Ambienti severi sulla dimensione come Mini Program
- Modelli che necessitano di animazione scheletrica, morph target — il supporto di Draco per questi è debole, la cattiva configurazione causa problemi
I tre affiancati: una tabella di selezione
I rapporti di compressione sotto referenziano benchmark di comunità (test di DeepKolos + discussioni Reddit r/threejs). I modelli variano, ma le relazioni relative sono stabili:
| Opzione | Rapporto compressione (vs float32) | Velocità decode | Dim. decoder | Con perdita? | Estensione glTF |
|---|---|---|---|---|---|
| Quantizzazione pura | ~50% | Nativa, nessun decode | 0 | Sì (precisione) | KHR_mesh_quantization |
| MeshOpt | ~25-35% | Estremamente veloce | ~25 KB | Sì (precisione) | EXT_meshopt_compression |
| Draco | ~10-20% | Veloce (la più lenta dei tre) | ~100-200 KB | Sì (precisione + topologia) | KHR_draco_mesh_compression |
Decoder e compatibilità di piattaforma:
| Piattaforma | Quantizzazione pura | MeshOpt | Draco |
|---|---|---|---|
| Web desktop | ✅ Nativo | ✅ Nativo | ✅ Serve config decoder |
| Web mobile | ✅ Nativo | ✅ Nativo | ⚠️ Decoder pesante |
| WebXR/VR | ✅ Nativo | ✅ Consigliato | ⚠️ Con cautela |
| WeChat Mini Program | ✅ Consigliato | ✅ Consigliato | ❌ Evitare se possibile |
In una riga: facile e senza dipendenze → quantizzazione pura; equilibrato → MeshOpt; massimo rapporto e si può attendere → Draco.
Pratica: quantizzazione e MeshOpt con gltfpack
gltfpack è lo strumento glTF ufficiale; un comando gestisce quantizzazione e MeshOpt.
Prima installa (binari dalle release di gltfpack):
# Quantizzare model.glb a 16 bit e aggiungere la compressione MeshOpt
gltfpack -i model.glb -o model-packed.glb -cc
# -cc = compress (aggiunge EXT_meshopt_compression sopra la quantizzazione predefinita)
Parametri comuni:
# Solo quantizzazione, senza MeshOpt (il più leggero, zero dipendenze decoder)
# gltfpack quantizza i vertici a 16 bit (KHR_mesh_quantization) per impostazione predefinita,
# non serve flag extra
gltfpack -i model.glb -o model-quant.glb
# Quantizzare e attivare MeshOpt
gltfpack -i model.glb -o model-meshopt.glb -cc
# Con tantissimi vertici, puoi anche semplificare (riduce il n. di vertici, altera il modello)
gltfpack -i model.glb -o model-simplify.glb -cc -si 0.5
# -si 0.5 significa semplificare a circa il 50% dei vertici
Su
-cc: è il interruttore «compress» che applica in aggiuntaEXT_meshopt_compression.Senza-cc, gltfpack quantizza comunque per impostazione predefinita — cioègltfpack -i in.glb -o out.glbda solo è già «quantizzazione pura, zero dipendenze decoder». (-vè il flag di log verboso — non confonderli.)
Risultati tipici (un modello PBR di 5 MB, 120k vertici, solo riferimento):
| Trattamento | Dim. file | Note |
|---|---|---|
| Originale (float32) | 5.0 MB | Base |
| Quantizzazione pura (predefinito) | 2.6 MB | Dimezzato, nessuna differenza visibile |
MeshOpt (-cc) | 1.7 MB | Altro 35% risparmiato, caricamento un po' più rapido |
Nota: la semplificazione
-siè un operazione con perdita che altera la geometria; non è la stessa cosa della compressione. La compressione cerca di preservare la fedeltà visiva; la semplificazione rimuove attivamente dettagli. I due si possono impilare, ma dipende dallo scenario.
Trappole comuni
- Direzione delle normali cambiata dopo la quantizzazione: di solito precisione troppo bassa. Usa almeno 16 bit per le normali, o codifica ottaedrica a 8 bit.
- Materiali persi dopo la decodifica Draco: Draco comprime solo le mesh; i materiali e le texture vanno gestiti separatamente. Al caricamento vanno configurati sia il decoder Draco sia le estensioni KHR.
- Draco non si carica in un Mini Program: il wasm del decoder è limitato in alcuni runtime; passare a MeshOpt di solito risolve.
- Il modello «deriva» dopo la quantizzazione: quando il modello è lontano dall'origine, la precisione a 16 bit non può esprimere sia coordinate grandi che dettagli piccoli. Soluzione: spostare il modello vicino all'origine prima di quantizzare, o aumentare la profondità di bit.
Prossimo passo
I vertici sono compressi — non festeggiare troppo presto. Come detto, le texture sono l'80% della dimensione del modello. Ora cambiamo campo di battaglia e vediamo perché il PNG/JPG tradizionale è un «ingordo di VRAM» agli occhi della GPU, e come i formati di texture nativi GPU lo risolvono.