LOD für Tausende animierte Figuren in Unity erstellen

Anleitung für Crowd-LOD in Unity mit Vertex Animation Textures: warum Vertices statt Dreiecke zählen, LOD als neues Mesh und Auswahl pro Entity.

Drei Versionen derselben Skelettfigur mit von links nach rechts abnehmender Dreieckszahl

Bauen Sie LOD für Tausende animierte Figuren in Unity nach der Zahl der Vertices, nicht der Dreiecke: Bei Vertex Animation Textures wird die Animation pro Vertex berechnet. Bereiten Sie die unteren Stufen von Hand vor, binden Sie die Vertices der niedrigsten Stufe an die Oberfläche des Originals und wählen Sie die Stufe für jede Figur einzeln. In unserem Tower-Defense-Spiel für den PC stieg die Bildrate bei 3 000 Gegnern in der Nahansicht der Horde so von 62 auf 120 FPS.

Das Verfahren betrifft Spiele, in denen sich Tausende Figuren gleichzeitig bewegen, also Tower Defense, Strategie und Survival. Unser Spiel entsteht in Unity mit ECS und ist in den Referenzen zu finden. LOD steht für Level of Detail: Eine entfernte Figur wird aus einem einfacheren Mesh gezeichnet. Die Bilder zeigen die echten Meshes unseres Skelett-Gegners, keine Spielszenen.

Warum generierter LOD bei einer Horde nicht hilft

Unity kann LOD-Stufen beim Import eines Modells erzeugen (Funktion Mesh LOD). Für Tausende Figuren hat das zwei Probleme.

Das erste ist das Aussehen. Die automatische Vereinfachung riss dünne Gliedmaßen auf und zerstörte die UVs, also die Zuordnung der Textur zum Mesh.

Das zweite sind die Kosten der Animation. Klassische Skelettanimation ist für Tausende Entities zu teuer, deshalb kommen Vertex Animation Textures (VAT) zum Einsatz. Eine VAT ist eine Textur, in der jede Spalte zu einem Vertex und jede Zeile zu einem Animationsframe gehört. Der Shader liest die Vertex-Position aus der Textur, die Kosten hängen also von der Zahl der gelesenen Vertices ab, auch beim Zeichnen der Schatten.

Die Obergrenze zeigt ein Test mit erzwungener generierter Stufe auf allen Figuren (Unity erzeugt auch die Stufen 3 und 4, Build, 1080p, 3 000 Gegner, Schatten an):

Erzwungene Stufe Dreiecke FPS
LOD 0 11 Millionen 72
LOD 1 6 Millionen 96
LOD 2 3,68 Millionen 120
LOD 4 1,43 Millionen 201

Der Spielraum ist groß. Er muss ohne sichtbare Löcher und ohne Sprünge zwischen den Stufen genutzt werden.

Vertices zählen, nicht Dreiecke

Halb so viele Dreiecke helfen für sich genommen wenig. Ein LOD mit halb so vielen Dreiecken und 996 von ursprünglich 1 521 Vertices sparte 1,9 ms pro Frame, ein LOD mit 217 Vertices 6,4 ms. Beobachten Sie bei jeder Stufe beide Werte.

Oft gibt es unnötig viele Vertices. An jeder UV-Naht wird ein Vertex verdoppelt, weil eine Position dann zwei Texturkoordinaten braucht. Unsere Modelle aus einem KI-Generator hatten UVs in Hunderten Inseln: Das Skelett hatte 1 521 Vertices, aber nur 608 verschiedene Kombinationen aus Position und Normale.

Schritt 1: Vertices in LOD 0 reduzieren

Die erste Einsparung kostet kein Detail. Verschmelzen Sie Vertices mit gleicher Position und ähnlicher Normale (Abweichung bis 25°), entfalten Sie die UVs neu mit Nähten nur an scharfen Kanten (über 70°) und übertragen Sie die Textur Dreieck für Dreieck aus der alten. Beim Skelett sank die Zahl von 1 521 auf 1 090 Vertices, bei den übrigen Gegnertypen um 19 bis 29 %, bei gleicher Form.

Schreiben Sie danach nur die Spalten in die VAT, die eine Stufe tatsächlich nutzt. Das sparte 40 bis 60 % des Animationsspeichers.

Schritt 2: LOD 1 durch Vereinfachung mit gemeinsamer Textur

LOD 1 ist das auf etwa 52 % der Dreiecke vereinfachte LOD 0. Vereinfachen Sie stückweise (jede Kombination aus Bone und zusammenhängendem Teil des Meshes) und sperren Sie die Vertices an den Rändern der Stücke. Geben Sie dem Kopf ein größeres Budget, denn seine Silhouette erkennt das Auge in der Menge.

Behalten Sie die Textur von LOD 0. Eine Stufe mit eigener Textur oder anderer Form ist beim Übergang auch bei ähnlicher Silhouette sichtbar.

Unter etwa 50 % der Dreiecke reißt das Vereinfachen aus vorhandenen Vertices dünne Teile und einzelne Stücke auf. Noch weiter nach unten braucht es einen anderen Weg.

Drei Versionen der Skelettfigur nebeneinander mit abnehmender Dreieckszahl
Dasselbe Skelett in drei Stufen: 873, 448 und 330 Dreiecke, 1 090, 705 und 520 Vertices.

Schritt 3: LOD 2 als neues Mesh, das am Original hängt

Die niedrigste Stufe ist ein neues geschlossenes Mesh. Verdicken Sie das Original mit einer Schicht, lassen Sie ein Voxel-Remesh darüberlaufen und vereinfachen Sie auf die Zielzahl (bei uns 200 bis 460 Dreiecke je nach Gegnertyp). Ziehen Sie das Ergebnis auf die Oberfläche des Originals und erzeugen Sie die UVs automatisch. Backen Sie die Textur aus dem Original in derselben Dichte wie LOD 0, damit auch die Mipmaps passen.

Das neue Mesh hat keine Animationsgewichte. Binden Sie deshalb jeden seiner Vertices an das nächstgelegene Dreieck des Originals und speichern Sie die drei Spalten dieses Dreiecks und die baryzentrischen Gewichte (die Anteile, die die Position eines Punkts im Dreieck festlegen).

Schema: Ein roter Vertex des vereinfachten Meshes ist gestrichelt mit den drei Vertices eines Originaldreiecks verbunden
Ein Vertex der unteren Stufe (rot) bewegt sich mit den drei Vertices des nächsten Originaldreiecks.

Die VAT-Spalte eines angehängten Vertex ist in jedem Frame die gewichtete Summe der Positionen und Normalen dieser drei Vertices. Der Shader muss nichts Besonderes tun, ein angehängter Vertex ist einfach eine weitere Spalte.

für jeden Frame f:
    VAT[angehängt, f] = w0 * VAT[a, f] + w1 * VAT[b, f] + w2 * VAT[c, f]

Schritt 4: Löcher automatisch prüfen

Bilder aus dem Modellierungswerkzeug zeigen Löcher nicht zuverlässig. Eine einfache Prüfung genügt: Rendern Sie Original und Kandidat aus 11 Richtungen mit 120 px und vergleichen Sie die Silhouetten.

Als Loch gelten Pixel des Originals innerhalb der Kontur des Kandidaten, die der Kandidat nicht abdeckt, sowie Pixel des Kandidaten mehr als 2 px außerhalb der Originalkontur. Bei uns gelten als Grenzen 1 % Löcher und 4 bis 6 % Verlust. Die Prüfung muss das materialabhängige Backface-Culling beachten. Die Grenzen haben wir danach festgelegt, wann Übergänge im Spiel sichtbar waren.

Schritt 5: Stufe für jede Figur einzeln wählen

Wenn die ganze Karte gleichzeitig die Stufe wechselt, sieht man das. Bei einer Auswahl pro Entity verteilen sich die Übergänge und gehen in der Menge unter.

Ganze Karte auf einmalMenge von Punkten in einer Farbe, alle Figuren haben dieselbe Stufe
Pro EntityMenge von Punkten in Bändern nach Abstand zur Kamera, nahe rot, ferne grau
Blick von oben, Kamera unten. Farbe ist die LOD-Stufe: Rot 0, Orange 1, Grau 2.

Hinweis zur Version: In der Dokumentation von Unity 6.2 steht, dass Entities Graphics immer LOD 0 verwendet. Die Auswahl pro Entity mit der folgenden Formel nutzen wir in Unity 6.6, prüfen Sie daher vorab das Verhalten Ihrer Version:

LOD = log2(d * C / D) * steigung + versatz
C = schwelle * 2 * tan(fov / 2) / bildhoehe

Dabei ist d der Abstand zur Kamera und D der Durchmesser der World Bounds der Entity. Wählen Sie Steigung und Versatz so, dass Stufe 1 bei 93,3 und Stufe 2 bei 167,9 Metern beginnt (Kamera mit 30° FOV und 1440 px). Setzen Sie die globale LOD-Schwelle auf Bildhöhe / 1440, dann hängen die Abstände nicht von der Auflösung ab.

Zwei Fehler, die Sie vermeiden sollten:

  • Skalierung. Berechnen Sie den Durchmesser D aus den World Bounds, also mit der Skalierung des Prefabs. Meshes für VAT liegen oft in kleinen Einheiten vor, das Prefab vergrößert sie 2- bis 4-fach. Ohne das zeichnet das Spiel die ganze Zeit LOD 0 und meldet nichts. Prüfen Sie das anhand der Dreieckszahl im Normalbetrieb gegenüber einem erzwungenen LOD. Die Drehung einer Entity vergrößert die achsenparallelen Bounds, deshalb multiplizieren wir den Durchmesser bei uns mit 1,22.
  • Baking. Die Auswahlkurve wird beim Baken der SubScene gespeichert, und das Baking erkennt eine Änderung am Mesh nicht. Erzwingen Sie nach der Anpassung einen Neuaufbau der SubScene.

Ergebnisse und Grenzen

macOS-Build, 2560 × 1440, 3 000 Gegner, Schatten an, Spiel nach dem Game Over angehalten (VAT-Animationen laufen weiter, die Messung gilt also für die Grafik):

Szene Vorher Nachher
Ganze Karte 90 FPS 130 FPS
Nahansicht der Horde 62 FPS 120 FPS

Auf dem Steam Deck (1280 × 800, 8 Recheneinheiten RDNA 2) sind es 75 bis 80 FPS auf der ganzen Karte. Die engste Nahansicht der Horde läuft mit hoher Schattenqualität bei 33 FPS, ohne Schatten bei 64. Schatten sind damit der größte verbleibende Posten: LOD senkt auch ihre Kosten, aber das Abschalten bringt immer noch einen großen Sprung.

Was LOD nicht löst: Die Lochprüfung läuft in der T-Pose und erkennt daher kein Dehnen in extremen Posen. Ein Gegnertyp überschreitet die Lochgrenze in LOD 2 noch. Die Berechnung der Hordenbewegung ändert LOD nicht, dazu gibt es den Artikel zur Hordennavigation in Unity DOTS, zu den Reaktionen auf Explosionen den Artikel zum Fake Ragdoll.

Brauchen Sie Hilfe bei der Performance Ihres Unity-Spiels oder bei einer Crowd-Simulation? Schreiben Sie uns.

← Mehr in Tower Defense