Leistungsoptimierung mit einem benutzerdefinierten Vegetationssystem für Thrive: Heavy Lies the Crown

ADAM AXLER / UNITYSenior Content Marketing Manager
Jun 18, 2025
Gedeihen: Heavy Lies the Crown | Zugalu Entertainment | Playside Studios
Diese Website wurde aus praktischen Gründen für Sie maschinell übersetzt. Die Richtigkeit und Zuverlässigkeit des übersetzten Inhalts kann von uns nicht gewährleistet werden. Sollten Sie Zweifel an der Richtigkeit des übersetzten Inhalts haben, schauen Sie sich bitte die offizielle englische Version der Website an.

Zugalu Entertainment wurde 2014 gegründet, um Spiele zu entwickeln, die Innovation, Nostalgie und kommerzielle Attraktivität verbinden. In den letzten 11 Jahren wurden unter anderem Epic Food Fight, Technolites, Chronique des Silencieux und Sovereign Syndicate veröffentlicht.

Am 6. November 2024 veröffentlichten sie Thrive: Heavy Lies the Crown im Early Access, zu großartigen Bewertungen. Bei dem Spiel handelt es sich um einen mittelalterlichen Städtebauer mit Echtzeitstrategieelementen und sowohl Einzelspieler- als auch kooperativem Multiplayer. Spieler können ihr Gebiet und ihr Königreich strategisch erweitern und dann auf einer großen Karte aufbauen. Auf ihrem weiteren Weg hängt das Schicksal des Königreichs von jeder Entscheidung ab, die sie treffen.

Heute veröffentlichte das Team die Version 1.0 des Spiels. Wir haben uns mit Garrett Hau, CTO bei Zugalu Entertainment, und Jackie Li, der leitenden Konzeptkünstlerin und technischen Grafikerin des Teams, zusammengesetzt, um die Leistungsherausforderungen zu besprechen, auf die sie gestoßen sind, und darüber, wie der Aufbau eines benutzerdefinierten Vegetationssystems der Schlüssel zur Optimierung des Spiels war.

Erstellung eines benutzerdefinierten Vegetationssystems

Da das Spiel in der Wildnis spielt, gibt es viele Bäume, Gras, Büsche usw. In der ursprünglichen Umsetzung war das Gras sehr spärlich, aber das Team wollte ein weitläufiges und üppiges Grasfeld schaffen. „Für viele unserer verschiedenen Biome, wie zum Beispiel das Grünlandbiom, wollten wir den Boden voller Vegetation bedecken“, sagt Hau. Um dies zu erreichen, benötigten sie ein System, das eine große Anzahl von Vegetationsinstanzen verarbeiten konnte.

Während des Großteils des Projekts verwendete das Team eine Drittanbieterlösung, die gut funktionierte, aber stark CPU-seitig war, rund 3 Millisekunden kostete und das Spiel sehr CPU-engpassig war. Da die Systemanforderungen relativ gering waren, beschlossen sie, die meisten Berechnungen auf die GPU zu übertragen und benötigten eine andere Lösung.

„Wir haben uns entschieden, unser eigenes System zu entwickeln und uns gleichzeitig in die kachelbasierte Natur des Spiels zu integrieren. Das ursprüngliche System hatte seine eigene Arbeitsweise, und bestimmte Interaktionen auf der Ebene der einzelnen Kacheln waren zu kostspielig“, sagt Hau.

Gedeihen: Heavy Lies the Crown | Zugalu Entertainment | Playside Studios
Vorschau der Vegetation im Editor

Sie wollten das beheben und Vegetation mit höherer Dichte haben. Da sie in ihrem neuen System mit der GPU rechneten, hatten sie mehr Leistungsspielraum und die Möglichkeit, einen florierenden Wald zu erschaffen.

Ein weiteres wichtiges Ziel war die Maskierung pro Kachel. „Vorher konnte man eine Straße nicht effizient ausblenden, wenn man sie platzierte, sodass die Vegetation einfach auf den Straßen wuchs“, sagt Hau. Da die anfängliche Methode auf der CPU basierte, würde jede zusätzliche Maske sie belasten und sie wollten, dass die Straßen oder irgendetwas wirklich Gras oder Vegetation maskieren, ohne viel Leistung zu fressen.

Gedeihen: Heavy Lies the Crown | Zugalu Entertainment | Playside Studios
Im Editor Aufnahme der Teamkachelmaskierung

Ausführen mit Compute Shadern

Das Team hatte auch einen großen Engpass, als es darum ging, die Vegetation über seine große Karte zu legen. Da das Spiel über eine üppige Vegetation und eine sehr hohe Kamera verfügt, die sich schnell über die Karte bewegen kann, war es wichtig, dass die Vegetation hineingespawnt wurde, ohne den Spieler aufzuhalten. Dies erwies sich als sehr schwierig.

„Wenn man sich die Vegetation anschaut, erkennt man, dass man potenziell Hunderttausende Instanzen spawnen muss. Also haben wir uns für GPU-Instancing entschieden, das genau für diesen Zweck entwickelt wurde und potenziell Millionen von Instanzen ermöglicht“, sagt Li.

Zunächst bereitete das Team die Daten für die GPU-Instancing vor. Sie mussten der GPU eine Reihe von Positionen konstruieren und füttern, an denen sie ihre Vegetation spawnen wollten. Da die Vegetation außerhalb der Kachelmaskierung nicht wirklich mit der CPU-Seite interagiert, wurde dies mit einem Compute Shader ausgeführt. Da der Compute-Shader auf der GPU ausgeführt wurde, bevor seine Render-Shader ausgeführt wurden, bereiteten sie die Daten in ihrem Compute-Shader vor und leiteten die resultierenden Daten zur Instanziierung zu. Dies wird auch als indirekt bezeichnet.

Gedeihen: Heavy Lies the Crown | Zugalu Entertainment | Playside Studios
Aufnahme von spawned Einheiten im Editor

Der nächste Schritt war herauszufinden, wie man die Compute Shader einsetzt, was sich als relativ einfach erwies. Li erklärt: „Ein Compute Shader ist nur eine Multithread-Operation auf der GPU. In unserem Fall können die Instanzdaten für jeden Thread individuell berechnet werden. Sieh es dir wie das Unity Jobsystem an, aber auf der GPU.“

Bei der Arbeit in einer Multithread-Umgebung sollte die Arbeitsauslastung jedes Threads so gestaltet sein, dass er nicht von der Ausführung in anderen Threads abhängig ist, um die Leistung zu maximieren.

Li sagt: „Wenn wir beispielsweise Zufälligkeit hinzufügen, verwenden wir Dinge wie Perlin-Rausch, Simplex-Rausch oder Hash-Funktionen. Die aktuell ausgewertete Oberfläche ist ebenfalls in ein einheitliches Raster unterteilt, wobei jeder Thread innerhalb jedes Rasterpunkts arbeitet, so dass wir uns keine Sorgen machen müssen, mehrere duplizierte Vegetation übereinander zu spawnen.“

Da das Gelände während der Laufzeit nicht verformbar war, holten sie diese Daten zu Beginn der Entwicklung ab und leiteten sie an die GPU weiter. Dies ermöglichte eine Vorverarbeitung der Höhendaten, insbesondere zur Berechnung der Steigung an jeder Höhenposition, so dass die Vegetation an die Kontur des Geländes angepasst werden konnte.

Gedeihen: Heavy Lies the Crown | Zugalu Entertainment | Playside Studios
Gedeihen: Heavy Lies the Crown | Zugalu Entertainment | Playside Studios

Computing-Shader konsolidieren


Obwohl das Team Compute Shader verwendete, musste es viele davon ausführen, um an die gewünschten Daten zu kommen. Ähnlich wie bei den Draw Calls ist weniger besser. Sie wollten die Anzahl der GPU-Befehle reduzieren, indem sie die Hälfte der Dispatching-Aufrufe eliminierten und dann den CPU-Datentransfer an die GPU zu einem API Aufruf kombinierten.

„Unser Vegetationssystem besteht aus vielen verschiedenen Arten von Vegetation, wobei jede Art von Vegetation eine Berechnung erfordert“, erklärt Li. „Bei 50 Vegetationen wären das 50 Depeschen mit jeweils n Threads.“

Das Ziel jedes Threads war es, eine Instanzposition zusammen mit einigen anderen Daten zu berechnen. Es war aber auch sehr gut möglich, dass ein Thread eine gekeulte Position berechnete, entweder durch maskierte Position oder außerhalb des Kamerastumpfs. In diesem Fall werden die Daten nicht zum Instanzarray hinzugefügt, das später zum Zeichnen der Vegetation verwendet wird.

Gedeihen: Heavy Lies the Crown | Zugalu Entertainment | Playside Studios
Gedeihen: Heavy Lies the Crown | Zugalu Entertainment | Playside Studios

Da ein Thread dem Array hinzugefügt werden kann oder nicht, haben wir eine Form von threadsicherer Liste wie Datenstruktur verwendet, in der wir den gültigen Wert an die Liste angehängt haben. HLSL bietet diese Funktion bequem als Anhangpuffer an. „Die Verwendung eines Anhangpuffers hat einen kleinen Nachteil“, sagt Li. „Ich musste zusätzliche GPU-Befehle ausführen, um die Anzahl der hinzugefügten Elemente zu erfassen und diese Anzahl zu löschen, damit der Anhängepuffer wiederverwendet werden konnte.“

Der Compute Shader stellte jedoch eine praktische Variable bereit, die als Groupshared bezeichnet wird und eine Thread-zu-Thread-Kommunikation ermöglicht. In Kombination mit der Interlocked-Funktion konnte so für jede Sendung ein globaler Indexzähler im Auge behalten werden. Dadurch konnten gültige Instanzdaten eng gepackt und indirekte Draw-Befehle aktualisiert werden – alles innerhalb desselben Sendungsaufrufs, der die Instanzposition berechnete.

Beim Senden der CPU-Daten sah sich das Team zunächst mit einer Leistungsstrafe konfrontiert. Sie mussten die Shader-Eigenschaften für die verschiedenen Vegetationstypen aktualisieren, die sich von Frame zu Frame änderten.

„Ursprünglich habe ich die Daten separat gesendet, was zu 50 SetData()-Befehlen geführt hat“, sagt Li. „Da der zugrunde liegende Datentyp jedoch gleich war, haben wir alle Daten in einem Puffer konsolidiert und dann jedem Vegetationstyp einen Offset-Index in diesen Puffer bereitgestellt. Dies ermöglichte nur einen SetData()-Befehl.“

Das Team schätzt konservativ, dass es 0,1 Millisekunden CPU-Zeit sparte, was 20 % der gesamten 0,5 Millisekunden entspricht.

Gedeihen: Heavy Lies the Crown | Zugalu Entertainment | Playside Studios
Gedeihen: Heavy Lies the Crown | Zugalu Entertainment | Playside Studios

Senden von Kacheldaten von der CPU an die GPU

Da die Karte sehr groß war und ungefähr 10 Millionen Kacheln umfasste, hatte das Team Schwierigkeiten, die Kacheldaten von der CPU auf die GPU zu holen. „Der Versuch, Millionen von Kacheln pro Frame an die GPU zu senden, war sehr kostspielig, da viele Daten zu senden waren. Wir mussten in der Lage sein, eine Teilmenge von gerade genug Daten zu senden, um den Bildschirm zu belegen“, sagt Hau.

Dafür nutzten sie das Jobsystem von Unity. Es half ihnen für die CPU-Seite und bot eine Multithread-Möglichkeit, die Daten zu erfassen und an die GPU zu senden. Hau erklärt: „Wenn es darum geht, Daten aus einem Array zu holen, ist es eine perfekte Arbeitsbelastung, die durch das Jobsystem beschleunigt werden kann.“

Jeder Thread kann ausgeführt werden, um ein Datensegment zu erfassen, das dann in ein Ziel-Array kopiert wird. Gleichzeitig wandelten sie die ursprünglichen 16-Bit-Daten in gepackte 32-Bit-Daten um, die im Compute-Shader verwendet wurden.

Das Team wandte den Burst Compiler auch auf die Daten des Jobsystems an, um optimierten Code zu erstellen. „Der Burst Compiler hat die Multithread-Leistung erheblich verbessert. Sobald ich das Attribut dort platziert hatte, ging es schnell von über einer Millisekunde auf weniger als 0,3 Millisekunden. Es war sehr beeindruckend, nur eine Zeile Code hinzuzufügen“, sagt Hau.

Gedeihen: Heavy Lies the Crown | Zugalu Entertainment | Playside Studios
Gedeihen: Heavy Lies the Crown | Zugalu Entertainment | Playside Studios

Das Team konnte zwar große Optimierungsgewinne verzeichnen, ist sich aber auch bewusst, dass die Leistung beim Rendern der gesamten Vegetation etwas ist, auf das man achten sollte.

„Obwohl wir von der Leistungseinsparung begeistert sind, ist das Overdraw immer noch ein Problem, das mein System nicht löst“, erklärt Li. „Das müssen wir im Hinterkopf behalten. Trotzdem sind wir mehr als zufrieden damit, wie das Spiel ausgegangen ist.“

Weitere Informationen zu Projekten Made with Unity finden Sie auf der Seite Ressourcen.