Up to date

This page is up to date for Godot 4.2. If you still find outdated information, please open an issue.

GPU-Optimierungen

Einführung

Die Nachfrage nach neuen Grafik-Features und der Fortschritt garantieren fast, dass Sie auf Grafik-Bottlenecks stoßen werden. Einige davon können CPU-seitig sein, beispielsweise bei Berechnungen innerhalb der Godot-Engine um Objekte für das Rendern vorzubereiten. Bottlenecks können auch im Grafiktreiber der CPU auftreten, der Anweisungen sortiert, welche an die GPU übergeben werden sollen, und bei der Übertragung dieser Anweisungen. Und schließlich treten auch auf der GPU selbst Bottlenecks auf.

Wo beim Rendern Bottlenecks auftreten, ist sehr hardwarespezifisch. Insbesondere mobile GPUs haben möglicherweise Probleme mit Szenen, die problemlos auf dem Desktop ausgeführt werden können.

Das Verständnis und die Untersuchung von GPU Bottlenecks unterscheidet sich etwas von der Situation der CPU. Das liegt daran, dass man die Performance oft nur indirekt ändern kann, indem man die Anweisungen an die GPU ändert. Außerdem kann es schwieriger sein, Messungen vorzunehmen. In vielen Fällen besteht die einzige Möglichkeit, die Performance zu messen, darin, die Veränderungen in der für das Rendern der einzelnen Frames benötigten Zeit zu untersuchen.

Zeichenaufrufe, Statusänderungen und APIs

Bemerkung

Der folgende Abschnitt ist für Endbenutzer nicht relevant, aber nützlich, um Hintergrundinformationen zu geben, die in späteren Abschnitten wichtig sind.

Godot sendet Anweisungen an die GPU über eine Grafik-API (Vulkan, OpenGL, OpenGL ES oder WebGL). Die damit verbundene Kommunikation und Treiberaktivität kann recht kostspielig sein, insbesondere bei OpenGL, OpenGL ES und WebGL. Wenn wir diese Anweisungen auf eine Weise bereitstellen können, die vom Treiber und der GPU bevorzugt wird, können wir die Performance erheblich steigern.

Fast jeder API-Befehl in OpenGL erfordert ein gewisses Maß an Validierung, um sicherzustellen, dass sich die GPU im richtigen Zustand befindet. Selbst scheinbar einfache Befehle können zu einer Flut von Verwaltungsaufgaben hinter den Kulissen führen. Ziel ist es daher, diese Befehle auf ein absolutes Minimum zu reduzieren und ähnliche Objekte so weit wie möglich zu gruppieren, damit sie zusammen gerendert werden können, oder mit einer minimalen Anzahl dieser teuren Zustandsänderungen.

2D-Batching

In 2D können die Kosten für die Behandlung jedes einzelnen Objekts unerschwinglich hoch sein - es können leicht Tausende von ihnen auf dem Bildschirm zu sehen sein. Aus diesem Grund wird bei OpenGL-basierten Rendering-Methoden 2D-Batching verwendet. Mehrere ähnliche Elemente werden gruppiert und in einem Batch gerendert, und zwar über einen einzigen Zeichenaufruf, anstatt für jedes Element einen separaten Zeichenaufruf zu machen. Darüber hinaus bedeutet dies, dass Zustandsänderungen, Material- und Texturänderungen auf ein Minimum beschränkt werden können.

Vulkan-basierte Rendering-Methoden verwenden noch kein 2D-Batching. Da Zeichenaufrufe mit Vulkan im Vergleich zu OpenGL viel billiger sind, besteht weniger Bedarf an 2D-Batching (obwohl es in einigen Fällen immer noch von Vorteil sein kann).

3D-Batching

In 3D versuchen wir nach wie vor, Zeichenaufrufe und Zustandsänderungen zu minimieren. Es kann jedoch schwieriger sein, mehrere Objekte in einem einzigen Zeichenaufruf zusammenzufassen. 3D-Meshes bestehen in der Regel aus Hunderten oder Tausenden von Dreiecken, und die Kombination großer Meshes in Echtzeit ist unerschwinglich teuer. Die Kosten für das Zusammenfügen übersteigen schnell die Vorteile, wenn die Anzahl der Dreiecke pro Mesh steigt. Eine viel bessere Alternative ist das Verbinden von Meshes im Voraus (statische Meshes im Verhältnis zueinander). Dies kann von Künstlern oder programmatisch innerhalb von Godot mit Hilfe eines Add-ons erfolgen.

Auch das Zusammenfassen von Objekten in 3D hat seinen Preis. Mehrere Objekte, die als eines gerendert werden, können nicht einzeln entfernt werden. Eine ganze Stadt, die sich außerhalb des Bildschirms befindet, wird auch dann gerendert, wenn sie mit einem einzelnen Grashalm verbunden wird, der sich auf dem Bildschirm befindet. Daher sollten Sie beim Zusammenfügen von 3D-Objekten immer den Standort der Objekte und das Culling berücksichtigen. Trotzdem überwiegen die Vorteile des Zusammenfügens von statischen Objekten oft, insbesondere bei einer großen Anzahl von weit entfernten oder Objekten aus wenigen Polygonen.

Für weitere Informationen zu 3D-spezifische Optimierungen, siehe Optimierung der 3D-Performance.

Wiederverwendung von Shadern und Materialien

Der Godot-Renderer unterscheidet sich ein wenig von dem, was es sonst da draußen gibt. Er wurde entwickelt, um GPU-Zustandsänderungen so weit wie möglich zu minimieren. StandardMaterial3D leistet gute Arbeit bei der Wiederverwendung von Materialien, die ähnliche Shader benötigen. Wenn benutzerdefinierte Shader verwendet werden, stellen Sie sicher, dass sie so oft wie möglich wiederverwendet werden. Die Prioritäten von Godot sind:

  • Wiederverwendung von Materialien: Je weniger verschiedene Materialien in der Szene vorhanden sind, desto schneller wird das Rendering sein. Wenn eine Szene eine große Anzahl von Objekten hat (Hunderte oder Tausende), versuchen Sie, die Materialien wiederzuverwenden. Im schlimmsten Fall sollten Sie Atlanten verwenden, um die Anzahl der Texturänderungen zu verringern.

  • Wiederverwendung von Shadern: Wenn Materialien nicht wiederverwendet werden können, versuchen Sie zumindest, Shader wiederzuverwenden. Hinweis: Shader werden automatisch zwischen StandardMaterial3Ds wiederverwendet, die dieselbe Konfiguration haben (Features, die mit einem Kontrollkästchen aktiviert oder deaktiviert sind), auch wenn sie unterschiedliche Parameter haben.

Wenn eine Szene z.B. 20.000 Objekte mit jeweils 20.000 verschiedenen Materialien enthält, ist das Rendering langsam. Wenn die gleiche Szene 20.000 Objekte hat, aber nur 100 Materialien verwendet, ist das Rendering viel schneller.

Pixel-Kosten vs. Vertex-Kosten

Sie haben vielleicht schon gehört, dass ein Modell umso schneller gerendert wird, je weniger Polygone es hat. Das ist wirklich relativ und hängt von vielen Faktoren ab.

Auf einem modernen PC und einer modernen Konsole sind die Vertex-Kosten gering. GPUs haben ursprünglich nur Dreiecke gerendert. Das bedeutete, dass in jedem Frame:

  1. Alle Vertices von der CPU transformiert werden mussten (einschließlich Clipping).

  2. Alle Vertices vom Hauptspeicher aus an den GPU-Speicher gesendet werden mussten.

Heutzutage wird all dies von der GPU erledigt, was die Performance erheblich steigert. 3D-Künstler haben in der Regel ein falsches Bild von der Polycount-Performance, da 3D-Modellierungssoftware (wie Blender, 3ds Max usw.) die Geometrie im CPU-Speicher halten muss, damit sie bearbeitet werden kann, was die tatsächliche Performance verringert. Spiele-Engines verlassen sich mehr auf die GPU, so dass sie viele Dreiecke viel effizienter rendern können.

Bei mobilen Geräten sieht die Sache anders aus. PC- und Konsolen-GPUs sind brachiale Monster, die so viel Strom aus dem Stromnetz ziehen können, wie sie brauchen. Mobile GPUs sind auf eine winzige Batterie beschränkt, daher müssen sie viel stromsparender sein.

Um effizienter zu sein, versuchen mobile GPUs, Overdraw zu vermeiden. Overdraw tritt auf, wenn dasselbe Pixel auf dem Bildschirm mehr als einmal gerendert wird. Stellen Sie sich eine Stadt mit mehreren Gebäuden vor. GPUs wissen nicht, was sichtbar ist und was nicht, bis sie es zeichnen. Zum Beispiel könnte ein Haus gezeichnet werden und dann ein anderes Haus davor (was bedeutet, dass dasselbe Pixel zweimal gerendert wird). PC-Grafikprozessoren kümmern sich in der Regel nicht darum und stellen der Hardware einfach mehr Pixelprozessoren zur Verfügung, um die Performance zu steigern (was auch den Stromverbrauch erhöht).

Mehr Strom zu verbrauchen ist auf mobilen Geräten keine Option, daher verwenden mobile Geräte eine Technik namens Tile-Based-Rendering, die den Bildschirm in ein Raster unterteilt. Jede Zelle speichert die Liste der auf ihr gezeichneten Dreiecke und sortiert sie nach Tiefe, um das Overdraw zu minimieren. Diese Technik verbessert die Performance und reduziert den Stromverbrauch, geht aber zu Lasten der Vertex-Performance. Infolgedessen können weniger Vertices und Dreiecke zum Zeichnen verarbeitet werden.

Außerdem hat das Tile-Based-Rendering Probleme, wenn kleine Objekte mit viel Geometrie auf einem kleinen Teil des Bildschirms zu sehen sind. Dies zwingt mobile GPUs dazu, ein einzelnes Bildschirm-Tile stark zu belasten, was die Performance erheblich beeinträchtigt, da alle anderen Zellen auf die Fertigstellung warten müssen, bevor der Frame angezeigt wird.

Zusammenfassend lässt sich sagen, dass die Anzahl der Vertices auf mobilen Geräten keine Rolle spielt, dass aber eine Konzentration von Vertices in kleinen Teilen des Bildschirms vermieden werden sollte. Wenn ein Charakter, NPC, Fahrzeug usw. weit entfernt ist (was bedeutet, dass es winzig aussieht), verwenden Sie ein Modell mit geringerem Detailgrad (LOD). Selbst auf Desktop-GPUs sollten Dreiecke vermieden werden, die kleiner sind als die Größe eines Pixels auf dem Bildschirm.

Beachten Sie die zusätzlich erforderliche Vertex-Verarbeitung, wenn Sie Folgendes verwenden:

  • Skinning (Skelettanimation)

  • Morphs (Shape-Keys)

Pixel/Fragment-Shader und Füllrate

Im Gegensatz zur Vertex-Verarbeitung sind die Kosten für Fragment-Shading (pro Pixel) im Laufe der Jahre drastisch gestiegen. Die Bildschirmauflösungen haben zugenommen: Die Fläche eines 4K-Bildschirms beträgt 8.294.400 Pixel, gegenüber 307.200 bei einem alten 640×480-VGA-Bildschirm. Das ist die 27-fache Fläche! Auch die Komplexität von Fragment-Shadern ist explodiert. Physikbasiertes Rendering erfordert komplexe Berechnungen für jedes Fragment.

Sie können ganz einfach testen, ob ein Projekt eine Füllratenbegrenzung aufweist. Schalten Sie V-Sync aus, um eine Frameratenbegrenzung zu verhindern, und vergleichen Sie dann die Frames pro Sekunde bei der Ausführung mit einem großen Fenster mit denen bei der Ausführung mit einem sehr kleinen Fenster. Es kann auch von Vorteil sein, die Größe der Shadow-Map zu reduzieren, wenn Sie Schatten verwenden. In der Regel werden Sie feststellen, dass die FPS bei einem kleinen Fenster ziemlich stark ansteigen, was darauf hindeutet, dass Sie in gewissem Maße in der Füllrate eingeschränkt sind. Wenn sich die FPS hingegen kaum oder gar nicht erhöhen, liegt der Bottleneck woanders.

Sie können die Performance in einem Projekt mit Füllratenbegrenzung erhöhen, indem Sie den Arbeitsaufwand für die GPU reduzieren. Sie können dies tun, indem Sie den Shader vereinfachen (vielleicht deaktivieren Sie teure Optionen, wenn Sie ein StandardMaterial3D verwenden) oder die Anzahl und Größe der verwendeten Texturen reduzieren. Wenn Sie Partikel ohne Shading verwenden, sollten Sie auch in Erwägung ziehen, Vertex-Shading in ihrem Material zu erzwingen, um die Shading-Kosten zu senken.

Siehe auch

Auf unterstützter Hardware kann Variable Rate Shading verwendet werden, um die Kosten für die Shading-Verarbeitung zu reduzieren, ohne die Schärfe der Kanten des endgültigen Bildes zu beeinträchtigen.

Wenn Sie für mobile Geräte entwickeln, sollten Sie die einfachsten Shader verwenden, die Sie sich leisten können.

Texturen einlesen

Der andere Faktor bei Fragment-Shadern sind die Kosten für das Lesen von Texturen. Das Lesen von Texturen ist ein teurer Vorgang, vor allem, wenn mehrere Texturen in einem einzigen Fragment-Shader gelesen werden. Berücksichtigen Sie auch, dass die Filterung den Vorgang weiter verlangsamen kann (trilineare Filterung zwischen Mipmaps und Mittelwertbildung). Das Lesen von Texturen ist auch teuer in Bezug auf den Stromverbrauch, was auf Mobiltelefonen ein großes Problem darstellt.

Wenn Sie Shader von Drittanbietern verwenden oder Ihre eigenen Shader schreiben, versuchen Sie, Algorithmen zu verwenden, die so wenig Texturlesevorgänge wie möglich erfordern.

Texturkomprimierung

Godot komprimiert beim Import standardmäßig Texturen von 3D-Modellen (VRAM-Komprimierung). Diese Video-RAM-Komprimierung ist nicht so effizient wie beim Speichern von PNG oder JPG, steigert aber die Performance enorm, wenn genügend große Texturen gezeichnet werden.

Dies liegt daran, dass das Hauptziel der Texturkomprimierung die Bandbreitenreduzierung zwischen Speicher und GPU ist.

In 3D hängen die Formen von Objekten mehr von der Geometrie als von der Textur ab, sodass eine Komprimierung im Allgemeinen nicht erkennbar ist. In 2D hängt die Komprimierung stärker von den Formen innerhalb der Texturen ab, sodass die durch die 2D-Komprimierung resultierenden Artefakte stärker wahrgenommen werden.

Als Warnung: Die meisten Android-Geräte unterstützen keine Texturkomprimierung von Texturen mit Transparenz (nur undurchsichtig). Denken Sie also daran.

Bemerkung

Selbst in 3D sollte die VRAM-Komprimierung von "Pixel Art"-Texturen deaktiviert werden, da sie sich negativ auf ihr Aussehen auswirkt, ohne die Performance aufgrund ihrer geringen Auflösung wesentlich zu verbessern.

Nachbearbeitung und Schatten

Nachbearbeitungseffekte und Schatten können auch im Hinblick auf die Aktivität des Fragment-Shaders teuer sein. Testen Sie die Auswirkungen immer auf verschiedener Hardware.

Die Verringerung der Größe von Shadow-Maps kann die Performance erhöhen, sowohl was das Schreiben als auch das Lesen der Shadow-Maps angeht. Darüber hinaus ist die beste Möglichkeit, die Performance von Schatten zu verbessern, die Deaktivierung von Schatten für so viele Lichter und Objekte wie möglich. Bei kleineren oder weit entfernten OmniLights/SpotLights kann der Schatten oft deaktiviert werden, ohne dass sich dies optisch bemerkbar macht.

Transparenz und Blending

Transparente Objekte stellen ein besonderes Problem für die Rendering-Effizienz dar. Undurchsichtige Objekte (insbesondere in 3D) können im Wesentlichen in beliebiger Reihenfolge gerendert werden, und der Z-Puffer sorgt dafür, dass nur die vordersten Objekte schattiert werden. Bei transparenten oder überblendeten Objekten ist das anders. In den meisten Fällen können sie sich nicht auf den Z-Puffer verlassen und müssen in "Maler-Reihenfolge" gerendert werden (d. h. von hinten nach vorne), um korrekt auszusehen.

Transparente Objekte sind auch besonders schlecht für die Füllrate, da jedes Objekt gezeichnet werden muss, auch wenn später andere transparente Objekte darüber gezeichnet werden.

Undurchsichtige Gegenstände müssen dies nicht tun. Normalerweise können sie den Z-Puffer nutzen, indem sie zuerst nur in den Z-Puffer schreiben und dann nur den Fragment-Shader für das "gewinnende" Fragment ausführen, das sich bei einem bestimmten Pixel vorne befindet.

Transparenz ist besonders teuer, wenn sich mehrere transparente Objekte überschneiden. In der Regel ist es besser, transparente Bereiche so klein wie möglich zu halten, um diese Anforderungen an die Füllrate zu minimieren, insbesondere auf mobilen Geräten, wo die Füllrate sehr teuer ist. In vielen Situationen kann das Rendern von komplexeren undurchsichtigen Geometrien sogar schneller sein als die Verwendung von Transparenz zum "Vortäuschen".

Multi-Plattform-Tipps

Wenn Sie auf mehreren Plattformen veröffentlichen möchten, testen Sie früh und häufig auf allen Ihren Plattformen, insbesondere auf Mobilgeräten. Ein Spiel auf dem Desktop zu entwickeln und es in letzter Minute das Handy zu portieren, endet meist in einer Katastrophe.

Im Allgemeinen sollten Sie Ihr Spiel für den kleinsten gemeinsamen Nenner entwickeln und dann optionale Erweiterungen für leistungsfähigere Plattformen hinzufügen. So können Sie beispielsweise die Rendering-Methode "Kompatibilität" sowohl für Desktop- als auch für mobile Plattformen verwenden, wenn Sie für beide Plattformen entwickeln.

Mobile/Kachel-Renderer

Wie oben beschrieben, funktionieren GPUs auf mobilen Geräten ganz anders als GPUs auf dem Desktop. Die meisten Mobilgeräte verwenden Kachel-Renderer. Diese teilen den Bildschirm in Kacheln normaler Größe auf, die in den superschnellen Cache-Speicher passen, und reduzieren die Lese- und Schreibvorgänge zum Hauptspeicher.

Es gibt jedoch einige Nachteile, durch die bestimmte Methoden erheblich komplizierter und teurer werden können. Kacheln, die auf den Ergebnissen des Renderns verschiedener Kacheln oder auf den Ergebnissen früherer Vorgänge beruhen, können sehr langsam sein. Testen Sie unbedingt die Performance von Shadern, Viewport-Texturen und der Nachbearbeitung.