Kris

Prima lezione per dimagrire i modelli 3D: le tre tecniche di compressione dei vertici

Compressione 3DCompressione verticiMeshOptDracoglTF

Nell'articolo precedente abbiamo aperto un file GLB e visto che le texture occupano l'80% del volume, mentre i vertici solo il 10-20%. Quindi la compressione dei vertici sembra irrilevante, vero?

Tutt’altro. Quando le texture di un modello sono già state compresse in KTX2 e i vertici sono fittissimi, quel 20% rimanente è rappresentato proprio dai vertici – e questo 20% può essere dimezzato, o addirittura ridotto del 90%. Ancora più importante, la compressione dei vertici è tra le poche ottimizzazioni che si applicano quasi a costo zero e con effetto immediato: bastano pochi comandi, si cambia un decoder, e il file si alleggerisce.

Questo articolo chiarisce tre cose: com’è fatto un vertice; le caratteristiche di quantizzazione, MeshOpt e Draco; e una conclusione che ti farà evitare trappole: non esiste la soluzione "migliore", ma solo quella "più adatta".

Quanto è grande un vertice?

Vediamo cosa contiene un vertice. In glTF, ogni vertice è composto da diversi attributi:

AttributoUsoPrecisione predefinitaByte per vertice
position (posizione)Coordinate del vertice nello spazio3 × float3212
normal (normale)Determina la direzione della luce3 × float3212
tangent (tangente)Calcolo della normal map4 × float3216
texcoord_0 (UV)Coordinate di campionamento texture2 × float328
color (colore vertice)Colorazione a livello di vertice4 × float3216

Un vertice con attributi PBR completi richiede 48-64 byte solo per i dati geometrici. Un modello con 100.000 vertici pesa 5-6 MB solo per i vertici.

Nota che qui si usa quasi sempre float32 (numero a virgola mobile a 32 bit). È la configurazione predefinita, ma anche il punto debole per la compressione dei vertici – perché la maggior parte degli attributi non ha bisogno di una precisione a 32 bit.

Primo approccio: Quantizzazione

La quantizzazione è il principio di base di tutte le compressioni di vertici, e anche Draco e MeshOpt la usano internamente.

La quantizzazione (mappare numeri a virgola mobile ad alta precisione su interi a bassa precisione) consiste essenzialmente in questo: del numero 3.14159265 ti basta ricordare 3.14. Per un insieme di coordinate spaziali, invece di registrare ogni decimale con 32 bit, usi un intero con un intervallo più piccolo.

Originale: position.x = 1.234567   (float32, 4 byte)
Quantizzato: position.x = 1234     (int16, 2 byte) + un scale/offset per ricostruire

Confronto prima e dopo la quantizzazione:

AttributoByte float32Dopo quantizzazione (16 bit)Risparmio
position12650%
normal126 (o 4, con int8 + ottaedrale)50-67%
tangent164-850-75%
texcoord8450%

Per il vertice di 48-64 byte di prima, dopo la quantizzazione si scende a 16-24 byte, dimezzando il volume.

Quando usare la quantizzazione

  • Vuoi solo ridurre il volume, senza compressione estrema.
  • Desideri zero dipendenze di decoder – il glTF quantizzato usa l'estensione standard KHR_mesh_quantization, supportata nativamente dai motori principali, senza bisogno di librerie aggiuntive.
  • La piattaforma di destinazione è sensibile alle dimensioni del bundle (ad esempio app leggere come PWA o ambienti con limitazioni di pacchetto, dove portare un decoder Draco da decine di KB sarebbe un costo).

Quando evitarla

  • Il modello è molto piccolo e i dettagli sono fondamentali (ad esempio componenti industriali con tolleranze millimetriche). La quantizzazione sui modelli piccoli rivela facilmente difetti: le texture possono ancora andare bene, ma uno spostamento di 0,1 mm nella posizione dei vertici è visibile a occhio nudo in primo piano.

Vero problema di perdita di precisione con la quantizzazione: in una scena di gioielleria, quantizzando un anello a 16 bit, in primo piano i bordi metallici presentavano scalettature. Il motivo non era il numero di vertici, ma il fatto che il world coordinate system era troppo piccolo, e l'intero a 16 bit non riusciva a esprimere la granularità necessaria. Soluzione: ridurre l'intervallo di quantizzazione (restringere il bounding box della position) o passare a una profondità maggiore per i modelli piccoli.

Secondo approccio: MeshOpt

MeshOpt è l'estensione ufficiale di glTF EXT_meshopt_compression, pensata per un buon rapporto compressione/velocità di decodifica.

Il suo approccio è: prima quantizza gli attributi (come sopra), poi applica una codifica entropica senza perdita (LISS – lossless entropy coding) per comprimere ulteriormente gli interi quantizzati. In altre parole: quantizzazione con perdita + codifica entropica senza perdita = volume più piccolo, qualità identica alla sola quantizzazione.

  • Rapporto di compressione: dal 30 al 50% in meno rispetto alla sola quantizzazione.
  • Velocità di decodifica: molto alta, implementazione pura C/JS, decine di milioni di vertici al secondo su un singolo thread.
  • Dimensioni del decoder: molto piccole (circa 20-30 KB dopo gzip).
  • Compatibilità: supporto nativo in Three.js e Babylon.js, è uno standard de facto per il Web.

Quando usare MeshOpt

  • Hai bisogno di un rapporto di compressione più alto ma non puoi accettare la decodifica più lenta di Draco.
  • Destinazione principalmente Web, mobile, WebXR – la velocità di decodifica incide direttamente sull'esperienza di caricamento iniziale.
  • Il modello deve essere decompresso frequentemente (ad esempio livelli caricati dinamicamente).

Quando evitarlo

  • La tua piattaforma di destinazione non supporta EXT_meshopt_compression (motori molto vecchi).
  • Ti basta "farlo funzionare" e non ti interessa un 30% di differenza – allora la pura quantizzazione è più semplice, con una dipendenza in meno.

Terzo approccio: Draco

Draco è una soluzione di compressione creata da Google, pensata per il massimo rapporto di compressione.

La differenza fondamentale rispetto ai due precedenti: Draco modifica le relazioni di connessione tra i vertici (topologia). La quantizzazione cambia solo la rappresentazione numerica di ogni vertice, MeshOpt vi aggiunge una codifica senza perdita, mentre Draco riorganizza la mesh triangolare con un modo più compatto di esprimere "quali vertici formano triangoli".

  • Rapporto di compressione: il più alto tra i tre, su modelli con molti vertici si arriva spesso a una riduzione del 90% o più.
  • Velocità di decodifica: la più lenta tra i tre, ma comunque abbastanza veloce (solo relativamente più lenta).
  • Dimensioni del decoder: maggiori (circa 100-200 KB, e di solito richiede un caricamento separato di wasm).
  • Qualità: regolabile, ma con compressione estrema si possono avere deformazioni visibili.

Quando usare Draco

  • Modelli enormi, con moltissimi vertici (modelli scansionati da milioni di vertici, terreni).
  • Caricamento una tantum, riutilizzo a lungo dopo la decompressione (la decodifica più lenta è accettabile).
  • Il collo di bottiglia non è il bundle, ma la velocità di download.

Quando evitarlo

  • Mobile + necessità di primo caricamento veloce – sia il decoder che il modello devono essere scaricati, il che rallenta.
  • Ambienti con restrizioni sulle dimensioni del bundle, come app leggere o PWA.
  • Il modello richiede animazioni di skinning o morph target – Draco ha un supporto debole per questi, e una configurazione errata può causare problemi.

I tre a confronto: una tabella per la scelta

I rapporti di compressione che seguono sono basati su benchmark della community (test su forum tecnici come Reddit, discussioni su r/threejs), possono variare da modello a modello, ma le relazioni relative sono stabili:

SoluzioneRapporto di compressione (rispetto a float32)Velocità di decodificaDimensione decoderCon perdita?Estensione glTF
Solo quantizzazione~50%Nativa, nessun decoder0Sì (precisione)KHR_mesh_quantization
MeshOpt~25-35%Molto veloce~25KBSì (precisione)EXT_meshopt_compression
Draco~10-20%Veloce (il più lento dei tre)~100-200KBSì (precisione + topologia)KHR_draco_mesh_compression

Compatibilità decoder e piattaforma:

PiattaformaSolo quantizzazioneMeshOptDraco
Web desktop✅ Nativo✅ Nativo✅ Richiede decoder
Web mobile✅ Nativo✅ Nativo⚠️ Decoder pesante
WebXR/VR✅ Nativo✅ Consigliato⚠️ Usare con cautela
App leggere (es. PWA)✅ Consigliato✅ Consigliato❌ Evitare se possibile

In sintesi: per semplicità e zero dipendenze → sola quantizzazione; per equilibrio → MeshOpt; per compressione estrema e si può aspettare → Draco.

Pratica: usare gltfpack per quantizzazione e MeshOpt

gltfpack è lo strumento ufficiale glTF, con un solo comando si applicano quantizzazione e MeshOpt.

Prima installalo (binari scaricabili da gltfpack release):

# Quantizza model.glb a 16 bit e aggiungi compressione MeshOpt
gltfpack -i model.glb -o model-packed.glb -cc

# -cc = compress (sovrappone EXT_meshopt_compression alla quantizzazione predefinita)

Parametri comuni:

# Solo quantizzazione, senza MeshOpt (più leggero, zero dipendenze decoder)
# gltfpack quantizza di default i vertici a 16 bit (KHR_mesh_quantization), non serve parametro aggiuntivo
gltfpack -i model.glb -o model-quant.glb

# Quantizza e abilita MeshOpt
gltfpack -i model.glb -o model-meshopt.glb -cc

# Se ci sono molti vertici, si può semplificare (riduce il numero di vertici, modifica 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

Nota su -cc: è l'opzione "compress", che aggiunge EXT_meshopt_compression. Senza -cc gltfpack fa già la quantizzazione di default – quindi il comando gltfpack -i in.glb -o out.glb è già "sola quantizzazione, zero dipendenze decoder". (-v è l'opzione verbose per log dettagliati, non confondere.)

Effetto tipico (modello PBR da 5 MB, 120.000 vertici, valori indicativi):

ElaborazioneDimensione fileDescrizione
Originale (float32)5,0 MBRiferimento
Solo quantizzazione (default)2,6 MBDimezzato, visivamente quasi identico
MeshOpt (-cc)1,7 MBRisparmio del 35%, caricamento leggermente più veloce

Attenzione: la semplificazione con -si è un'operazione con perdita che modifica la geometria del modello, non è compressione. La compressione cerca di mantenere la coerenza visiva, mentre la semplificazione riduce attivamente i dettagli. Possono essere combinate, ma verifica che il tuo scenario lo permetta.

Errori comuni da evitare

  • Normali invertite o errate dopo la quantizzazione: di solito causate da precisione insufficiente. Usa almeno 16 bit per le normali o la codifica ottaedrica a 8 bit.
  • Materiali mancanti dopo la decompressione Draco: Draco comprime solo la mesh, i materiali e le texture vanno gestiti separatamente. Durante il caricamento configura sia il decoder Draco sia l'estensione KHR.
  • Draco non si carica nei contesti sandbox / mini-app: il decoder wasm ha restrizioni in alcuni ambienti. Passare a MeshOpt solitamente risolve il problema.
  • Modello che "deriva" dopo la quantizzazione: se il modello è lontano dall'origine, 16 bit non bastano a coprire grandi coordinate e piccoli dettagli. Sposta il modello vicino all'origine prima della quantizzazione o aumenta la profondità di bit.

Prossimi passi

Abbiamo ridotto i vertici, ma non è ancora il momento di festeggiare: le texture occupano fino all'80% del volume del modello. Nel prossimo articolo vedremo perché i tradizionali formati PNG/JPG sono così pesanti per la GPU e come i formati di texture nativi per GPU risolvono questo problema alla radice.

Supportaci