Performance
Wie ihr alle sicher gemerkt habt ist die Entwicklung einer VR-App auf Android mit vielen Performance-Problemen verbunden. Hier sollen ein paar technische und konzeptuelle Ansätze gesammelt werden, die dabei helfen können die Performance zu verbessern und dementsprechend auch größere/detailreichere Applikation zu bauen.
1. Low Poly
Ihr macht es schon alle, es bleibt aber wichtig. Modelle mit einer geringen Anzahl an Polygonen, sind eine der effektivsten Methode, um schnell und einfach Performance zu sparen. Achtet immer darauf, wo ihr Polygone sparen könnt. Beziehungsweise sind hier auch die Vertices (Vertex-Punkte) gemeint. Dafür gibt es in Blender viele (mehr oder weniger automatische) Möglichkeiten.
- https://all3dp.com/2/blender-how-to-reduce-polygons/
- https://docs.blender.org/manual/en/latest/modeling/meshes/editing/mesh/cleanup.html
Damit wird meistens auch automatisch die UV-Map reduziert, also denkt daran, dass eure UV-Maps, Texturen, Materialien womöglich verbuggt sein können.
Meistens kommt man mit deutlich weniger Polygonen aus als man ursprünglich vielleicht denkt. Überlegt auch immer, wie wichtig ein Modell für eure Narrative ist. Eine Blume in einer Vase, mit der nie interagiert wird, die nie erwähnt wird, kann wahrscheinlich mit deutlich weniger Polygonen auskommen, als eine Blume in einer Vase, die das zentrales Objekt in der Geschichte darstellt.
2. Materialien in Unity
Die Materials in Unity beschreiben graphischen Eigenschaften von Modellen. Über diese können Objekte texturiert werden, die Lichteigenschaften definiert werden u.v.m. Dementsprechend ist aber auch hier viel Performance rauszuholen.
Auch hier wird das vermutlich alles schon instinktiv gemacht. Wenn man im Low-Poly-Stil arbeitet, hat man einen weiteren Vorteil. Die Texturen/Materialien können mit viel weniger auskommen. Oft benutzt man solide Farben anstelle von großen detailreichen Texturen. Idealerweise benutzt man dafür die eingebauten Materials von Unity und benutzt gar nicht erst Texturen, wenn es nicht absolut nötig ist. Solide Farben können einfach im Unity-Material als Farbe eingestellt werden. Wenn man Texturen braucht, sollte man das möglichste Minimun nehmen, was noch gut aussieht, um Performance zu sparen. Denkt daran: Das Display, worauf das Spiel läuft ist super klein, gar nicht so hochauflösend und wird außerdem für VR noch in zwei geteilt. Eine 4K Textur ergibt da einfach keinen Sinn.
Damit zusammenhängend kann also auch eine UV-Map einfacher ausfallen, was ebenso perfomance spart.
Generell bietet es sich an das einfachste Material/den einfachsten Shader zu nehmen, wie nur möglich. Der Standard-Shader bietet einige Funktionen, wie Normal Map, Metallic Map, Occlusion Map, Height Map und ganz viel mehr. Die braucht ihr aber natürlich nicht unbedingt, besonders bei Low-Poly. Auch wenn diese Funktionen im Shader nicht von euch befüllt wurden, ziehen sie Performance. Aber das Schöne: Es gibt auch andere Shader und die Möglichkeit eigene Shader zu erstellen (Achtung: das kann ziemlich komplex werden!). Beispielsweise bietet euch Unity schon direkt Mobile Shader an. Diese beinhalten dann auch quasi nur die Möglichkeit eine Textur/solide Farbe einzustellen. Mehr nicht. Außerdem ist der Shader im Hintergrund noch mehr auf mobile Plattformen optimiert. Nutzt solche Shader wo es nur geht.
Übrigens: Normal, Metallic Maps und was es da noch alles an Maps gibt, sind für gewöhnlich in der gleichen Auflösung wie die Haupttextur. Dementsprechend schnell leidet auch die Leistung, wenn ihr plötzlich doppelt oder dreimal so viele Texturen laden und anzeigen müsst. Überlegt also auch hier, ob ihr unbedingt ein Metallic Map braucht und wie groß die Texturen tatsächlich sein sollen.
2.1 Texture Baking
3. Quality Settings
4. Batching
Eine weitere Methode um Ressourcen zu sparen und Performance zu verbessern ist das Batching. Dabei geht es im Prinzip die Anzahl an Aufrufen an die GPU zu reduzieren bzw. so viel wie möglich zu bündeln. Das Stichwort dazu heißt Draw Calls. Beispiel: Ihr habt einen Tisch in der Szene. Dieser Tisch hat 3 zugewiese Materials (bspw. zwei verschiedene Holzarten und Metall). Jedes Material wird in einem separaten Draw-Call gerendert. Duplizieren wir jetzt diesen Tisch mit den gleichen Materials haben wir schon 6 Draw-Calls. Das rechnet sich sehr sehr schnell. Natürlich können wir die Draw-Calls aber auch reduzieren - und zwar mit Batching/Instancing.
4.1 Static Batching
Die erste und vermutlich einfachste Form des Batchings ist das Static Batching. Hierbei könnt ihr statisch (sich nicht bewegende) Objekte einfach taggen und Unity übernimmt alles für euch. Hier werden die Meshes aller markierten Objekt so gut es geht kombiniert und zu wenigen Drawcalls zusammengefügt.
4.2 Dynamic Batching
Dynamic Batching funktioniert under the hood sehr ähnlich zum Static Batching, passiert aber während der Laufzeit und kann entsprechend auch sich bewegende Objekte batchen. Hier ist allerdings Vorsicht geboten. Da das während der Laufzeit und auf der CPU berechnet wird, kann es sein, dass ihr damit die Performance kaum verbessert oder sogar verschlechtert. Das hängt ein wenig von der entsprechenden Hardware ab.
4.3 GPU Instancing
Falls ihr in eurer Szene viele gleiche Objekte habt, wie beispielsweise Bäume, Steine o.ä., kann es sinnvoll sein GPU Instancing zu benutzen. Das kann man in dem Shader Material des entsprechenden Objektes aktivieren. Einmal aktiviert werden alle Kopien eines Objektes in einem Draw Call gerendert. Hinweis: Die Farbe und Größe der einzelnen Objekte muss hier nicht identisch sein.
4.4 Manuelles "Batching"
Es bleibt die Optionen Meshes/Objekte selbst zusammenzufügen. Beispielsweise mehrere Modelle in Blender zu einem einzigen Modell zusammenzustückeln. Oder aber eines der vielen Assets bzw. die Unity-Methode Mesh.CombineMeshes zu benutzen. Denkt auch hier an die Materialien. Es bringt nichts Modelle zusammenzuführen, wenn das Modell dann 20 verschiedene Materialien besitzt. Dann lohnt es sich womöglich eine Textur für das gesamte Modell zu baken, falls Texturen nötig sind.
5. Licht
6. Occlusion Culling
Occlusion Culling, oder auch Frustum Culling, ist eine weitere sehr simple Möglichkeit massiv an Ressourcen zu sparen. Kurz zusammengefasst soll Occlusion Culling bewirken, dass nur das gerendert wird, was tatsächlich von der Kamera gesehen werden kann. Hier ein Beispiel aus dem Making Of zu Horizon Zero Dawn.
Original source: Horizon Zero Dawn – The making of the game (2017) by VPRO Documentary https://youtu.be/A0eaGRcdwpo?t=1102
Macht man das nicht, wird zu jedem Zeitpunkt die gesamte Szene gerendert, auch was hinter der Kamera ist, oder was von anderen Objekten versteckt ist.
In Unity gibt es also ein dediziertes Tool, um diese Methode anzuwenden. Hier kann die Szene dann (grob beschrieben) in Blöcke unterteilt. Außerdem wird dann dabei berechnet, welche Objekte wie gesehen oder eben nicht gesehen werden können. Wichtig: Das muss gebaked werden, also im Voraus berechnet werden. Entsprechend muss aber auch neu gebaked werden, wenn ihr was an der Szene ändert. Vergesset das nicht, besonders vor der Abgabe lohnt es sich einmal alles neu zu berechnen.
Wie genau ihr das Occlusion Culling einstellt könnt ihr im bereits verlinkten Unity Manual nachschauen. Video Tutorials gibt es dazu auch ohne Ende.
7. LOD
8. Profiler & Frame Debugger