Maßstabsgetreue Darstellung: Effiziente Strategien für massive Objektzählungen

Dies ist der vierte Beitrag in einer von Mega Cat Studios verfassten Reihe. In diesem Beitrag untersucht Matthew Wojtechko die Erkenntnisse zur Leistungsfähigkeit, die aus der Verwendung von URP und HDRP gewonnen wurden, und geht auf fortgeschrittene Lösungen wie benutzerdefinierte Batch Renderer Group-Implementierungen für das Rendern von Gras ein, wo jede Millisekunde zählt.
Lesen Sie auch die anderen Blogbeiträge dieser Reihe:
- Skalierung von Unity -Workflows: Lehren aus mittelgroßen bis großen Projekten
- Volltreffer: Wie das ereignisgesteuerte Eingabesystem von Unity die Steuerung in Backyard Baseball ermöglicht
- Wie man ein klassisches Sportspiel mit Leveldesign, Weltgestaltung und visuellen Effekten für eine neue Generation neu erfindet.
Das Rendern von Tausenden von Objekten ohne Frame-Drops ist eine ständige Herausforderung. Dichte Stadtlandschaften und weitläufige Landschaften sind zum Markenzeichen moderner Videospiele geworden. Doch während diese Panoramen für die Spieler atemberaubend sind, können sie für die Ingenieure, die mit der Aufrechterhaltung flüssiger 60 Bilder pro Sekunde (fps) beauftragt sind, eine Herausforderung darstellen.
Und wenn Sie Plattformen mit geringerer Leistung anvisieren, wie z. B. mobile oder netzunabhängige Geräte, sind nicht nur diese Ansichten ein Problem. Jede Umgebung, die auch nur einen geringen Detailgrad aufweist, kann die Hardware leicht zum Überhitzen bringen.
Bei Mega Cat Studios reizen wir die Möglichkeiten der Unity-Rendering-Systeme schon seit Jahren voll aus, und wir mussten all unsere Tricks anwenden, um den liebenswerten Grafikstil von Backyard Baseball für PCs und Konsolen zum Laufen zu bringen, ohne dabei die künstlerische Intention zu beeinträchtigen.
In diesem Beitrag werden wir Techniken wie Static Batching und Vertex-Animationstexturen untersuchen und einige der Probleme aufzeigen, die uns bei unserem bisher größten 3D Titel begegnet sind, alles mit dem Ziel, Ihnen dabei zu helfen, Ihre Framerate auch bei einer explosionsartigen Zunahme der Objektanzahl flüssig zu halten.

Untersuchen Sie die Ursachen der Darstellungsprobleme und optimieren Sie diese anschließend.
Das Rendern ist eine Zusammenarbeit zwischen CPU und GPU.
„Die CPU ist der Trainer, die GPU der Athlet.“ „Die CPU gibt die Spielzüge vor und die GPU führt sie aus“, sagt Liam Dudas, Optimierungsingenieur bei Backyard Baseball.
Manchmal ist die Arbeit, die die GPU zu erledigen hat, zeitaufwändiger als andere Aufgaben. Komplexe Meshes, Shader und Beleuchtung können zu Verlangsamungen führen. Die beste Möglichkeit zur Leistungsverbesserung besteht darin, deren Komplexität zu reduzieren, indem man den Künstler bittet, diese Assets zu optimieren.
Manchmal geht es nicht darum, was die CPU die GPU auffordert zu tun, sondern darum, wie sie es auffordert .
Führt die CPU ihre Anweisungen optimal aus? Dauert es lange, bis ermittelt wird, was gezeichnet werden soll, sodass die CPU Däumchen dreht, anstatt den Rest des Spiels zum Laufen zu bringen? Liefert die CPU redundante Informationen? Ein guter Trainer kann sein Team zu einer unerwarteten Aufholjagd inspirieren, aber wenn es um Optimierung geht, können wir diese Systeme nicht einfach dazu bringen, sich „mehr anzustrengen“. Es geht darum, die Informationen zu verstehen, mit denen sie arbeiten, und wie sie diese kommunizieren.
Letztendlich geht es darum, die Zeichenaufrufe zu optimieren, die die CPU an die GPU weiterleitet. Je mehr Arbeit wir in einen einzigen Aufruf packen können, desto besser, insbesondere wenn Zehntausende von Objekten gerendert werden müssen.
Es gibt viele Tricks, um dies zu erreichen – aber vorher sei ein Wort der Warnung gesagt.
Optimieren Sie nicht einfach wahllos, nur um des Nervenkitzels eines cleveren Codes willen. Immer zuerst Benchmarks erstellen. Finden Sie heraus, wo Ihr Projekt am meisten leidet. Die erste Frage, die Sie sich immer stellen sollten: Liegt der Flaschenhals bei der CPU oder der GPU?
Einige der in diesem Beitrag vorgestellten Lösungen erhöhen die CPU-Geschwindigkeit, andere die GPU-Geschwindigkeit. Man sollte sich nicht in der Optimierung eines Prozessors verlieren, wenn der andere eigentlich die Ursache für die Frame-Spitzen ist. Sobald Sie entschieden haben, ob Ihr Spiel CPU- oder GPU-limitiert ist, untersuchen Sie, welcher Vorgang am längsten dauert, und optimieren Sie diesen. Der Leitfaden zu Best Practices für das Profiling von Unity ist ein guter Ausgangspunkt und enthält ein praktisches Flussdiagramm, das Hilfestellung beim oft undurchsichtigen Prozess des Profilings bietet.
„Ich muss viele Profile erstellen“, sagt Liam. „Ich kann erkennen, wann die CPU stark ausgelastet ist und die GPU nicht, und umgekehrt.“ Tatsächlich waren beide zu unterschiedlichen Zeiten unser größter Engpass.“
Am Ende dieses Beitrags haben wir eine Reihe unserer Lieblingsressourcen zusammengestellt, um Sie bei Ihrer eigenen Profilerstellung zu unterstützen.
Nachdem wir diese Vorbemerkung geklärt haben, wollen wir uns nun den Optimierungen widmen.
Vereinfachen und Aussortieren
Beim Rendern müssen wir festlegen, welche Meshes gerendert werden sollen und wo.
Je mehr Meshes wir haben und je mehr Draw-Calls wir benötigen, desto mehr muss die CPU herausfinden, was zu tun ist, während die GPU auf ihren nächsten Auftrag wartet.
Wir könnten den Prozess beschleunigen, indem wir weniger Meshes rendern. Man könnte beispielsweise komplexe Meshes durch Imitatoren, Billboards oder Low-Poly-Versionen ersetzen; oder man könnte eine weitläufige Welt in kleinere Level unterteilen, bei denen weniger auf einmal gerendert werden muss. Dies sind bewährte Lösungen, die Sie nach Möglichkeit nutzen sollten, allerdings schränken sie den Detaillierungsgrad Ihrer Umgebungen ein.
Eine Optimierungsmöglichkeit, die dies vermeidet und einfach umzusetzen ist, ist das Occlusion Culling. Dabei prüft die Kamera, was sich in ihrem Sichtfeld befindet, und vermeidet das Rendern von Dingen, die der Spieler nicht sehen kann. Das spart Zeit, erfordert aber einen höheren Speicherverbrauch. In Umgebungen mit vielen statischen Meshes, wie beispielsweise detaillierten Räumen in einem Bürogebäude, lohnt sich das jedoch oft. Diese Optimierung ist zwar Industriestandard, reicht aber allein oft nicht aus.
Statische Chargenverarbeitung
Eine weitere Möglichkeit, die Anzahl der zu rendernden Meshes zu begrenzen, ohne die Vielfalt einzubüßen, besteht darin, sie zu einem einzigen Mesh zu kombinieren.
Das können Sie selbst manuell erledigen. Oder, sofern die Meshes das gleiche Material verwenden, kann Unity dies für Sie in einem Prozess namens Batching erledigen. Wenn Sie ein Spielobjekt auf statisch setzen, fasst Unity automatisch alle Meshes mit demselben Material zusammen, um die Anzahl der Draw-Calls zu reduzieren. Verwenden Sie diese Option für alles, was unbeweglich bleibt, wie Bäume und Mauern.
Dadurch werden RAM-Nutzung und CPU-Auslastung drastisch reduziert, sodass die GPU mehr Inhalte gleichzeitig rendern kann. Bedenken Sie jedoch, dass mehr GPU-Speicher zum Speichern der kombinierten Netze benötigt wird.
Die statische Stapelverarbeitung ist oft unser wichtigster Optimierungsprozess, insbesondere für Backyard Baseball . Die Verwendung des Chargensystems von URP ist einfach, daher besteht die größte Herausforderung darin, herauszufinden, wie man mit möglichst wenigen Materialien so viele Modelle wie möglich durchführt. Dieser Balanceakt ist der Grund, warum wir einige der kreativsten Köpfe in unseren Optimierungs- und Grafikteams haben.
GPU-Instanziierung
Dies ist ein guter Zeitpunkt, um zwischen einer Sache und einem Instanz dieser Sache zu unterscheiden.
Nehmen wir an, Sie haben ein Netz. Ein Netz ist ein Gewinn. Und das Material ist ein Vermögenswert. Es handelt sich um einzelne Dateien, die in Ihrer Dateistruktur vorhanden sind. Für jedes Objekt mit einem 3D Erscheinungsbild in Ihrem Spiel beschreibt ein Mesh seine Form und ein Material beschreibt dann, wie seine Oberfläche aussieht.
Da Unity die Details jedoch vereinfacht, ist einigen Entwicklern nicht bewusst, dass beim Ausführen ihres Spiels tatsächlich mehrere Kopien dieser Elemente in ihrer Spielwelt instanziiert werden. Wenn Ihr Spieler einen Wald erkundet, bewegt er sich durch viele Kopien dieses einen Baumes, die als Asset in den Spieldateien gespeichert sind.
Aus diesem Grund haben wir für die Vegetation in Backyard Baseball GPU-Instanziierung verwendet. Anstatt jede Kopie des Baums mit separaten Aufrufen der GPU zu rendern, fordert die CPU die GPU stattdessen auf, alle identischen Modelle gleichzeitig zu rendern. Wenn man nicht aufpasst, könnte das dazu führen, dass alle Bäume im Wald identisch aussehen. Mit einigen effektiven Parametern im Shader des Materials, zum Beispiel einem Wert zur Anpassung der Farbe der Blätter, kann man der GPU ein Modell und ein Material zusammen mit den individuellen Farben für jeden Baum übergeben, anstatt jeden Baum einzeln anzufordern.

Wenn Sie die Einstellung „Instanz aktivieren“ für ein Material einschalten, weisen Sie Unity an, verschiedene Materialinstanzen, die dasselbe Material verwenden, in einem einzigen Draw-Call zu kombinieren. Dies ist eine hervorragende Methode, um sowohl Variation als auch Leistung in Ihren Meshes in Einklang zu bringen. Und im Gegensatz zum statischen Batching kann GPU-Instancing auch auf Spielobjekte angewendet werden, die sich in Ihrer Szene bewegen. So sind Sie nicht nur auf die Optimierung von Dingen wie Wänden und Bäumen beschränkt, sondern können auch Requisiten wie Trümmerteile eines Wracks oder Büromaterialien optimieren.
Vertex-Animationstexturen
Statisches Batching und GPU-Instancing sind zwei unterschiedliche Methoden zur Optimierung von Netzen und Materialien. Manchmal sind es jedoch die Animationen, die den Flaschenhals verursachen. Nehmen wir das einfache Waldbeispiel von vorhin. Stellen Sie sich nun vor, wie sich seine Äste und Blätter im Wind wiegen, alles mit einzigartiger und natürlicher Lebendigkeit. Es reicht einfach nicht aus, 10.000 einzigartige Animationsprozesse auf die übliche Weise auszuführen. Wenn du nicht aufpasst, wird die arme CPU nach Luft schnappen!
Ein Trick besteht in der Verwendung von Vertex-Animationstexturen (VAT). Eine VAT kapert die Rechenleistung einer GPU, um sich an Animationen zu versuchen.
Für David Chávez Armenteros, leitender Entwickler bei Mega Cat Studios, war die Verwendung von VAT zur Simulation einer Menschenmenge eine seiner liebsten Grafikimplementierungen, die es zu optimieren galt.
„Mit dem Animator und den standardmäßigen Skinned Mesh Renderern können nur wenige Dutzend Charaktere gerendert werden, bevor die Leistung nachlässt“, sagt er. „Durch die Kombination von VAT mit GPU-Instancing können jedoch Tausende animierter Objekte gleichzeitig auf dem Bildschirm dargestellt werden.“
Bei VAT liest jeder Mesh-Vertex in jedem Frame seine animierte Position aus einer Textur ab. Dazu kodieren wir Positions- und Rotationsdaten in RGB Werte. Anschließend liest ein spezieller Shader alle Pixel einer Textur aus, um zu bestimmen, wie alle Eckpunkte verschoben werden sollen.
Das haben wir für das schwankende Gras in Backyard Baseball verwendet. Es ist eine effiziente Methode, um flüssige Animationen zu erzeugen, aber denken Sie daran, es handelt sich ausschließlich um eine visuelle Anpassung – der Collider ändert sich nicht. Dadurch eignet sich VAT ideal für Szenarien, in denen Bewegung unerlässlich ist, eine präzise Kollisionserkennung jedoch nicht.

Und jede Optimierung bringt einen Kompromiss mit sich. Die Mehrwertsteuer entlastet die CPU, allerdings auf Kosten einer gewissen Mehrbelastung des Arbeitsspeichers. Dies alles muss gegen andere Rendering-Lösungen im Spiel abgewogen werden.
„Die größte Herausforderung bei dem System bestand darin, den Speicherverbrauch und die GPU-basierten Ausleseberechnungen so auszubalancieren, dass sie mit den begrenzten Ressourcen der kabellosen Geräte zurechtkamen“, sagt Jordan Latta, technischer Künstler bei Backyard Baseball .
URP vs HDRP
Die erste Entscheidung, die Sie bei der Optimierung von Grafiken treffen, ist die Wahl der Rendering-Pipeline zu Beginn eines Projekts. Die Universal Render Pipeline (URP) bietet eine gute Basisleistung und ist für mobile und netzunabhängige Geräte ein absolutes Muss. Die High Definition Render Pipeline (HDRP) zielt auf hochauflösende visuelle Darstellungen ab und bietet ein spezialisierteres Instanzierungsverhalten.
Generell empfehlen wir in Unity die Verwendung von URP gegenüber HDRP . Vielleicht haben Sie einen Anwendungsfall, der HDRP erfordert, aber uns leitet folgendes einfache Prinzip: Gestalten Sie Ihr Rendering so effizient wie möglich.
HDRP bietet Ihnen einige High-End-Funktionen, aber wenn Sie nicht auf realistische Bildwiedergabe Wert legen, raten wir Ihnen davon ab.
Weitere Optimierungstricks
Manchmal liegt das Problem bei Tausenden von Objekten auf dem Bildschirm gar nicht an der Darstellung. Es ist Ihr eigener Code. In solchen Fällen können Sie folgende Tricks anwenden:
- Tausende von Aktualisierungsschleifen werden zu einer einzigen zusammengefasst. Anstatt jedem GameObject ein eigenes MonoBehaviour zu geben, sollten diese zu einem einzigen zusammengefasst werden, sodass sie dieselbe Update-Funktion verwenden. Ein zentraler Manager hilft, Tausende von einzelnen Aktualisierungsschleifen zu vermeiden.
- Anstatt alles im selben Thread auszuführen, verwenden Sie Multithreading. Prüfen Sie, ob das Job-System und der Burst Compiler gute Optionen für Ihr Spiel sein könnten.
- Profitieren Sie von den Verstecken. Anstatt die Hauptkamera oder die Transform-Eigenschaft eines GameObjects in jedem Frame zu erfassen, sollten diese einmal zwischengespeichert und dann als Referenz wiederverwendet werden. Tarodev demonstriert die Leistungsverbesserung und erläutert auch diese anderen Optimierungen.
Den größten Rendering-Boost, den Sie für Ihr nächstes Projekt erzielen können, erhalten Sie wahrscheinlich durch die Verwendung der neuesten LTS-Version von Unity. Unity 6.3 LTS bietet eine Vielzahl von Verbesserungen, darunter Optimierungen zur Leistungssteigerung.
Für Backyard Baseball bestand unser einfachster und bester Optimierungserfolg darin, den Einsatz von Texturen einzuschränken.
Es gibt ein paar raffinierte Möglichkeiten, wie man versehentlich doppelt so viele Texturen verwenden kann, wie man benötigt:
- Shader : Wenn ein Shader eine Textur bei zwei verschiedenen Gelegenheiten verwendet, ohne dabei dieselbe Referenz zu teilen, kann es vorkommen, dass die Textur an mehreren Stellen im Speicher geladen wird.
- Skripte : Ebenso kann es vorkommen, dass verschiedene Bereiche Ihres Quellcodes dieselbe Textur über unterschiedliche Referenzen laden.
- Version Control : Wenn zwei Entwickler dieselbe Datei zu ihrem lokalen Branch hinzufügen, werden manchmal beide Dateien beim Zusammenführen der Branches beibehalten, was unnötig Speicherplatz belegt.
Wir versuchen, uns dieser Fallstricke bewusst zu sein, damit wir sie verhindern können, bevor sie überhaupt auftreten. Zu strenge Vorgaben verlangsamen jedoch den schnellen Iterationszyklus. Hier kommen unsere Optimierungsexperten ins Spiel, die Shader, Assets und Skripte prüfen, wenn das Projekt in die späteren Phasen seines Entwicklungszyklus eintritt.
Bei Mega Cat Studios ermöglichen uns diese Strategien, Szenen zu rendern, die vor wenigen Jahren noch undenkbar waren.
Für uns ist Rendering im Grunde Magie: Wir können alles, was wir uns vorstellen können, in Echtzeit erschaffen, aber erst nach sorgfältigem Studium der geheimen Texte (Unity Dokumentation, Blogbeiträge, YouTube-Tutorials). Zum Glück haben wir einige wahre Experten in unserem Team, die nichts lieber tun, als sich in diese Dokumentation zu vertiefen und die gewonnenen Erkenntnisse mit dem Team zu teilen.
Und natürlich profitieren davon letztendlich unsere Spieler. Wir hoffen, dass die Fans viel Freude daran haben werden , Backyard Baseball zu erkunden, ohne auch nur einen Moment innezuhalten und darüber nachzudenken, wie viel Blut, Schweiß und Magie in die Erschaffung dieser so real wirkenden Welt geflossen sind.
Mehr erfahren
- Unity Profilierungsleitfäden
- Unity -Dokumentation
- Ankündigung der Unity 6-Funktionen
- Offizielle Unity Optimierungs-Tutorials
- Tarodevs Anleitung: So rendert man 2 Millionen Objekte mit 120 FPS
