Die erste Lektion zur Modell-Diät: Drei Methoden zur Vertex-Kompression
Im letzten Artikel haben wir eine GLB-Datei seziert und gesehen, dass Texturen 80 % des Volumens ausmachen, während Vertices nur 10–20 % belegen. Ist Vertex-Kompression also irrelevant?
Ganz im Gegenteil. Wenn die Texturen eines Modells bereits auf KTX2 komprimiert sind und die Vertices dicht an dicht liegen, bleiben die restlichen 20 % – und diese 20 % lassen sich um die Hälfte oder sogar 90 % reduzieren. Noch wichtiger: Vertex-Kompression ist eine der wenigen Optimierungen, die fast kostenlos sofort wirken: Ein paar Befehle, ein anderer Decoder – und die Datei wird schlanker.
Dieser Artikel erklärt drei Dinge: Wie Vertex-Daten eigentlich aussehen; die Eigenheiten von Quantisierung, MeshOpt und Draco; und eine Schlussfolgerung, die dir viel Ärger erspart – Es gibt nicht die „beste“ Lösung, sondern nur die „passende“ für dein Szenario.
Wie groß ist ein Vertex?
Schauen wir uns zuerst an, was in einem Vertex steckt. In glTF besteht jeder Vertex aus mehreren Attributen:
| Attribut | Verwendung | Standard-Präzision | Bytes pro Vertex |
|---|---|---|---|
| position (Position) | Koordinaten im Raum | 3 × float32 | 12 |
| normal (Normale) | Bestimmt Lichtrichtung | 3 × float32 | 12 |
| tangent (Tangente) | Normal-Map-Berechnung | 4 × float32 | 16 |
| texcoord_0 (UV) | Textur-Koordinaten | 2 × float32 | 8 |
| color (Vertexfarbe) | Vertex-Färbung | 4 × float32 | 16 |
Ein Vertex mit vollständigen PBR-Attributen belegt 48–64 Byte reine Geometriedaten. Ein Modell mit 100.000 Vertices kommt so auf 5–6 MB allein für die Vertices.
Beachte: Hier wird fast ausschließlich float32 (32-Bit-Gleitkommazahl) verwendet. Das ist die Standardeinstellung – und der Ansatzpunkt für Vertex-Kompression, denn die allermeisten Attribute benötigen gar keine 32-Bit-Genauigkeit.
Erste Methode: Quantisierung
Quantisierung ist das grundlegende Prinzip aller Vertex-Kompression – auch Draco und MeshOpt verwenden sie intern.
Quantisierung (Abbildung hochpräziser Gleitkommazahlen auf Ganzzahlen mit geringerer Bitbreite) bedeutet im Kern: Die Zahl 3.14159265 – du merkst dir einfach 3.14. Eine Gruppe von Koordinaten in einem Raum wird nicht mehr mit 32 Bit exakt gespeichert, sondern als Ganzzahl mit kleinerem Wertebereich.
Ursprünglich: position.x = 1.234567 (float32, 4 Byte)
Quantisiert: position.x = 1234 (int16, 2 Byte) + ein Scale/Offset zur Rückrechnung
Vergleich vor und nach der Quantisierung:
| Attribut | float32 (Byte) | Quantisiert (16 Bit) | Einsparung |
|---|---|---|---|
| position | 12 | 6 | 50 % |
| normal | 12 | 6 (oder 4 mit int8 + octahedral) | 50–67 % |
| tangent | 16 | 4–8 | 50–75 % |
| texcoord | 8 | 4 | 50 % |
Aus den ursprünglichen 48–64 Byte pro Vertex werden nach Quantisierung 16–24 Byte – das Volumen halbiert sich oder mehr.
Wann Quantisierung einsetzen
- Du willst nur die Dateigröße reduzieren, ohne extreme Kompression
- Du möchtest keine Decoder-Abhängigkeit – quantisiertes glTF verwendet die Standard-Erweiterung
KHR_mesh_quantization, die von den gängigen Engines nativ unterstützt wird, ohne zusätzliche Bibliotheken - Die Zielplattform ist empfindlich gegenüber Paketgröße (z. B. Mini-Apps in Telegram, WhatsApp – jeder zusätzliche Decoder kostet Dutzende Kilobyte)
Wann nicht
- Das Modell ist extrem klein und Details sind das Verkaufsargument (z. B. Industrie-Bauteile im Millimeterbereich). Quantisierung zeigt bei kleinen Modellen schnell ihre Schwächen – die Textur mag noch okay sein, aber eine Verschiebung der Vertex-Position um 0,1 mm ist in der Nahaufnahme sichtbar.
Echter Fallstrick bei Quantisierungspräzision: In einer Schmuck-Showcase-Szene führte die Quantisierung eines Ringmodells auf 16 Bit zu sichtbaren Kanten in der Nahaufnahme. Ursache war nicht die Anzahl der Vertices, sondern der zu kleine Weltkoordinatenbereich – die 16-Bit-Ganzzahl reichte nicht aus, um feine Details darzustellen. Lösung: Verkleinerung des
position-Bounding-Box oder Umstellung auf höhere Bitbreite für kleine Modelle.
Zweite Methode: MeshOpt
MeshOpt ist die offizielle glTF-Erweiterung EXT_meshopt_compression. Ihr Ziel: „Gute Kompression, extrem schnelles Dekodieren“.
Der Ansatz: Zuerst werden die Attribute quantisiert (wie oben), dann werden die quantisierten Ganzzahlen mit einer LISS (lossless entropy coding, verlustfreie Entropiekodierung) weiter verlustfrei komprimiert. Oder anders: Verlustbehaftete Quantisierung + verlustfreie Entropiekodierung = kleinere Datei bei gleicher Bildqualität wie reine Quantisierung.
- Kompressionsrate: 30–50 % kleiner als reine Quantisierung
- Dekodiergeschwindigkeit: Sehr schnell, reine C/JS-Implementierung, mehrere zehn Millionen Vertices pro Sekunde auf einem Kern
- Decoder-Größe: Klein (ca. 20–30 KB nach gzip)
- Kompatibilität: Wird nativ von Three.js, Babylon.js unterstützt – de facto Standard im Web-Bereich
Wann MeshOpt einsetzen
- Du brauchst eine höhere Kompressionsrate, kannst aber die langsamere Dekodierung von Draco nicht akzeptieren
- Hauptsächlich Web, Mobile, WebXR – die Dekodiergeschwindigkeit wirkt sich direkt auf die Ladezeit der ersten Seite aus
- Modelle müssen häufig dekomprimiert werden (z. B. dynamisch geladene Level)
Wann nicht
- Die Zielplattform erkennt
EXT_meshopt_compressionnicht (sehr alte Engines) - Dir reicht „es läuft“ und der Unterschied von 30 % ist egal – dann ist reine Quantisierung einfacher, eine Abhängigkeit weniger
Dritte Methode: Draco
Draco ist ein Kompressionsverfahren von Google, das auf extreme Kompressionsrate abzielt.
Der entscheidende Unterschied zu den beiden anderen: Draco verändert die Verbindungsstruktur der Vertices (Topologie). Quantisierung modifiziert nur die Zahlenwerte jedes Vertex, MeshOpt fügt eine verlustfreie Kodierung hinzu – Draco hingegen organisiert das Dreiecksnetz neu, um kompakter auszudrücken, „welche Vertices Dreiecke bilden“.
- Kompressionsrate: Höchste der drei Methoden – bei dichten Modellen oft über 90 % Reduktion
- Dekodiergeschwindigkeit: Langsamste der drei, aber immer noch schnell (relativ)
- Decoder-Größe: Größer (ca. 100–200 KB, meist als separate wasm-Datei)
- Bildqualität: Einstellbar, aber bei extremer Kompression können sichtbare Verformungen auftreten
Wann Draco einsetzen
- Modelle sind riesig, mit sehr vielen Vertices (Scan-Modelle mit Millionen Vertices, Gelände)
- Einmaliges Laden, langes Wiederverwenden nach der Dekompression (die langsamere Dekodierung ist akzeptabel)
- Paketgröße ist nicht das Problem, sondern die Download-Geschwindigkeit
Wann nicht
- Mobile + schnelle erste Darstellung – Decoder und Modell müssen beide geladen werden, was die Zeit verlängert
- Umgebungen mit strengen Paketgrößenbeschränkungen (z. B. Mini-Apps, Telegram Mini Apps)
- Modelle mit Skinning-Animationen oder Morph Targets – Draco unterstützt diese nur eingeschränkt, bei falscher Konfiguration treten Fehler auf
Alle drei im Vergleich: Eine Tabelle zur Auswahl
Die folgenden Kompressionsraten basieren auf Community-Benchmarks (z. B. Tests von DeepKolos auf Deutsch, Diskussionen auf Reddit r/threejs). Abweichungen je nach Modell möglich, aber die relativen Verhältnisse sind stabil:
| Methode | Kompressionsrate (relativ zu float32) | Dekodiergeschwindigkeit | Decoder-Größe | Verlustbehaftet? | glTF-Erweiterung |
|---|---|---|---|---|---|
| Nur Quantisierung | ~50 % | Nativ, kein Decoder nötig | 0 | Ja (Präzision) | KHR_mesh_quantization |
| MeshOpt | ~25–35 % | Sehr schnell | ~25 KB | Ja (Präzision) | EXT_meshopt_compression |
| Draco | ~10–20 % | Schnell (langsamste der drei) | ~100–200 KB | Ja (Präzision + Topologie) | KHR_draco_mesh_compression |
Decoder- und Plattform-Kompatibilität:
| Plattform | Nur Quantisierung | MeshOpt | Draco |
|---|---|---|---|
| Desktop Web | ✅ Nativ | ✅ Nativ | ✅ Decoder nötig |
| Mobile Web | ✅ Nativ | ✅ Nativ | ⚠️ Decoder schwer |
| WebXR / VR | ✅ Nativ | ✅ Empfohlen | ⚠️ Vorsicht |
| Telegram Mini Apps | ✅ Empfohlen | ✅ Empfohlen | ❌ Vermeiden |
Fazit: Willst du einfach und ohne Abhängigkeiten → Nur Quantisierung; willst du einen guten Mittelweg → MeshOpt; willst du extreme Kompression und kannst warten → Draco.
Praxis: Mit gltfpack quantisieren und MeshOpt anwenden
gltfpack ist das offizielle glTF-Tool. Mit einem Befehl erledigst du Quantisierung und MeshOpt.
Installation (Binärdateien von gltfpack release herunterladen):
# Quantisierung auf 16 Bit und MeshOpt-Kompression
gltfpack -i model.glb -o model-packed.glb -cc
# -cc = compress (fügt EXT_meshopt_compression über der Standard-Quantisierung hinzu)
Wichtige Parameter:
# Nur quantisieren, ohne MeshOpt (am leichtesten, kein Decoder nötig)
# gltfpack wendet standardmäßig 16-Bit-Quantisierung an (KHR_mesh_quantization), kein zusätzlicher Parameter nötig
gltfpack -i model.glb -o model-quant.glb
# Quantisieren und MeshOpt aktivieren
gltfpack -i model.glb -o model-meshopt.glb -cc
# Bei sehr vielen Vertices: Vereinfachen (reduziert Vertex-Anzahl, verändert das Modell)
gltfpack -i model.glb -o model-simplify.glb -cc -si 0.5
# -si 0.5 bedeutet Reduktion auf ca. 50 % der Vertices
Hinweis zu
-cc: Es ist der Schalter für „compress“ und fügtEXT_meshopt_compressionhinzu. Ohne-ccwendet gltfpack standardmäßig nur Quantisierung an – der Befehlgltfpack -i in.glb -o out.glbist also bereits „reine Quantisierung, kein Decoder nötig“. (-vist der Schalter für ausführliche Logs, nicht verwechseln.)
Typische Ergebnisse (Beispiel: 5 MB Modell mit 120.000 Vertices, PBR):
| Verarbeitung | Dateigröße | Beschreibung |
|---|---|---|
| Original (float32) | 5,0 MB | Basis |
| Nur Quantisierung (Standard) | 2,6 MB | Halbiert, optisch kein Unterschied |
MeshOpt (-cc) | 1,7 MB | Nochmal 35 % weniger, lädt etwas schneller |
Achtung:
-siVereinfachung ist eine verlustbehaftete Operation, die die Geometrie verändert – das ist nicht Kompression. Kompression erhält die Optik so weit wie möglich, Vereinfachung reduziert aktiv Details. Beide können kombiniert werden, aber prüfe, ob das Szenario dies erlaubt.
Häufige Fallstricke
- Normalen zeigen nach der Quantisierung in die falsche Richtung: Meist zu niedrige Präzision. Normalen mindestens 16 Bit verwenden, oder octahedral-Kodierung mit 8 Bit.
- Nach Draco-Dekodierung fehlen Materialien: Draco komprimiert nur das Netz, Materialien und Texturen müssen separat behandelt werden. Beim Laden sowohl Draco-Decoder als auch die KHR-Erweiterung konfigurieren.
- Draco in Telegram Mini Apps lädt nicht: Der wasm-Decoder hat in manchen Laufzeitumgebungen Einschränkungen. Wechsel zu MeshOpt behebt das Problem meist.
- Modell „driftet“ nach Quantisierung: Wenn das Modell weit vom Ursprung entfernt ist, reicht 16-Bit-Präzision nicht für große Koordinaten und feine Details. Lösung: Modell vor der Quantisierung zum Ursprung verschieben oder höhere Bittiefe verwenden.
Nächste Schritte
Die Vertices sind komprimiert, aber Grund zum Feiern ist das noch nicht: Wie eingangs erwähnt, machen Texturen 80 % des Modellvolumens aus. Im nächsten Artikel wechseln wir das Spielfeld und sehen uns an, warum herkömmliche PNG/JPG-Texturen für die GPU enorme Speicherfresser sind – und wie GPU-native Texturformate das Problem lösen.