Dies ist der zweite Teil einer Reihe von Artikeln, die Optimierungs-Tipps für Ihre Unity-Projekte enthalten. Verwenden Sie sie als Leitfaden für die Ausführung mit höheren Bildraten und weniger Ressourcen. Wenn Sie diese bewährten Verfahren ausprobiert haben, sollten Sie sich die anderen Seiten dieser Reihe ansehen:
Mit den Grafikwerkzeugen von Unity können Sie optimierte Grafiken in jedem beliebigen Stil und für eine Vielzahl von Plattformen erstellen - von mobilen Geräten bis hin zu High-End-Konsolen und Desktop. Dieser Prozess hängt in der Regel von Ihrer künstlerischen Ausrichtung und Ihrer Render-Pipeline ab. Bevor Sie also loslegen, empfehlen wir Ihnen, sich die verfügbaren Render-Pipelines anzusehen.
Bei der Auswahl einer Rendering-Pipeline sollten Sie diese Überlegungen berücksichtigen. Neben der Auswahl einer Pipeline müssen Sie auch einen Rendering-Pfad auswählen.
Der Rendering-Pfad stellt eine bestimmte Reihe von Vorgängen in Bezug auf Beleuchtung und Schattierung dar. Die Entscheidung für einen Rendering-Pfad hängt von den Anforderungen und der Zielhardware Ihrer Anwendung ab.
Vorwärts-Rendering-Pfad
Forward Rendering wird sowohl in der Universal Render Pipeline (URP) als auch in der Eingebaute Render-Pipeline. Beim Forward-Rendering projiziert die Grafikkarte die Geometrie und zerlegt sie in Scheitelpunkte. Diese Scheitelpunkte werden weiter in Fragmente oder Pixel zerlegt, die auf dem Bildschirm dargestellt werden und das endgültige Bild ergeben.
Die Pipeline übergibt jedes Objekt einzeln an die Grafik-API. Forward Rendering ist mit Kosten für jedes Licht verbunden. Je mehr Lichter in Ihrer Szene vorhanden sind, desto länger dauert das Rendering.
Der Forward Renderer der Built-in Pipeline zeichnet jedes Licht in einem separaten Durchgang pro Objekt. Wenn mehrere Lichter auf dasselbe GameObject treffen, kann dies zu einer erheblichen Überzeichnung führen, wenn überlappende Bereiche denselben Pixel mehr als einmal zeichnen müssen. Um die Überzeichnung zu reduzieren, sollten Sie die Anzahl der Echtzeit-Lichter minimieren.
Anstatt einen Durchgang pro Licht zu rendern, wählt das URP die Lichter pro Objekt aus. Dadurch kann die Beleuchtung in einem einzigen Durchgang berechnet werden, was zu weniger Zeichenaufrufen im Vergleich zum Forward Renderer der Built-In Render Pipeline führt.
Die integrierte Render-Pipeline, die URP und die High Definition Render Pipeline (HDRP) verwenden ebenfalls den Deferred Shading-Rendering-Pfad. Beim Deferred Shading wird die Beleuchtung nicht pro Objekt berechnet.
Stattdessen verschiebt Deferred Shading schwere Renderingaufgaben, wie z. B. die Beleuchtung, auf eine spätere Phase und verwendet zwei Durchgänge. Im ersten Durchgang, der auch als G-Puffer genannt, rendert Unity die GameObjects. Dieser Durchgang ruft verschiedene Arten von geometrischen Eigenschaften ab und speichert sie in einem Satz von Texturen.
G-Puffer-Texturen können enthalten:
- Diffuse und spiegelnde Farben
- Glätte der Oberfläche
- Okklusion
- Weltraumnormale
- Emission + Umgebung + Reflektionen + Lichtkarten
Im zweiten Durchgang, dem Beleuchtungsdurchgang, rendert Unity die Beleuchtung der Szene auf der Grundlage des G-Buffers. Stellen Sie sich vor, Sie iterieren über jedes Pixel und berechnen die Beleuchtungsinformationen auf der Grundlage des Puffers anstelle der einzelnen Objekte. Das Hinzufügen von weiteren, nicht schattenwerfenden Lichtern im Deferred Shading führt nicht zu den gleichen Leistungseinbußen wie beim Forward Rendering.
Die Wahl eines Rendering-Pfads ist zwar keine Optimierung im eigentlichen Sinne, kann sich aber auf die Optimierung Ihres Projekts auswirken. Die anderen Techniken und Arbeitsabläufe in diesem Abschnitt hängen davon ab, welche Render-Pipeline und welchen Pfad Sie wählen.
Sowohl HDRP als auch URP unterstützen Shader Graph, eine knotenbasierte visuelle Schnittstelle für die Shadererstellung. Es ermöglicht Benutzern ohne Erfahrung in der Shader-Programmierung, komplexe Schattierungseffekte zu erstellen.
Über 150 Knoten sind derzeit in Shader Graph verfügbar. Außerdem können Sie mit der API Ihre eigenen benutzerdefinierten Knoten erstellen.
Jeder Shader in einem Shader-Graph beginnt mit einem Master-Knoten, der die Ausgabe des Graphen bestimmt. Konstruieren Sie die Shader-Logik durch Hinzufügen und Verbinden von Knoten und Operatoren innerhalb der visuellen Schnittstelle.
Der Shader-Graph geht dann in das Backend der Render-Pipeline. Das Endergebnis ist ein ShaderLab-Shader, der funktionell einem in HLSL oder Cg geschriebenen Shader ähnelt.
Die Optimierung eines Shader-Graphen folgt vielen der gleichen Regeln, die auch für herkömmliche HLSL- oder Cg-Shader gelten; eine wichtige davon ist, dass die Leistung Ihrer Anwendung umso stärker beeinträchtigt wird, je mehr Verarbeitung Ihr Shader-Graph durchführt.
Wenn Sie CPU-gebunden sind, wird die Optimierung Ihrer Shader die Framerate nicht verbessern, aber es könnte Ihre Akkulaufzeit für mobile Plattformen verbessern.
Wenn Sie GPU-gebunden sind, befolgen Sie diese Richtlinien zur Verbesserung der Leistung mit Shader Graph:
Entfernen Sie unbenutzte Knoten: Ändern Sie keine Standardeinstellungen und verbinden Sie keine Knoten, wenn diese Änderungen nicht notwendig sind. Shader Graph kompiliert alle nicht verwendeten Funktionen automatisch heraus. Wenn möglich, backen Sie Werte in Texturen. Anstatt z. B. einen Knoten zum Aufhellen einer Textur zu verwenden, können Sie die zusätzliche Helligkeit auf das Texturelement selbst anwenden.
Verwenden Sie nach Möglichkeit ein kleineres Datenformat: Ziehen Sie in Erwägung, Vector2 statt Vector3 zu verwenden oder die Genauigkeit zu verringern (z. B. halb statt float), wenn Ihr Projekt dies zulässt.
Reduzieren Sie mathematische Operationen: Shader-Operationen werden viele Male pro Sekunde ausgeführt, daher sollten Sie versuchen, mathematische Operatoren so weit wie möglich zu optimieren. Versuchen Sie, die Ergebnisse zu mischen, anstatt eine logische Verzweigung zu schaffen. Verwenden Sie Konstanten und kombinieren Sie skalare Werte, bevor Sie Vektoren anwenden. Konvertieren Sie schließlich alle Eigenschaften, die nicht im Inspektor erscheinen müssen, als Inline-Knoten. All diese schrittweisen Verbesserungen können Ihr Rahmenbudget entlasten.
Verzweigen Sie eine Vorschau: Je größer Ihr Diagramm wird, desto langsamer kann die Kompilierung werden. Vereinfachen Sie Ihren Arbeitsablauf mit einem separaten, kleineren Zweig, der nur die Vorgänge enthält, die Sie gerade in der Vorschau sehen möchten. Dann iterieren Sie schneller auf diesem kleineren Zweig, bis Sie die gewünschten Ergebnisse erzielen. Wenn der Zweig nicht mit dem Hauptknoten verbunden ist, können Sie den Vorschauzweig in Ihrem Diagramm belassen. Unity entfernt während der Kompilierung Knoten, die keinen Einfluss auf die endgültige Ausgabe haben.
Manuell optimieren: Selbst erfahrene Grafikprogrammierer können einen Shader-Graphen verwenden, um einen Standardcode für einen skriptbasierten Shader festzulegen. Wählen Sie das Shader-Grafik-Asset aus und wählen Sie dann Shader kopieren aus dem Kontextmenü. Erstellen Sie einen neuen HLSL/Cg-Shader und fügen Sie dann den kopierten Shader-Graphen ein. Dies ist ein einseitiger Vorgang, mit dem Sie jedoch durch manuelle Optimierungen zusätzliche Leistung erzielen können.
Entfernen Sie alle unbenutzten Shader aus der Liste " Immer enthaltene Shader", die Sie in den Grafikeinstellungen finden(Bearbeiten > Projekteinstellungen > Grafik). Fügen Sie hier alle Shader hinzu, die für die Lebensdauer der Anwendung benötigt werden.
Verwenden Sie die pragma-Direktiven für die Shader-Kompilierung, um die Kompilierung eines Shaders an die jeweilige Zielplattform anzupassen. Verwenden Sie dann ein Shader-Schlüsselwort (oder einen Shader-Graph-Schlüsselwortknoten ), um Shader-Varianten mit bestimmten aktivierten oder deaktivierten Funktionen zu erstellen.
Shader-Varianten können für plattformspezifische Funktionen nützlich sein, erhöhen aber auch die Erstellungszeit und die Dateigröße. Sie können verhindern, dass Shader-Varianten in Ihr Build aufgenommen werden, wenn Sie wissen, dass sie nicht erforderlich sind.
Analysieren Sie zunächst die Editor.log nach Shader-Timing und Größe. Suchen Sie dann die Zeilen, die mit " Kompilierter Shader " und " Komprimierter Shader" beginnen.
Dieses Beispielprotokoll zeigt die folgenden Statistiken:
Kompilierter Shader 'TEST Standard (Specular setup)' in 31.23s
d3d9 (interne Programme insgesamt: 482, einzigartig: 474)
d3d11 (interne Programme insgesamt: 482, einzigartig: 466)
Metall (gesamte interne Programme: 482, einzigartig: 480)
glcore (gesamte interne Programme: 482, einzigartig: 454)
Komprimierter Shader 'TEST Standard (Specular setup)' auf d3d9 von 1.04MB auf 0.14MB
Komprimierter Shader 'TEST Standard (Specular setup)' auf d3d11 von 1.39MB auf 0.12MB
Komprimierter Shader 'TEST Standard (Specular setup)' auf Metall von 2.56MB auf 0.20MB
Komprimierter Shader 'TEST Standard (Specular setup)' auf glcore von 2.04MB auf 0.15MB
Diese Statistiken verraten Ihnen einiges über den Shader:
- Aufgrund von #pragma multi_compile und shader_feature ergeben sich 482 Varianten.
- Unity komprimiert den in den Spieldaten enthaltenen Shader auf ungefähr die Summe der komprimierten Größen: 0,14+0,12+0,20+0,15 = 0,61 MB.
- Zur Laufzeit behält Unity die komprimierten Daten im Speicher (0,61 MB), während die Daten für Ihre aktuell verwendete Grafik-API unkomprimiert bleiben. Wenn Ihre aktuelle API beispielsweise Metall ist, würde das 2,56 MB ausmachen.
Nach einem Build wird der Projekt-Prüfer (experimental) das Editor.log analysieren, um eine Liste aller Shader, Shader-Schlüsselwörter und Shader-Varianten anzuzeigen, die in das Projekt kompiliert wurden. Es kann auch die Datei Spieler.log analysieren, nachdem das Spiel gelaufen ist. Dies zeigt Ihnen, welche Varianten die Anwendung tatsächlich kompiliert und zur Laufzeit verwendet hat.
Nutzen Sie diese Informationen, um ein skriptfähiges Shader-Stripping-System zu erstellen und die Anzahl der Varianten zu reduzieren. Dies kann die Erstellungszeiten, die Erstellungsgrößen und die Speichernutzung zur Laufzeit verbessern.
Lesen Sie den Artikel Stripping von skriptfähigen Shader-Varianten, um diesen Prozess im Detail zu sehen.
Anti-Aliasing trägt zu einer schärferen Bildqualität bei, indem es gezackte Kanten reduziert und Specular-Aliasing minimiert.
Wenn Sie Forward Rendering mit der integrierten Render-Pipeline verwenden, Multisample-Aliasierung (MSAA) in der Option Qualität Einstellungen verfügbar. MSAA erzeugt hochwertiges Anti-Aliasing, kann aber teuer sein. Die Einstellung " MSAA Sample Count" aus dem Dropdown-Menü legt fest, wie viele Samples der Renderer zur Bewertung des Effekts verwendet(None, 2X, 4X, 8X). Wenn Sie Forward Rendering mit URP oder HDRP verwenden, können Sie MSAA auf dem URP-Asset oder HDRP-Asset aktivieren.
Alternativ können Sie Anti-Aliasing auch als Nachbearbeitungseffekt hinzufügen. Diese erscheint in der Komponente Kamera (unter Anti-Aliasing) mit einer Reihe von Optionen:
- Fast Approximate Anti-aliasing (FXAA) glättet die Kanten auf Pixel-Ebene. Dies ist die am wenigsten ressourcenintensive Art des Anti-Aliasing. Das endgültige Bild wird dadurch leicht unscharf.
- Subpixel Morphological Anti-aliasing (SMAA) blendet Pixel auf der Grundlage der Ränder eines Bildes ein. Es bietet viel schärfere Ergebnisse als FXAA und eignet sich für flache, cartoonartige oder saubere Kunststile.
In HDRP können Sie auch FXAA und SMAA im Post-Processing Anti-Aliasing auf der Kamera mit einer zusätzlichen Option verwenden:
- Temporal Anti-aliasing (TAA) glättet Kanten unter Verwendung von Frames aus dem History-Buffer. Dies ist effektiver als FXAA, erfordert aber Bewegungsvektoren, um zu funktionieren. TAA kann auch Ambient Occlusion und Volumetrics verbessern. Die Qualität ist im Allgemeinen besser als bei FXAA, erfordert aber zusätzliche Ressourcen und kann gelegentlich Geisterbilder erzeugen.
Die schnellste Möglichkeit, eine Beleuchtung zu erstellen, ist eine, die nicht pro Bild berechnet werden muss. Verwenden Sie Lightmapping, um statische Beleuchtung nur einmal zu backen, anstatt sie in Echtzeit zu berechnen.
Fügen Sie Ihrer statischen Geometrie dramatische Beleuchtung mit Globale Beleuchtung (GI). Aktivieren Sie die Option GI beisteuern für Objekte, um hochwertige Beleuchtung in Form von Lichtkarten zu speichern.
Der Prozess der Erstellung einer Lightmapping-Umgebung dauert länger als das bloße Platzieren eines Lichts in der Szene, bietet aber wichtige Vorteile wie z. B.:
- Zwei- bis dreimal so schnell für Zwei-Pixel-Lichter
- Verbesserte Grafik durch Global Illumination, die realistisch aussehende, direkte und indirekte Beleuchtung berechnen kann, während der Lightmapper die resultierende Karte glättet und entrauscht
- Gebackene Schatten und Beleuchtung werden ohne die Leistungseinbußen gerendert, die normalerweise bei Echtzeit-Beleuchtung und -Schatten auftreten
Komplexere Szenen können lange Backzeiten erfordern. Wenn Ihre Hardware den Progressiver GPU-Lichtmapper (in der Vorschau) unterstützt, kann diese Option die Erzeugung von Lichtmustern drastisch beschleunigen, indem sie die GPU statt der CPU nutzt.
Folgen Sie dieser Anleitung, um mit Lightmapping in Unity zu beginnen.
Reflexionssonden können zwar realistische Reflexionen erzeugen, sind aber in Bezug auf die Anzahl der Chargen sehr kostspielig. Probieren Sie daher diese Optimierungstipps aus, um die Auswirkungen auf die Leistung zu minimieren:
- Verwenden Sie Cubemaps mit niedriger Auflösung, Culling-Masken und Texturkompression, um die Laufzeitleistung zu verbessern.
- Verwenden Sie Typ: Gebackenes, um Aktualisierungen pro Frame zu vermeiden.
- Wenn die Verwendung von Type: Echtzeit ist in URP notwendig, versuchen Sie, Every Frame zu vermeiden, wann immer es möglich ist. Einstellen des Aktualisierungsmodus und Zeitsplitting Einstellungen an, um die Aktualisierungsrate zu verringern. Sie können die Aktualisierung auch mit der Option Über Skripting steuern und die Sonde über ein benutzerdefiniertes Skript rendern.
- Wenn die Verwendung von Type: Echtzeit in HDRP erforderlich ist, wählen Sie den Modus On Demand. Sie können die Rahmeneinstellungen auch unter Projekteinstellungen > HDRP-Standardeinstellungen ändern.
- Reduzieren Sie die Qualität und die Funktionen unter Echtzeitreflexion, um die Leistung zu verbessern.
Schattenwurf kann per Mesh Renderer und Licht deaktiviert werden. Deaktivieren Sie Schatten, wann immer es möglich ist, um die Anzahl der Zeichenaufrufe zu verringern. Sie können auch falsche Schatten erzeugen, indem Sie eine unscharfe Textur auf ein einfaches Mesh oder ein Quad unter Ihren Figuren anwenden. Ansonsten können Sie Blob-Schatten mit benutzerdefinierten Shadern erstellen.
Vermeiden Sie insbesondere die Aktivierung von Schatten für Punktlichter. Für jedes Punktlicht mit Schatten sind sechs Shadow-Map-Durchläufe pro Licht erforderlich - im Vergleich zu einem einzigen Shadow-Map-Durchlauf für ein Spot-Licht. Ziehen Sie in Erwägung, Punktlichter durch Punktlichter zu ersetzen, wenn dynamische Schatten unbedingt erforderlich sind. Wenn Sie dynamische Schatten vermeiden können, verwenden Sie eine Cubemap als Licht.cookie mit Ihren Punktlichtern.
In einigen Fällen können Sie einfache Tricks anwenden, anstatt mehrere zusätzliche Lichter hinzuzufügen. Anstatt z. B. ein Licht zu erzeugen, das direkt in die Kamera strahlt, um einen Randbeleuchtungseffekt zu erzielen, können Sie einen Shader verwenden, um die Randbeleuchtung zu simulieren (siehe Beispiele für Oberflächenshader für eine Implementierung dieser Funktion in HLSL).
Bei komplexen Szenen mit vielen Lichtern trennen Sie Ihre Objekte mit Hilfe von Ebenen und beschränken dann den Einfluss jedes Lichts auf eine bestimmte Culling-Maske.
Light Probes speichern gebackene Beleuchtungsinformationen über den leeren Raum in Ihrer Szene und bieten gleichzeitig eine hochwertige Beleuchtung (sowohl direkt als auch indirekt). Sie verwenden sphärische Oberschwingungen, die sich im Vergleich zu dynamischen Lichtern schnell berechnen lassen. Dies ist besonders nützlich für sich bewegende Objekte, die normalerweise kein Baked Lightmapping erhalten können.
Lichtsonden können auch auf statische Netze angewendet werden. Suchen Sie in der Komponente Mesh Renderer das Dropdown-Menü Receive Global Illumination und schalten Sie es von Lightmaps auf Light Probes um.
Verwenden Sie weiterhin Lightmapping für Ihre markante Ebenengeometrie, aber wechseln Sie zu Light Probes für die Beleuchtung kleinerer Details. Für die Light Probe-Beleuchtung sind keine eigenen UVs erforderlich, was Ihnen den zusätzlichen Schritt des Entpackens Ihrer Meshes erspart. Sonden sparen auch Speicherplatz, da sie keine Lightmap-Texturen erzeugen.
Weitere Informationen finden Sie im Beitrag Statische Beleuchtung mit Light Probes sowie in Making believable visuals in Unity.
Einer unserer umfassendsten Leitfäden aller Zeiten enthält über 80 umsetzbare Tipps zur Optimierung Ihrer Spiele für PC und Konsole. Diese ausführlichen Tipps wurden von unseren Experten von Success und Accelerate Solutions erstellt und helfen Ihnen, das Beste aus Unity herauszuholen und die Leistung Ihres Spiels zu steigern.