Leistungsempfehlungen für Unity

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

Wir haben vor kurzem eine Anwendung namens Quality Fundamentals veröffentlicht, die allgemeine Leistungs-, Design- und Umgebungsprobleme sowie Lösungen für HoloLens 2-Apps abdeckt. Diese App ist eine großartige 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. Dieser Artikel enthält Inhalte mit einigen der wichtigsten Szenenkonfigurationen für die Erstellung leistungsfähiger Mixed Reality-Apps. Im folgenden Abschnitt werden einige dieser empfohlenen Einstellungen hervorgehoben.

Erstellen eines Profils mit Unity

Unity bietet den integrierten Unity Profiler , der eine hervorragende Ressource ist, um wertvolle Performance-Informationen für Ihre spezielle App zu sammeln. Obwohl du den Profiler im Editor ausführen kannst, stellen diese Metriken nicht die wahre Laufzeitumgebung dar, sodass die Ergebnisse mit Vorsicht verwendet werden sollten. Erstellen Sie remote ein Profil Ihrer Anwendung, während Sie sie auf dem Gerät ausführen, um die genauesten und umsetzbarsten Erkenntnisse zu erhalten.

Unity bietet hervorragende Dokumentation für:

  1. So verbinden Sie den Unity-Profiler remote mit UWP-Anwendungen
  2. Effektive Diagnose von Leistungsproblemen mit dem Unity Profiler

GPU-Profilerstellung

Unity Profiler

Wenn der Unity Profiler angeschlossen ist und der GPU-Profiler hinzugefügt wurde (siehe Profiler oben rechts hinzufügen), kann man sehen, wie viel Zeit auf der CPU & GPU bzw. in der Mitte des Profilers verbracht wird. Dies ermöglicht dem Entwickler eine schnelle Annäherung, ob seine Anwendung CPU- oder GPU-gebunden ist.

Unity CPU vs. GPU

Hinweis

Um GPU-Profiling zu verwenden, müssen Sie Grafikaufträge in den Unity Player-Einstellungen deaktivieren. Weitere Informationen finden Sie im GPU Usage Profiler-Modul von Unity.

Unity-Framedebugger

Der Frame-Debugger von Unity ist ebenfalls ein leistungsstarkes und aufschlussreiches Tool. Es gibt Ihnen einen guten Überblick darüber, was die GPU in jedem Frame tut. Zu den Dingen, auf die Sie achten sollten, sind andere Renderziele und Blit-Befehle, die zwischen diesen kopiert werden können, da diese bei HoloLens teuer sind. Idealerweise sollten auf HoloLens keine Off-Screen-Renderziele verwendet werden. Diese werden hinzugefügt, wenn teure Renderingfunktionen (z. B. MSAA, HDR oder Vollbildeffekte wie Bloom) aktiviert werden, die vermieden werden sollten.

HoloLens-Bildratenüberlagerung

Auf der Seite "Systemleistung im Geräteportal" finden Sie eine gute Zusammenfassung der CPU- und GPU-Leistung des Geräts. Sie können " Bildfrequenzzähler anzeigen" im Headset und "Bildratendiagramm im Headset anzeigen" aktivieren. Diese Optionen aktivieren einen FPS-Zähler bzw. ein Diagramm, das Ihnen in jeder ausgeführten Anwendung auf Ihrem Gerät sofortiges Feedback gibt.

PIX

PIX kann auch für die Profilerstellung 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 Framedebugger von Unity sehen, auch in PIX angezeigt und können genauer inspiziert und profiliert werden.

Hinweis

Unity bietet die Möglichkeit, die Renderzielauflösung Ihrer Anwendung zur Laufzeit über die XRSettings.renderViewportScale-Eigenschaft einfach zu ändern. Das endgültige Bild, das auf dem Gerät angezeigt wird, hat eine feste Auflösung. Die Plattform erfasst die Ausgabe mit geringerer Auflösung, um ein Bild mit höherer Auflösung für das Rendering auf Displays zu erstellen.

UnityEngine.XR.XRSettings.renderViewportScale = 0.7f;

Empfehlungen für die CPU-Leistung

Der folgende Inhalt behandelt ausführlichere Leistungspraktiken, insbesondere für die Entwicklung von Unity & C#.

Cacheverweise

Es wird empfohlen, Verweise auf alle relevanten Komponenten und GameObjects bei der Initialisierung zwischenzuspeichern, da wiederholte Funktionsaufrufe wie GetComponent<T>() und Camera.main im Verhältnis zu den Speicherkosten für die Speicherung eines Zeigers teurer sind. Camera.main verwendet nur FindGameObjectsWithTag() darunter, das Ihr Szenendiagramm teuer nach einem Kameraobjekt mit dem Tag "MainCamera" durchsucht.

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

Avoid GetComponent(string)
Bei Verwendung von GetComponent() gibt es eine Handvoll verschiedener Überladungen. Es ist wichtig, immer die typbasierten Implementierungen und niemals die überladung der zeichenfolgenbasierten Suche zu verwenden. Die Suche nach Zeichenfolge in Ihrer Szene ist kostspieliger als die Suche nach Typ.
(Gut) Component GetComponent(Type type)
(Gut) T GetComponent<T>()
(Schlecht) Component GetComponent(string)>

Vermeiden Sie teure Vorgänge

  1. Vermeiden der Verwendung von LINQ

    Obwohl LINQ sauber und einfach zu lesen und zu schreiben ist, erfordert es im Allgemeinen mehr Rechen- und Arbeitsspeicher, als wenn Sie den Algorithmus manuell geschrieben hätten.

    // 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 sind zwar nützlich, können aber teuer in der Ausführung sein. Bei den meisten davon müssen Sie Ihr gesamtes Szenendiagramm nach einer passenden Liste von GameObjects durchsuchen. Diese Vorgänge können im Allgemeinen vermieden werden, indem Verweise zwischengespeichert oder eine Managerkomponente für die GameObjects implementiert werden, um die Verweise zur Laufzeit nachzuverfolgen.

        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 um jeden Preis eliminiert werden. Diese Funktionen können in der Größenordnung von 1000x langsamer sein als direkte Funktionsaufrufe.

  1. Vorsicht vor Boxen

    Boxing ist ein Kernkonzept der Sprache und Laufzeit von C#. Es ist der Prozess des Umbruchs von werttypisierten Variablen wie char, int, bool, usw. in referenztypisierte Variablen. Wenn eine werttypisierte Variable "boxed" wird, wird sie in ein System.Objectumschlossen, das auf dem verwalteten Heap gespeichert ist. Der Speicher wird zugewiesen und muss schließlich nach dem Entsorgen vom Garbage Collector verarbeitet werden. Diese Zuordnungen und Zuordnungen verursachen Leistungskosten und sind in vielen Szenarien unnötig oder können leicht durch eine kostengünstigere Alternative ersetzt werden.

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

    Beispiel für Boxen 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 (z. B. Update), die viele Male pro Sekunde und/oder Frame ausgeführt werden, sollten sorgfältig geschrieben werden. Alle teuren Vorgänge hier haben enorme und konsistente Auswirkungen auf die Leistung.

  1. Leere Rückruffunktionen

    Obwohl der folgende Code in Ihrer Anwendung unschuldig erscheinen mag, zumal jedes Unity-Skript automatisch mit einer Update-Methode initialisiert wird, können diese leeren Rückrufe teuer werden. Unity arbeitet zwischen einer nicht verwalteten und einer verwalteten Codegrenze, zwischen UnityEngine-Code und Ihrem Anwendungscode hin und her. Der Kontextwechsel über diese Brücke ist ziemlich teuer, auch wenn nichts auszuführen ist. Dies wird besonders problematisch, wenn Ihre App über Hunderte von GameObjects mit Komponenten verfügt, die leere sich wiederholende Unity-Rückrufe haben.

    void Update()
    {
    }
    

Hinweis

Update() ist die häufigste Manifestation dieses Leistungsproblems, aber andere sich wiederholende Unity-Rückrufe, wie z. B. die folgenden, können genauso schlimm, wenn nicht sogar schlimmer sein: FixedUpdate(), LateUpdate(), OnPostRender", OnPreRender(), OnRenderImage(), usw.

  1. Bevorzugte Ausführung von Operationen pro Frame

    Die folgenden Unity-APIs sind allgemeine Vorgänge für viele holographische Apps. Obwohl dies nicht immer möglich ist, können die Ergebnisse dieser Funktionen in der Regel einmal berechnet und die Ergebnisse in der gesamten Anwendung für einen bestimmten Frame wiederverwendet werden.

    1. Es empfiehlt sich, eine dedizierte Singleton-Klasse oder einen Dienst zu haben, die Ihren Blick auf Raycast in die Szene übertragen und dieses Ergebnis dann in allen anderen Szenenkomponenten wiederverwenden, anstatt wiederholte und identische Raycast-Operationen für jede Komponente durchzuführen. Einige Anwendungen erfordern möglicherweise Raycasts aus verschiedenen Ursprüngen oder gegen verschiedene LayerMasks.
        UnityEngine.Physics.Raycast()
        UnityEngine.Physics.RaycastAll()
    
    1. Vermeiden Sie GetComponent()-Vorgänge in wiederholten Unity-Rückrufen wie Update(), indem Sie Referenzen in Start() oder Awake() zwischenspeichern.
        UnityEngine.Object.GetComponent()
    
    1. Es wird empfohlen, alle Objekte nach Möglichkeit bei der Initialisierung zu instanziieren und das Objektpooling zu verwenden, um GameObjects während der gesamten Laufzeit Ihrer Anwendung zu recyceln und wiederzuverwenden
        UnityEngine.Object.Instantiate()
    
  2. Vermeiden Sie Schnittstellen und virtuelle Konstrukte

    Das Aufrufen von Funktionsaufrufen über Schnittstellen im Vergleich zu direkten Objekten oder das Aufrufen virtueller Funktionen kann oft viel teurer sein als die Verwendung direkter Konstrukte oder direkter Funktionsaufrufe. Wenn die virtuelle Funktion oder Schnittstelle unnötig ist, sollte sie entfernt werden. Der Leistungseinbruch bei diesen Ansätzen ist jedoch den Kompromiss wert, wenn ihre Verwendung die Entwicklungszusammenarbeit, die Lesbarkeit des Codes und die Wartbarkeit des Codes vereinfacht.

    Im Allgemeinen wird empfohlen, Felder und Funktionen nicht als virtuell zu markieren, es sei denn, es besteht eine klare Erwartung, dass dieses Element überschrieben werden muss. Besonders vorsichtig sollte man bei hochfrequenten Codepfaden sein, die viele Male pro Frame oder sogar einmal pro Frame aufgerufen werden, wie z. B. eine UpdateUI() Methode.

  3. Vermeiden Sie es, Strukturen als Wert zu übergeben

    Im Gegensatz zu Klassen sind Strukturen Werttypen, und wenn sie direkt an eine Funktion übergeben werden, wird ihr Inhalt in eine neu erstellte Instance kopiert. Diese Kopie erhöht die CPU-Kosten und mehr Arbeitsspeicher auf dem Stapel. Bei kleinen Strukturen ist der Effekt minimal und daher akzeptabel. Ändern Sie jedoch für Funktionen, die wiederholt jeden Frame aufgerufen haben, und für Funktionen, die große Strukturen annehmen, wenn möglich, die Funktionsdefinition so, dass sie als Verweis übergeben wird. Weitere Informationen finden Sie hier

Sonstiges

  1. Physik

    1. Im Allgemeinen besteht die einfachste Möglichkeit zur Verbesserung der Physik darin, die für die Physik aufgewendete Zeit oder die Anzahl der Iterationen pro Sekunde zu begrenzen. Dies verringert die Simulationsgenauigkeit. Siehe TimeManager in Unity.

    2. Die Arten von Collidern in Unity weisen sehr unterschiedliche Leistungsmerkmale auf. In der folgenden Reihenfolge sind die leistungsstärksten Collider bis zu den am wenigsten performanten Collider von links nach rechts aufgeführt. Es ist wichtig, Mesh-Collider zu vermeiden, die wesentlich teurer sind als die primitiven Collider.

    Sphere < Capsule < Box <<< Mesh (konvex) < Mesh (nicht konvex)

  2. Animationen

    Deaktivieren Sie Animationen im Leerlauf, indem Sie die Komponente "Animator" deaktivieren (das Deaktivieren des Spielobjekts hat nicht denselben Effekt). Vermeiden Sie Entwurfsmuster, bei denen ein Animator in einer Schleife sitzt und einen Wert auf denselben Wert festlegt. Es gibt einen erheblichen Aufwand für diese Technik, ohne Auswirkungen auf die Anwendung. Weitere Informationen finden Sie hier.

  3. Komplexe Algorithmen

    Wenn Ihre Anwendung komplexe Algorithmen wie inverse Kinematik, Pfadbestimmung usw. verwendet, suchen Sie nach einem einfacheren Ansatz oder passen Sie relevante Einstellungen für ihre Leistung an.

Empfehlungen für CPU-zu-GPU-Leistung

Im Allgemeinen hängt die CPU-zu-GPU-Leistung von den Draw-Aufrufen ab, die an die Grafik-Karte gesendet werden. Um die Leistung zu verbessern, müssen Draw-Calls strategisch a) reduziert oder b) umstrukturiert werden, um optimale Ergebnisse zu erzielen. Da Draw-Aufrufe selbst ressourcenintensiv sind, reduziert sich durch ihre Reduzierung der erforderlichen Arbeiten insgesamt. Darüber hinaus erfordern Zustandsänderungen zwischen Draw-Aufrufen kostspielige Validierungs- und Übersetzungsschritte im Grafiktreiber, und daher kann die Umstrukturierung der Draw-Aufrufe Ihrer Anwendung zur Begrenzung von Zustandsänderungen (z. B. unterschiedliche Materialien usw.) die Leistung steigern.

Unity hat einen großartigen Artikel, der einen Überblick gibt und sich mit dem Batching von Draw-Calls für ihre Plattform befasst.

Instanziiertes Rendering in einem Durchgang

Single Pass Instanced Rendering in Unity ermöglicht es, Draw-Aufrufe für jedes Auge auf einen instanzierten Draw-Aufruf zu reduzieren. Aufgrund der Cache-Kohärenz zwischen zwei Draw-Aufrufen gibt es auch eine Leistungsverbesserung auf der GPU.

So aktivieren Sie dieses Feature in Ihrem Unity-Projekt

  1. Öffnen Sie die OpenXR-Einstellungen (wechseln Sie zuProjekteinstellungen>bearbeiten>,XR-Plug-in-Verwaltung>,OpenXR).
  2. Wählen Sie Single Pass Instanced aus dem Dropdown-Menü Render-Modus aus.

Lesen Sie die folgenden Artikel von Unity für Details zu diesem Renderingansatz.

Hinweis

Ein häufiges Problem mit dem Single Pass Instanced Rendering tritt auf, wenn Entwickler bereits über vorhandene benutzerdefinierte Shader verfügen, die nicht für die Instanzierung geschrieben wurden. Nach der Aktivierung dieses Features bemerken Entwickler möglicherweise, dass einige GameObjects nur auf einem Auge gerendert werden. Dies liegt daran, dass die zugehörigen benutzerdefinierten Shader nicht über die entsprechenden Eigenschaften für die Instanzierung verfügen.

Statische Batchverarbeitung

Unity ist in der Lage, viele statische Objekte zu stapeln, um Draw-Aufrufe an die GPU zu reduzieren. Static Batching funktioniert für die meisten Renderer-Objekte in Unity, die 1) dasselbe Material teilen und 2) alle als statisch markiert sind (Wählen Sie ein Objekt in Unity aus und aktivieren Sie das Kontrollkästchen oben rechts im Inspektor). GameObjects, die als statisch markiert sind, können während der gesamten Laufzeit Ihrer Anwendung nicht verschoben werden. Daher kann die statische Batchverarbeitung bei HoloLens schwierig sein, wo praktisch jedes Objekt platziert, verschoben, skaliert usw. werden muss. Bei immersiven Headsets kann die statische Batchverarbeitung Draw-Calls drastisch reduzieren und somit die Leistung verbessern.

Weitere Informationen finden Sie unter Statisches Batching unter Draw Call Batching in Unity .

Dynamische Batchverarbeitung

Da es problematisch ist, Objekte für die HoloLens-Entwicklung als statisch zu markieren, kann die dynamische Batchverarbeitung ein hervorragendes Tool sein, um diese fehlende Funktion zu kompensieren. Es kann auch bei immersiven Headsets nützlich sein. Die dynamische Batchverarbeitung in Unity kann jedoch schwierig zu aktivieren sein, da GameObjects a) dasselbe Material freigeben und b) eine lange Liste anderer Kriterien erfüllen müssen.

Lesen Sie Dynamic Batching unter Draw Call Batching in Unity für die vollständige Liste. In den meisten Fällen werden GameObjects ungültig, wenn sie dynamisch gestapelt werden können, da die zugehörigen Gitterdaten nicht mehr als 300 Scheitelpunkte umfassen können.

Andere Techniken

Batchverarbeitung kann nur erfolgen, wenn mehrere GameObjects dasselbe Material gemeinsam verwenden können. In der Regel wird dies dadurch blockiert, dass GameObjects eine eindeutige Textur für das jeweilige Material haben müssen. Es ist üblich, Texturen zu einer großen Textur zu kombinieren, eine Methode, die als Textur-Atlasing bekannt ist.

Darüber hinaus ist es vorzuziehen, Meshes in einem GameObject zu kombinieren, wo dies möglich und sinnvoll ist. Jedem Renderer in Unity ist ein kombinierter Mesh-Aufruf zugeordnet, im Gegensatz zum Übermitteln eines kombinierten Netzes unter einem Renderer.

Hinweis

Durch das Ändern der Eigenschaften von Renderer.material zur Laufzeit wird eine Kopie des Materials erstellt und somit möglicherweise die Stapelverarbeitung unterbrochen. Verwenden Sie Renderer.sharedMaterial, um die Eigenschaften des freigegebenen Materials in GameObjects zu ändern.

Empfehlungen zur GPU-Leistung

Bandbreite und Füllraten

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

  • Die Speicherbandbreite ist die Lese- und Schreibrate, die die GPU aus dem Arbeitsspeicher ausführen kann
  • Die Füllrate bezieht sich auf die Pixel, die von der GPU pro Sekunde gezeichnet werden können.

Optimieren der Tiefenpufferfreigabe

Es wird empfohlen, die Tiefenpufferfreigabe zu aktivieren, um die Stabilität des Hologramms zu optimieren. Wenn Sie die tiefenbasierte Neuprojektion in der späten Phase mit dieser Einstellung aktivieren, wird empfohlen, das 16-Bit-Tiefenformat anstelle des 24-Bit-Tiefenformats auszuwählen. Die 16-Bit-Tiefenpuffer reduzieren die Bandbreite (und damit den Strom), die mit dem Tiefenpufferdatenverkehr verbunden ist, drastisch. Dies kann eine große Verbesserung sowohl bei der Leistungsreduzierung als auch bei der Leistung bedeuten. Es gibt jedoch zwei mögliche negative Ergebnisse bei Verwendung des 16-Bit-Tiefenformats.

Z-Fighting

Die reduzierte Genauigkeit des Tiefenbereichs macht Z-Fighting mit 16-Bit wahrscheinlicher als mit 24-Bit. Um diese Artefakte zu vermeiden, ändern Sie die nahen/fernen Clip-Ebenen der Unity-Kamera , um die geringere Genauigkeit zu berücksichtigen. Bei HoloLens-basierten Anwendungen kann eine ferne Clip-Ebene von 50 m anstelle der Unity-Standardebene von 1.000 m im Allgemeinen Z-Fighting eliminieren.

Deaktivierter Schablonenpuffer

Wenn Unity eine Rendertextur mit 16-Bit-Tiefe erstellt, wird kein Schablonenpuffer erstellt. Wenn Sie das 24-Bit-Tiefenformat auswählen, wie in der Unity-Dokumentation beschrieben, wird ein 24-Bit-Z-Puffer und ein 8-Bit-Schablonenpuffer erstellt (wenn 32-Bit auf einem Gerät (z. B. HoloLens) anwendbar ist, was im Allgemeinen der Fall ist).

Vollbildeffekte vermeiden

Techniken, die auf dem Vollbildschirm arbeiten, können teuer sein, da ihre Größenordnung Millionen von Operationen pro Frame beträgt. Es wird empfohlen, Nachbearbeitungseffekte wie Anti-Aliasing, Bloom und mehr zu vermeiden.

Optimale Lichteinstellungen

Die globale Echtzeitbeleuchtung in Unity kann hervorragende visuelle Ergebnisse liefern, erfordert jedoch teure Beleuchtungsberechnungen. Wir empfehlen, die globale Echtzeitbeleuchtung für jede Unity-Szenendatei über> dieEinstellungen> für dieFensterrenderingbeleuchtung> zu deaktivieren. Deaktivieren Sie die globale Echtzeitbeleuchtung.

Darüber hinaus wird empfohlen, alle Schattenwürfe zu deaktivieren, da diese auch teure GPU-Durchgänge zu einer Unity-Szene hinzufügen. Schatten können pro Licht deaktiviert werden, können aber auch ganzheitlich über die Qualitätseinstellungen gesteuert werden.

Bearbeiten>Project Settings, dann wählen Sie die Kategorie Qualität aus und wählen Sie für die UWP-Plattform die Option > " Niedrige Qualität " aus. Man kann die Shadows-Eigenschaft auch einfach auf Disable Shadows setzen.

Es wird empfohlen, Baked Lighting mit Ihren Modellen in Unity zu verwenden.

Polyanzahl reduzieren

Die Polygonanzahl wird um

  1. Entfernen von Objekten aus einer Szene
  2. Ressourcendezimierung, die die Anzahl der Polygone für ein bestimmtes Netz reduziert
  3. Implementieren eines Detaillierungssystems (LOD) in Ihrer Anwendung, das weit entfernte Objekte mit einer Version mit weniger Polygonen derselben Geometrie rendert

Grundlegendes zu Shadern in Unity

Eine einfache Annäherung an die Leistung von Shadern besteht darin, die durchschnittliche Anzahl der Vorgänge zu identifizieren, die jeweils zur Laufzeit ausgeführt werden. Dies kann ganz einfach in Unity erfolgen.

  1. Wählen Sie Ihr Shaderobjekt oder ein Material aus, dann in der oberen rechten Ecke des Inspektorfensters. Wählen Sie das Zahnradsymbol gefolgt von "Shader auswählen".

    Shader in Unity auswählen

  2. Wählen Sie bei ausgewähltem Shader-Asset die Schaltfläche "Code kompilieren und anzeigen" unter dem Inspektorfenster aus.

    Shadercode in Unity kompilieren

  3. Suchen Sie nach dem Kompilieren in den Ergebnissen nach dem Abschnitt "Statistiken" mit der Anzahl der verschiedenen Operationen für den Vertex- und Pixel-Shader (Hinweis: Pixel-Shader werden oft auch als Fragment-Shader bezeichnet).

    Unity Standard Shader-Operationen

Pixel-Shader optimieren

Wenn man sich die kompilierten Statistikergebnisse mit der obigen Methode ansieht, führt der Fragment-Shader im Durchschnitt mehr Operationen aus als der Vertex-Shader. Der Fragment-Shader, auch bekannt als Pixel-Shader, wird pro Pixel in der Bildschirmausgabe ausgeführt, während der Vertex-Shader nur pro Scheitelpunkt aller Gitter ausgeführt wird, die auf den Bildschirm gezeichnet werden.

Daher haben Fragment-Shader aufgrund all der Beleuchtungsberechnungen nicht nur mehr Anweisungen als Vertex-Shader, sondern Fragment-Shader werden fast immer auf einem größeren Datensatz ausgeführt. Wenn die Bildschirmausgabe beispielsweise ein 2k x 2k-Bild ist, kann der Fragment-Shader 2.000*2.000 = 4.000.000 Mal ausgeführt werden. Wenn zwei Augen gerendert werden, verdoppelt sich diese Zahl, da es zwei Bildschirme gibt. Wenn eine Mixed Reality-Anwendung über mehrere Durchläufe, Vollbild-Nachbearbeitungseffekte oder das Rendern mehrerer Gittermodelle auf demselben Pixel verfügt, erhöht sich diese Zahl dramatisch.

Daher kann die Reduzierung der Anzahl der Vorgänge im Fragment-Shader im Allgemeinen zu weitaus größeren Leistungssteigerungen gegenüber Optimierungen im Vertex-Shader führen.

Unity Standard Shader-Alternativen

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

Unity bietet außerdem unbeleuchtete, vertexbeleuchtete, diffuse und andere vereinfachte Shaderoptionen, die im Vergleich zum Unity Standard-Shader schneller sind. Weitere Informationen finden Sie unter Verwendung und Leistung von integrierten Shadern.

Shader-Vorladen

Verwenden Sie Shader-Vorabladen und andere Tricks, um die Shader-Ladezeit zu optimieren. Insbesondere bedeutet das Shader-Vorladen, dass Sie keine Ruckler aufgrund der Laufzeit-Shader-Kompilierung sehen.

Überzeichnung begrenzen

In Unity kann man die Überzeichnung für seine Szene anzeigen, indem man das Zeichenmodus-Menü in der oberen linken Ecke der Szenenansicht umschaltet und Überzeichnen auswählt.

Im Allgemeinen kann eine Überzeichnung durch Culling von Objekten im Voraus gemildert werden, bevor sie an die GPU gesendet werden. Unity stellt Details zur Implementierung von Occlusion Culling für seine Engine zur Verfügung.

Empfehlungen für Arbeitsspeicher

Eine übermäßige Speicherzuweisung & eine übermäßige Aufhebung der Zuordnung kann sich negativ auf Ihre holografische Anwendung auswirken, was zu inkonsistenter Leistung, eingefrorenen Frames und anderem nachteiligen Verhalten führt. Es ist besonders wichtig, Speicherüberlegungen bei der Entwicklung in Unity zu verstehen, da die Speicherverwaltung vom Garbage Collector gesteuert wird.

Garbage Collection

Holografische Apps verlieren Verarbeitungszeit für die Rechenzeit an den Garbage Collector (GC), wenn die GC aktiviert wird, um Objekte zu analysieren, die sich während der Ausführung nicht mehr im Gültigkeitsbereich befinden, und deren Speicher freigegeben werden muss, damit er für die Wiederverwendung verfügbar gemacht werden kann. Bei konstanten Zuordnungen und Zuordnungsaufhebungen muss der Garbage Collector in der Regel häufiger ausgeführt werden, was die Leistung und Benutzerfreundlichkeit beeinträchtigt.

Eine der häufigsten Methoden, die zu übermäßiger Garbage Collection führt, ist das Nicht-Zwischenspeichern von Verweisen auf Komponenten und Klassen in der Unity-Entwicklung. Alle Verweise sollten während Start() oder Awake() erfasst und in späteren Funktionen wie Update() oder LateUpdate() wiederverwendet werden.

Weitere Schnelltipps:

  • Verwenden Sie die StringBuilder C#-Klasse, um komplexe Zeichenfolgen zur Laufzeit dynamisch zu erstellen.
  • Entfernen Sie Aufrufe von Debug.Log(), wenn sie nicht mehr benötigt werden, da sie weiterhin in allen Buildversionen einer App ausgeführt werden
  • Wenn Ihre holografische App im Allgemeinen viel Speicher benötigt, sollten Sie System.GC.Collect() während der Ladephasen aufrufen, z. B. wenn Sie einen Lade- oder Übergangsbildschirm präsentieren.

Objektpooling

Objektpooling ist eine beliebte Technik zur Reduzierung der Kosten für die kontinuierliche Objektzuordnung und -zuordnung. Dies geschieht, indem ein großer Pool identischer Objekte zugewiesen und inaktive, verfügbare Instanzen aus diesem Pool wiederverwendet werden, anstatt ständig Objekte im Laufe der Zeit zu erzeugen und zu zerstören. Objektpools eignen sich hervorragend für wiederverwendbare Komponenten mit variabler Lebensdauer während einer App.

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. So kann Ihre App so schnell wie möglich in einen interaktiven Zustand wechseln. Während die neue Szene aktiviert wird, kann es zu einer starken CPU-Spitze kommen, und alle gerenderten Inhalte könnten stottern oder hängen. Eine Möglichkeit, dies zu umgehen, besteht darin, die AsyncOperation.allowSceneActivation-Eigenschaft für die zu ladende Szene auf "false" festzulegen, auf das Laden der Szene zu warten, den Bildschirm auf schwarz zu setzen und sie dann wieder auf "true" festzulegen, um die Szenenaktivierung abzuschließen.

Denken Sie daran, dass dem Benutzer der holografische Begrüßungsbildschirm angezeigt wird, während die Startszene geladen wird.