Leistungsempfehlungen für Unity

Dieser Artikel baut auf den Leistungsempfehlungen für Mixed Reality auf, konzentriert sich aber auf Unity-spezifische Verbesserungen.

Wir haben vor Kurzem eine Anwendung namens Quality Fundamentals veröffentlicht, deren Gegenstand allgemeine Leistungs-, Entwurfs- und Umgebungsprobleme und -lösungen für HoloLens 2 sind. Diese App ist eine hervorragende visuelle Demo für die folgenden Inhalte.

Der wichtigste erste Schritt beim Optimieren der Leistung von Mixed Reality-Apps in Unity besteht darin, sicherzustellen, dass Sie die empfohlenen Umgebungseinstellungen für Unity verwenden. Der betreffende Artikel enthält Inhalte mit einigen der wichtigsten Szenenkonfigurationen zum Entwickeln leistungsfähiger Mixed Reality-Apps. Einige dieser empfohlenen Einstellungen werden unten ebenfalls hervorgehoben.

So erstellen Sie ein Profil mit Unity

Unity bietet den integrierten Unity Profiler , der eine hervorragende Ressource zum Sammeln wertvoller Einblicke zur Leistung für Ihre bestimmte App darstellt. Der Profiler kann zwar im Editor ausgeführt werden, diese Metriken stellen aber nicht die reale Laufzeitumgebung dar, daher sollten Ergebnisse aus diesen Profilerläufen mit Umsicht angewendet werden. Es wird empfohlen, ein Remote-Profil Ihrer Anwendung zu erstellen, während sie auf dem Gerät läuft, um möglichst genaue und verwertbare Erkenntnisse zu erhalten.

Unity bietet eine hervorragende Dokumentation zu diesen Punkten:

  1. Herstellen einer Remoteverbindung zwischen dem Unity-Profiler und UWP-Anwendungen
  2. Effektive Diagnose von Leistungsproblemen mit dem Unity Profiler

GPU-Profilerstellung

Unity-Profiler

Bei verbundenem Unity Profiler und nach dem Hinzufügen des GPU-Profilers (siehe dazu Add Profiler („Profiler hinzufügen“) in der oberen rechten Ecke) lässt sich in der Mitte des Profilers ablesen, wie viel Zeit für CPU bzw. GPU aufgewendet wird. Dadurch kann der Entwickler näherungsweise einschätzen, ob seine Anwendung CPU- oder GPU-lastig ist.

Unity-CPU im Vergleich zu -GPU

Hinweis

Um die GPU-Profilerstellung zu verwenden, müssen Sie in den Unity Player-Einstellungen die Grafikaufträge deaktivieren. Weitere Details finden Sie im GPU-Nutzungsprofiler-Modul von Unity.

Unity-Framedebugger

Frame Debugger von Unity stellt ebenfalls ein leistungsstarkes Tool dar, mit dem sich Erkenntnisse gewinnen lassen. Sie erhalten einen guten Überblick darüber, was die GPU für jeden Frame ausführt. Worauf Sie achten sollten, sind zusätzliche Rendering-Ziele und Blit-Befehle zum Kopieren zwischen ihnen, da diese auf der HoloLens sehr teuer sind. Idealerweise sollten auf HoloLens keine Renderziele außerhalb des Bildschirms verwendet werden. Diese werden in der Regel hinzugefügt, wenn teure Rendering-Features aktiviert werden (z.B. MSAA, HDR oder Vollbild-Effekte wie Bloom), die Sie vermeiden sollten.

HoloLens Framerate-Überlagerung

Die Seite Systemleistung des Geräteportals enthält eine gute Zusammenfassung der CPU- und GPU-Leistung des Geräts. Sie können Bildfrequenzzähler im Headset anzeigen und Bildfrequenzdiagramm im Headset anzeigen aktivieren. Diese Optionen aktivieren einen FPS-Zähler bzw. ein Diagramm, das Ihnen in jeder laufenden Anwendung auf Ihrem Gerät sofortiges Feedback gibt.

PIX

PIX kann auch zum Profilieren von Unity-Anwendungen verwendet werden. Es gibt auch detaillierte Anweisungen zur Verwendung und Installation von PIX für HoloLens 2. In einem Entwicklungsbuild werden die gleichen Bereiche, die Sie im Frame Debugger von Unity sehen, auch in PIX angezeigt und können genauer überprüft und profiliert werden.

Hinweis

Unity bietet die Möglichkeit, die Auflösung des Renderziels Ihrer Anwendung zur Laufzeit mithilfe der Eigenschaft XRSettings.renderViewportScale auf einfache Weise zu ändern. Das endgültige Bild, das auf dem Gerät angezeigt wird, hat eine feste Auflösung. Die Plattform führt ein Sampling der Ausgabe mit niedrigerer Auflösung durch, um ein Bild mit höherer Auflösung zum Rendern auf Displays zu erstellen.

UnityEngine.XR.XRSettings.renderViewportScale = 0.7f;

CPU-Leistungsempfehlungen

Die Inhalte unten befassen sich mit tiefgreifenden Praktiken zur Leistungsverbesserung, insbesondere für die Entwicklung mit Unity und C# bestimmt.

Zwischenspeichern von Verweisen

Wir empfehlen, Verweise auf alle relevanten Komponenten und GameObjects bei der Initialisierung zwischenzuspeichern, da wiederholte Funktionsaufrufe wie GetComponent<T>() und Camera.main relativ zu den Speicherkosten für die Speicherung eines Zeigers teurer sind. . Camera.main basiert auf der zugrundeliegenden Funktion FindGameObjectsWithTag() , die aufwändig Ihre Szenendarstellung nach einem Kameraobjekt mit dem Tag „MainCamera“ absucht.

using UnityEngine;
using System.Collections;

public class ExampleClass : MonoBehaviour
{
    private Camera cam;
    private CustomComponent comp;

    void Start() 
    {
        cam = Camera.main;
        comp = GetComponent<CustomComponent>();
    }

    void Update()
    {
        // Good
        this.transform.position = cam.transform.position + cam.transform.forward * 10.0f;

        // Bad
        this.transform.position = Camera.main.transform.position + Camera.main.transform.forward * 10.0f;

        // Good
        comp.DoSomethingAwesome();

        // Bad
        GetComponent<CustomComponent>().DoSomethingAwesome();
    }
}

Hinweis

Vermeiden von GetComponent(string)
Beim Verwenden von GetComponent() haben Sie es mit einer handvoll verschiedener Überladungen zu tun. Es ist wichtig, immer die typbasierten Implementierungen und nie die zeichenfolgenbasierte Suchüberladung zu verwenden. Die Suche nach der Zeichenfolge in Ihrer Szene ist erheblich teurer als die Suche nach dem Typ.
(Gut) Component GetComponent(Typ Typ)
(Gut) T GetComponent<T>()
(Schlecht) Component GetComponent(string)>

Vermeiden aufwändiger Vorgänge

  1. Vermeiden Sie die Verwendung von LINQ

    Zwar kann LINQ sauber und einfach zu lesen und zu schreiben sein, es erfordert im Allgemeinen aber mehr Berechnungen und Speicher, als sie für einen manuell geschriebenen Algorithmus erforderlich wären.

    // Example Code
    using System.Linq;
    
    List<int> data = new List<int>();
    data.Any(x => x > 10);
    
    var result = from x in data
                 where x > 10
                 select x;
    
  2. Allgemeine Unity-APIs

    Bestimmte Unity-APIs können zwar nützlich sein, sind in der Ausführung aber kostspielig. Die meisten davon beinhalten das Durchsuchen Ihres gesamten Szenendarstellung nach einer Liste passender Spielobjekte. Diese Vorgänge können im Allgemeinen durch das Zwischenspeichern von Verweisen oder das Implementieren einer Manager-Komponente für die GameObjects vermieden werden, um die Verweise zur Laufzeit zu verfolgen.

        GameObject.SendMessage()
        GameObject.BroadcastMessage()
        UnityEngine.Object.Find()
        UnityEngine.Object.FindWithTag()
        UnityEngine.Object.FindObjectOfType()
        UnityEngine.Object.FindObjectsOfType()
        UnityEngine.Object.FindGameObjectsWithTag()
        UnityEngine.Object.FindGameObjectsWithTag()
    

Hinweis

SendMessage() und BroadcastMessage() sollten unter allen Umständen entfernt werden. Diese Funktionen können der Größenordnung nach 1000mal langsamer als direkte Funktionsaufrufe sein.

  1. Vermeiden von Boxing

    Boxing ist ein Grundkonzept der C#- Sprache und ihrer Runtime. Dabei handelt es sich um den Prozess, Variablen eines Werttyps, wie etwa char, int, bool usw., mit Variablen vom Verweistyp zu umschließen. Wenn eine Variable eines Werttyps „geboxt“ ist, ist sie von einem System.Object umschlossen, das auf dem verwalteten Heap gespeichert wird. Speicher wird zugewiesen und muss gegebenenfalls beim Verwerfen vom Garbage Collector verarbeitet werden. Diese Zuweisungen und ihre Aufhebung bringen Leistungskosten mit sich und sind in vielen Szenarien unnötig oder können leicht durch preiswertere Alternativen ersetzt werden.

    Um das Boxing zu vermeiden, stellen Sie sicher, dass die Variablen, Felder und Eigenschaften, in denen Sie numerische Typen und Strukturen (einschließlich Nullable<T>) speichern, stark als spezifische Typen typisiert sind, wie z. B. int, float? oder MyStruct, anstatt ein Objekt zu verwenden. Wenn Sie diese Objekte in eine Liste einfügen, stellen Sie sicher, dass Sie eine stark typisierte Liste verwenden, z. B. List<int> anstatt List<object> oder ArrayList.

    Beispiel für Boxing in C#

    // boolean value type is boxed into object boxedMyVar on the heap
    bool myVar = true;
    object boxedMyVar = myVar;
    

Wiederholte Codepfade

Alle sich wiederholenden Unity-Rückruffunktionen (d. h. Update), die mehrmals pro Sekunde und/oder Frame ausgeführt werden, sollten sorgfältig geschrieben werden. Alle aufwändigen Vorgänge an dieser Stelle haben einen sehr großen und konsistenten Einfluss auf die Leistung.

  1. Leere Rückruffunktion

    Zwar mag der Code unten harmlos aussehen, und Sie neigen wohl dazu, ihn in Ihrer Anwendung zu belassen, vor allem, da sich jedes Unity-Skript automatisch mit einer Update-Methode initialisiert, diese leeren Rückrufe können aber teuer werden. Unity überquert bei der Arbeit ständig in beiden Richtungen eine Grenze zwischen verwaltetem und nicht verwaltetem Code, nämlich zwischen dem Code der UnityEngine und Ihrem Anwendungscode. Der Kontextwechsel beim Überqueren dieser Grenze ist ziemlich teuer, auch dann, wenn es gar nichts auszuführen gibt. Dies wird besonders dann problematisch, wenn Ihre App über 100e von Spielobjekten verfügt, die ihrerseits wiederholte leere Unity-Rückrufe verwenden.

    void Update()
    {
    }
    

Hinweis

Update () ist die gängigste Form dieses Leistungsproblems, aber andere sich wiederholende Unity-Rückrufe, wie z. B. die folgenden, können genauso schlecht sein, wenn nicht gar schlechter: FixedUpdate(), LateUpdate(), OnPostRender", OnPreRender(), OnRenderImage() usw.

  1. Vorgänge, die vorzugsweise einmal pro Frame ausgeführt werden sollten

    Die folgenden Unity-APIs stellen gängige Vorgänge für viele Holografie-Apps dar. Es ist zwar nicht immer möglich, aber oft können die Ergebnisse dieser Funktionen einmal berechnet und dann für einen bestimmten Frame im gesamten Bereich der Anwendung wiederverwendet werden.

    a) Es ist eine gute Praxis, eine dedizierte Singleton-Klasse oder einen Dienst einzusetzen, die bzw. der Ihren Raycast vom Anvisieren der Szene verarbeitet, und dieses Ergebnis dann in allen anderen Szenekomponenten wiederzuverwenden, statt wiederholte und identische Raycastvorgänge für jede Komponente auszulösen. In manchen Anwendungen sind möglicherweise Raycasts aus einem anderen Ursprung oder gegen andere LayerMasks (Ebenenmasken) erforderlich.

        UnityEngine.Physics.Raycast()
        UnityEngine.Physics.RaycastAll()
    

    b) Vermeiden Sie GetComponent()-Vorgänge in wiederholten Unity-Rückrufen wie Update(), indem Sie Verweise zwischenspeichern: in Start() oder Awake()

        UnityEngine.Object.GetComponent()
    

    c) Es ist eine bewährte Methode, nach Möglichkeit alle Objekte bei der Initialisierung zu instanziieren und Objektpooling zu verwenden, um die gesamte Laufzeit Ihrer Anwendung hindurch Spielobjekte zu recyceln und wiederzuverwenden

        UnityEngine.Object.Instantiate()
    
  2. Vermeiden von Schnittstellen und virtuellen Konstrukten

    Das Aufrufen von Funktionsaufrufen über Schnittstellen im Gegensatz zu direkten Objekten oder dem Aufrufen von virtuellen Funktionen ist oftmals sehr viel kostspieliger als die Verwendung direkter Konstrukte oder direkter Funktionsaufrufe. Wenn die virtuelle Funktion oder Schnittstelle nicht benötigt wird, sollte sie entfernt werden. Der Leistungspreis für diese Ansätze spricht aber für den Kompromiss, wenn ihre Verwendung die Zusammenarbeit bei der Entwicklung vereinfacht, die Lesebarkeit und die Wartbarkeit des Codes verbessert.

    Im Allgemeinen wird empfohlen, Felder und Funktionen nicht als virtuell zu markieren, es sei denn, es gibt eine klare Annahme, dass das betreffende Element überschrieben werden muss. Insbesondere im Umfeld stark frequentierter Codepfade, die mehrmals pro Frame oder auch nur einmal pro Frame aufgerufen werden, wie etwa eine UpdateUI()-Methode, sollten Sie vorsichtig sein.

  3. Vermeiden der Übergabe von Strukturen über den Wert

    Im Gegensatz zu Klassen sind Strukturen Werttypen, und bei der direkten Übergabe an eine Funktion werden ihre Inhalte in eine neu erstellte Instanz kopiert. Diese Kopie bewirkt höhere CPU-Kosten sowie zusätzlichen Speicherbedarf auf dem Stack. Für kleine Strukturen ist der Effekt minimal und daher akzeptabel. Bei Funktionen, die in jedem Frame wiederholt aufgerufen werden, sowie bei Funktionen, die große Strukturen akzeptieren, ändern Sie nach Möglichkeit die Funktionsdefinition, um durch Verweis zu übergeben. Weitere Informationen finden Sie hier

Verschiedenes

  1. Physische Effekte

    a) Im Allgemeinen ist die einfachste Möglichkeit, die physischen Effekte zu verbessern, die Menge an Zeit oder die Anzahl der Iterationen pro Sekunde einzuschränken, die zu ihrer Berechnung aufgewendet wird. Dadurch wird die Genauigkeit der Simulation reduziert. Mehr dazu finden Sie unter TimeManager in Unity

    b) Die verschiedenen Collidertypen in Unity weisen stark unterschiedliche Leistungsmerkmale auf. Der Auftrag unten listet Collider nach ihrer Leistungsfähigkeit auf, von den leistungsfähigsten links bis zu den am wenigsten leistungsfähigen Collidern rechts. Es ist wichtig, Gittermodell-Collider zu meiden, die erheblich teurer sind als die primitiven Collider.

    Kugel < Kapsel < Kasten <<< Gittermodell (konvex) < Gittermodell (nicht konvex)

    Weitere Informationen finden Sie unter Unity Physics Best Practices (Bewährte Methoden für physische Effekte in Unity).

  2. Animationen

    Deaktivieren Sie Leerlaufanimationen, indem Sie die Animatorkomponente deaktivieren (das Deaktivieren des Spielobjekts hat nicht die gleiche Wirkung). Vermeiden Sie Entwurfsmuster, bei denen ein Animator in einer Schleife sitzt und einen Wert ständig erneut festlegt. Diese Technik verursacht beträchtlichen Mehraufwand ohne positiven Effekt auf die Anwendung. Weitere Informationen finden Sie hier.

  3. Komplexe Algorithmen

    Wenn Ihre Anwendung komplexe Algorithmen verwendet, wie z. B. inverse Kinematik, Pfadsuche usw., suchen Sie nach einem einfacheren Ansatz, oder passen Sie relevante Einstellungen leistungsbezogen an.

CPU- im Vergleich mit GPU-Leistungsempfehlungen

Im Allgemeinen lässt sich das Leistungsverhältnis zwischen CPU und GPU auf die Zeichenaufrufe reduzieren, die an die Grafikkarte übermittelt werden. Um die Leistung zu verbessern, müssen Zeichenaufrufe strategisch a) verringert oder b) neu strukturiert werden, um optimale Ergebnisse zu erzielen. Da Zeichenaufrufe ihrerseits ressourcenintensiv sind, bewirkt ihre Verringerung auch eine Verringerung der insgesamt aufzuwendenden Arbeit. Ferner werden durch Statuswechsel zwischen Zeichenaufrufen aufwändige Schritte zur Validierung und Übersetzung im Grafiktreiber erforderlich, daher kann eine Neustrukturierung der Zeichenaufrufe Ihrer Anwendung mit dem Ziel, die Statuswechsel (d. h. verschiedene Materialien usw.) einzuschränken, die Leistung steigern.

Unity bietet einen hervorragenden Artikel, der einen Überblick hierzu gibt und tief in die Batchverarbeitung von Zeichenaufrufen für die Unity-Plattform einsteigt.

Single-Pass-Instanzrendering

Single-Pass-Instanzrendering in Unity ermöglicht es, die Zeichenaufrufe für jedes Auge auf einen instanziierten Zeichenaufruf zu reduzieren. Aufgrund der Cachekohärenz zwischen zwei Zeichenaufrufen kommt es hier auch zu einer gewissen Leistungsverbesserung bei der GPU.

Aktivieren dieser Funktion in Ihrem Unity-Projekt

  1. Öffnen Sie OpenXR-Einstellungen. (Navigieren Sie zu Bearbeiten>Projekteinstellungen>XR-Plug-In-Verwaltung>OpenXR.)
  2. Wählen Sie im Dropdownmenü Rendermodus die Option Single-Pass-Instanz aus.

Details zu diesem Renderingansatz finden Sie in den folgenden Artikeln von Unity.

Hinweis

Ein häufiges Problem beim Single-Pass-Instanzrendering tritt auf, wenn Entwickler bereits vorhandene benutzerdefinierte Shader einsetzen, die noch nicht für die Instanziierung geschrieben wurden. Nach dem Aktivieren dieses Features stellen die Entwickler möglicherweise fest, dass manche Spielobjekte nur für ein Auge gerendert werden. Dies hat den Grund, dass die zugeordneten benutzerdefinierten Shader nicht die geeigneten Eigenschaften für die Instanziierung aufweisen.

Informationen zum Beheben dieses Problems finden Sie unter Single Pass Stereo Rendering for HoloLens (Single-Pass-Stereorendering für HoloLens) von Unity

Statische Batchverarbeitung

Unity kann viele statische Objekte als Batch verarbeiten, um die Zeichenaufrufe an die GPU zu verringern. Die statische Batchverarbeitung funktioniert für die meisten Rendererobjekte in Unity, die 1) das Material gemeinsam haben und 2) alle als Static gekennzeichnet sind (Wählen Sie ein Objekt in Unity aus, und aktivieren Sie das Kontrollkästchen in der oberen rechten Ecke des Inspektors). GameObjects, die als Static gekennzeichnet sind, können nicht durch die Runtime Ihrer Anwendung bewegt werden. Daher kann es schwierig sein, statische Batchverarbeitung für HoloLens zu nutzen, bei der praktisch jedes Objekt platziert, bewegt, skaliert usw. werden muss. Bei immersiven Headsets kann statische Batchverarbeitung die Anzahl der Zeichenaufrufe drastisch reduzieren und die Leistung so verbessern.

Weitere Details erfahren Sie unter Static Batching (Statische Batchverarbeitung) unter Draw Call Batching in Unity (Batchverarbeitung von Zeichenaufrufen in Unity).

Dynamische Batchverarbeitung

Da es in der HoloLens-Entwicklung problematisch ist, Objekte als Static zu kennzeichnen, kann dynamische Batchverarbeitung ein hervorragendes Mittel darstellen, das Fehlen dieser Funktion zu kompensieren. Es kann auch bei immersiven Headsets nützlich sein. Die dynamische Batchverarbeitung in Unity kann aber schwierig zu aktivieren sein, da die GameObjects a) das gleiche Material verwenden und b) eine lange Liste weiterer Kriterien erfüllen müssen.

Die vollständige Liste finden Sie unter Dynamic Batching (Dynamische Batchverarbeitung) unter Draw Call Batching in Unity (Batchverarbeitung von Zeichenaufrufen in Unity). In den meisten Fällen wird die dynamische Batchverarbeitung von GameObjects ungültig, da die zugeordneten Gittermodelldaten nicht mehr als 300 Scheitelpunkte umfassen dürfen.

Andere Verfahren

Batchverarbeitung kann nur erfolgen, wenn mehrere GameObjects gemeinsam dasselbe Material verwenden können. Normalerweise steht dem das Erfordernis gegenüber, dass GameObjects eine eindeutige Textur für ihr jeweiliges Material besitzen müssen. Es ist üblich, Texturen in einer großen Textur zusammenzufassen, eine Methode, die als Texture Atlasing bezeichnet wird.

Darüber hinaus ist es vorzuziehen, Gittermodelle zu einem GameObject zu kombinieren, wo es möglich und vernünftig ist. Für jeden Renderer in Unity ist eine bestimmte Anzahl Zeichenaufrufe im Vergleich mit dem Übermitteln eines kombinierten Gittermodells unter einem Renderer typisch.

Hinweis

Beim Ändern von Eigenschaften von „Renderer.material“ zur Laufzeit wird eine Kopie des Materials erstellt, was die Batchverarbeitung möglicherweise unmöglich macht. Verwenden Sie Renderer.sharedMaterial, um die mehreren GameObjects gemeinsamen Materialeigenschaften zu ändern.

GPU-Leistungsempfehlungen

Weitere Informationen zum Optimieren des Grafikrenderings in Unity

Bandbreite und Füllraten

Beim Rendern eines Frames in der GPU ist eine Anwendung entweder an die Speicherbandbreite oder die Füllrate gebunden.

  • Speicherbandbreite ist die Rate der Lese- und Schreibvorgänge, die die GPU aus ihrem Arbeitsspeicher durchführen kann.
  • Füllrate bezieht sich auf die Pixel, die pro Sekunde von der GPU gezeichnet werden können.

Optimieren der gemeinsamen Nutzung des Tiefenpuffers

Es wird empfohlen, die gemeinsame Nutzung des Tiefenpuffers zu aktivieren, um die Hologrammstabilität zu optimieren. Wenn die tiefenbasierte Neuprojektion in der Spätphase mit dieser Einstellung aktiviert wird, wird die Wahl des 16-Bit-Tiefenformats anstelle des 24-Bit-Tiefenformats empfohlen. Die 16-Bit-Tiefenpuffer reduzieren die Bandbreite (und somit die erforderliche Leistung), die mit dem Tiefenpuffer-Datenverkehr einhergeht, drastisch. Dies kann sowohl den Stromverbrauch senken als auch die Leistung verbessern. Allerdings gibt es zwei mögliche negative Ergebnisse durch Einsatz des 16-Bit-Tiefenformats.

Z-Fighting

Die verringerte Genauigkeit des Tiefenbereichs erhöht die Wahrscheinlichkeit von Z-Fighting bei 16-Bit im Vergleich mit 24-Bit. Um diese Artefakte zu vermeiden, ändern Sie die Clippingebenen der Unity-Kamera für nah und fern, um der geringeren Genauigkeit Rechnung zu tragen. In HoloLens-basierten Anwendungen kann eine Clippingebene von 50 m anstelle des Unity-Standardwerts von 1000 m im Allgemeinen jegliches Z-Fighting beseitigen.

Deaktivierter Schablonenpuffer

Wenn Unity eine Rendertextur mit 16-Bit Tiefe erstellt, wird kein Schablonenpuffer erstellt. Beim Auswählen eines Formats mit 24-Bit Tiefe werden gemäß der Unity-Dokumentation ein 24-Bit-Z-Puffer und ein 8-Bit-Schablonenpuffer erstellt (wenn 32-Bit auf einem Gerät, z. B. HoloLens, möglich sind, was aber allgemein der Fall ist).

Vermeiden von Vollbildeffekten

Techniken, die im Vollbildmodus arbeiten, können teuer sein, da sie die Größenordnung von Millionen von Vorgängen pro Frame erreichen. Es wird empfohlen, Effekte der Nachbearbeitung wie Antialiasing, Blooming und mehr zu vermeiden.

Optimale Beleuchtungseinstellungen

Die globale Beleuchtung in Echtzeit in Unity kann herausragende visuelle Ergebnisse produzieren, bringt jedoch umfangreiche Beleuchtungsberechnungen mit sich. Wir empfehlen, die globale Beleuchtung in Echtzeit für jede Unity-Szenendatei mit dieser Einstellung zu deaktivieren: Window>Rendering>Lighting Settings> Uncheck Real-time Global Illumination (Fenster > Rendering > Beleuchtungseinstellungen > Globale Beleuchtung in Echtzeit deaktivieren).

Ferner wird empfohlen, jede Art von Schattenwurf zu deaktivieren, da dieser ebenfalls teure GPU-Durchgänge für eine Unity-Szene mit sich bringt. Schatten können durch Licht deaktiviert werden, lassen sich über die Qualitätseinstellungen aber auch im Ganzen steuern.

Edit>Project Settings (Bearbeiten > Projekteinstellungen), wählen Sie dann die Kategorie Quality (Qualität) aus > wählen Sie Low Quality (Niedrige Qualität) für die UWP-Plattform aus. Sie können auch einfach die Shadows-Eigenschaft (Schatten) auf Disable Shadows (Schatten deaktivieren) festlegen.

Wir empfehlen, für Ihre Modelle in Unity „Baked Lighting“ (vorab gerenderte Lichtdetails) zu verwenden.

Reduzieren der Polygonanzahl

Das Reduzieren der Polygonanzahl wird durch eins dieser Verfahren erreicht

  1. Entfernen von Objekten aus einer Szene
  2. Starke Herabsetzung der Medienobjekte, wodurch sich die Anzahl der Polygone für ein bestimmtes Gittermodell reduziert
  3. Implementieren eines Level of Detail (LOD) System (Detailstufensystems) in Ihrer Anwendung, bei der weit entfernte Objekte mit einer aus weniger Polygonen bestehenden Version der gleichen Geometrie gerendert werden

Grundlegendes zu Shadern in Unity

Eine einfache Annäherung für den Leistungsvergleich von Shadern besteht darin, die durchschnittliche Anzahl der Vorgänge zu bestimmen, die jeder zur Laufzeit ausführt. Dies kann in Unity sehr einfach erfolgen.

  1. Wählen Sie Ihr Shadermedienobjekt oder ein Material aus, und wählen Sie dann in der oberen rechten Ecke des Inspektorfensters das Zahnradsymbol aus, gefolgt von „Select Shader“ (Shader auswählen)

    Auswählen eines Shaders in Unity

  2. Wählen Sie bei ausgewähltem Shadermedienobjekt die Schaltfläche „Compile and show code“ (Compilieren und Code anzeigen) unter dem Inspektorfenster aus

    Kompilieren von Shadercode in Unity

  3. Suchen Sie nach dem Compilieren nach dem Statistikabschnitt in den Ergebnissen mit der Anzahl der verschiedenen Vorgänge sowohl für den Vertex- als auch für den Pixelshader (Hinweis: Pixelshader werden auch häufig als Fragmentshader bezeichnet)

    Standardshadervorgänge in Unity

Optimieren von Pixelshadern

Beim Blick auf die compilierten Statistikergebnisse unter Verwendung der Methode oben zeigt sich, dass der Fragmentshader im Mittel mehr Vorgänge als der Vertexshader ausführt. Der Fragmentshader, auch als Pixelshader bezeichnet, wird pro Pixel der Bildschirmausgabe ausgeführt, während der Vertexshader nur pro Scheitelpunkt aller Gittermodelle ausgeführt wird, die auf dem Bildschirm gezeichnet werden.

Daher weisen Fragmentshader aufgrund der vielen Beleuchtungsberechnungen nicht nur mehr Anweisungen auf, sie werden auch fast immer auf einem größeren Dataset ausgeführt. Wenn die Bildschirmausgabe beispielsweise ein Bild mit 2.000 mal 2.000 Pixeln ist, kann der Fragmentshader 2.000*2.000 = 4.000.000 Mal ausgeführt werden. Wenn zwei Augen gerendert werden, verdoppelt sich diese Zahl, da zwei Bildschirme vorhanden sind. Wenn eine Mixed Reality-Anwendung mehrere Durchgänge, Nachbearbeitungseffekte im Vollbildmodus und/oder Rendering mehrerer Gittermodelle für das gleiche Pixel aufweist, erhöht sich diese Zahl dramatisch.

Daher können mit dem Verringern der Zahl der Vorgänge im Fragmentshader im Allgemeinen viel größere Leistungssteigerungen als durch Optimierungen im Vertexshader erreicht werden.

Alternativen zu den Unity-Standardshadern

Anstatt ein physikalisch basiertes Rendering (PBR) oder einen anderen hochwertigen Shader zu verwenden, sollten Sie einen leistungsfähigeren und kostengünstigeren Shader nutzen. Im Mixed Reality-Toolkit steht der MRTK-Standardshader bereit, der für Mixed Reality-Projekte optimiert wurde.

Unity bietet darüber hinaus unlit (nicht beleuchtet), vertex lit (nach Scheitelpunkten beleuchtet), diffuse (diffus), und weitere vereinfachte Shaderoptionen, die im Vergleich schneller arbeiten als der Unity-Standardshader. Detailliertere Informationen finden Sie unter Usage and Performance of Built-in Shaders (Verwendung und Leistung der integrierten Shader).

Vorabladen der Shader

Verwenden Sie Vorabladen der Shader und andere Tricks, um die Shaderladezeiten zu optimieren. Insbesondere bedeutet Vorabladen der Shader, dass kein Hängen aufgrund von Shadercompilierung zur Laufzeit sichtbar wird.

Einschränken des Überzeichnens

In Unity kann Überzeichnung für eine Szene angezeigt werden, indem ein Wert im Zeichenmodusmenü in der oberen linken Ecke der Szenenansicht auf Overdraw (Überzeichnung) umgeschaltet wird.

Im Allgemeinen kann Überzeichnung durch rechtzeitiges Culling (Entfernen der Rückseiten) von Objekten abgemildert werden, bevor sie an die GPU gesendet werden. Unity bietet ausführliche Informationen zum Implementieren von Occlusion Culling (Verdeckungs-Culling) für ihre Engine.

Empfehlungen zum Arbeitsspeicher

Exzessive Zuweisungsvorgänge von Arbeitsspeicher und deren Aufhebung können in Ihrer Holografieanwendung negative Effekte zeigen, die zu inkonsistenter Leistung, eingefrorenen Frames und anderem abträglichem Verhalten führen. Das Verstehen der Arbeitsspeicheraspekte ist beim Entwickeln in Unity besonders wichtig, da die Arbeitsspeicherverwaltung vom Garbage Collector gesteuert wird.

Garbage Collection

Holografie-Apps verlieren Prozessorzeit an den Garbage Collector (GC), wenn der GC aktiviert wird, um Objekte zu analysieren, die sich während der Ausführung nicht mehr im Bereich befinden und deren Arbeitsspeicher freigegeben werden muss, damit er für die Wiederverwendung zur Verfügung steht. Ständiges Zuweisen und Freigeben macht häufigere Läufe des Garbage Collectors erforderlich, was Leistung und Benutzererfahrung beeinträchtigt.

Unity hat eine hervorragende Seite zur Verfügung gestellt, auf der die Arbeitsweise des Garbage Collectors detailliert beschrieben ist und Tipps zum Schreiben von – im Hinblick auf die Speicherverwaltung – effizienterem Code gegeben werden.

Eine der gängigsten Methoden, die zu einer übermäßigen Garbage Collection führt, besteht darin, in der Unity-Entwicklung keine Verweise auf Komponenten und Klassen zwischenzuspeichern. Alle Verweise sollten während Start() oder Awake() erfasst und in späteren Funktionen wie Update() oder LateUpdate() wiederverwendet werden.

Weitere schnelle Tipps:

  • Verwenden Sie die StringBuilder-Klasse von C#, um dynamisch zur Laufzeit komplexe Zeichenfolgen zu generieren
  • Entfernen Sie Aufrufe von Debug.Log(), wenn sie nicht mehr erforderlich sind, da sie in allen Buildversionen einer App trotzdem noch ausgeführt werden
  • Wenn Ihre Holografie-App allgemein viel Arbeitsspeicher erfordert, erwägen Sie das Aufrufen von System.GC.Collect() in Ladephasen etwa beim Anzeigen eines Lade- oder Übergangsbildschirms

Objektpooling

Objektpooling ist eine gängige Methode, um die Kosten für fortlaufende Objektzuordnungen und deren Aufhebung zu reduzieren. Dies erfolgt durch Zuordnen eines großen Pools identischer Objekte und Wiederverwendung inaktiver, verfügbarer Instanzen aus diesem Pool, statt im Lauf der Zeit ständig neue Objekte zu erstellen und zu entfernen. Objektpools eignen sich hervorragend für wiederverwendbare Komponenten, die im Rahmen einer App eine variable Lebensdauer haben.

Startleistung

Erwägen Sie, Ihre App mit einer kleineren Szene zu starten und dann SceneManager.LoadSceneAsync zu verwenden, um den Rest der Szene zu laden. Dadurch kann Ihre App so schnell wie möglich in einen interaktiven Zustand gelangen. Beim Aktivieren der neuen Szene kann es zu einer großen CPU-Spitze kommen, und alle gerenderten Inhalte werden möglicherweise stockend oder hängend ausgeführt. Eine Möglichkeit, dies zu umgehen, besteht darin, die AsyncOperation.allowSceneActivation-Eigenschaft für die zu ladende Szene auf „false“ festzulegen, das Laden der Szene abzuwarten, den Bildschirm auf schwarz zu setzen und den Wert der Eigenschaft dann wieder auf „true“ festzulegen, um die Szenenaktivierung abzuschließen.

Beachten Sie, dass während des Ladens der Startszene für den Benutzer der holografische Begrüßungsbildschirm angezeigt wird.

Siehe auch