Optimierung von 3D-Modellen für die Spielszene

Dieser Artikel vervollständigt eine Reihe von Veröffentlichungen aus dem Krasnodar-Studio Plarium zu verschiedenen Aspekten der Arbeit mit 3D-Modellen in Unity. Vorherige Artikel: „Funktionen für die Arbeit mit Mesh in Unity“ , „Unity: Prozedurale Bearbeitung von Mesh“ , „Import von 3D-Modellen in Unity und Fallstricke“ , „Pixeleinzug beim Scannen von Texturen“ .

Vor fast 2 Jahren haben wir einen Artikel geschrieben, in dem wir über die Möglichkeit der Optimierung der 3D-Geometrie in einer Szene mit Einschränkungen des Kamerawinkels und der Drehung der entsprechenden Objekte gesprochen haben. Seitdem ist nicht viel passiert, aber die Möglichkeit, die Lösung zu verbessern, verschiedene Ansätze in Betracht zu ziehen und andere auszuspionieren, beschäftigt die Entwickler. In diesem Artikel beschreiben wir eine verbesserte Version des Algorithmus, der auf dem Zeichnen von Polygonen basiert, sowie den Versuch, einen Teil dieser Arbeit auf ein 3D-Paket zu übertragen.



In der Szene zuschneiden


Das Grundprinzip dieses Algorithmus haben wir bereits im obigen Artikel betrachtet: Wir löschen alle Effekte und transparenten Objekte, malen unverarbeitete Polygone mit einer Farbe und verarbeiten sie mit anderen, rendern und extrahieren das Ergebnis. In der alten Version wurde so gemalt, dass alles Schwarz überflüssig war und nur ein Dreieck rot markiert war.

In den Kommentaren zu diesem Artikel wies einer der Leser auf die Möglichkeit hin, den Algorithmus zu optimieren, indem eine Eins-zu-Eins-Entsprechung zwischen der Menge von Polygonen und einer Menge von eindeutigen Zahlen hergestellt wird. Dann ist es möglich, mehr als ein Dreieck auf die gleiche Weise zu verarbeiten. Betrachten Sie diese Option.

In diesem Fall und beim letzten Mal sollte ein Teil des Vortrainings mit der Deaktivierung aller pfeifenden Objekte auf der Bühne und von Objekten verbunden sein, von denen garantiert wird, dass sie die Sichtbarkeit des Zielmodells nicht beeinträchtigen. Kameraansichten werden fast unabhängig voneinander verarbeitet und nur durch einen gemeinsamen Indexpuffer aus sichtbaren Polygonen verbunden. Zusätzlich wird für jeden Winkel eine Geometrievorbearbeitung durchgeführt, bei der Polygone gedreht werden, die zurück zur Kamera ( Rückseite ) gedreht werden. Dies geschieht, weil in einer bestimmten Phase des Algorithmus ein temporäres Netz mit einer erheblich größeren Anzahl von Scheitelpunkten als dem ursprünglichen erstellt wird. Diese Zahl kann den Schwellenwert von 65.535 leicht überschreiten, was zusätzliche Gesten in den Berechnungen erfordert und zu einer verringerten Leistung führt. In jedem Fall werden diese Polygone gelöscht, da ihre Farbe nicht in den Rahmen fällt. Aufgrund der Tatsache, dass jedes Dreieck möglicherweise drei Garbage Vertices erzeugt, erleichtert die vorherige Eliminierung unnötiger Polygone die Hauptphase des Algorithmus und senkt die Speicherkosten.

Es gebe ein 3D-Modell, dessen Geometrie durch ein Netz dargestellt wird. Um ein bestimmtes Polygon in einer eindeutigen Farbe zu zeichnen, müssen Sie alle Scheitelpunkte in dieser Farbe zeichnen. Da im allgemeinen Fall ein Knoten zu verschiedenen Polygonen gehören kann, ist es nicht möglich, das Problem direkt zu lösen. Unabhängig davon, wie wir einen Scheitelpunkt färben, kriecht seine Farbe beim Rendern gemäß dem Interpolationsalgorithmus auf der Seite der Grafikkarte über alle Dreiecke, deren Eigentümer er ist.


Ein Beispiel für die Farbinterpolation bei der Anzeige von Polygonen mit gemeinsamen Eckpunkten

Aus diesem Grund ist es notwendig, das Netz in separate unabhängige Polygone zu unterteilen und dabei die Topologie und Geometrie des Objekts beizubehalten. Dictum factum. Wir transformieren die Arrays von Dreiecken und Scheitelpunkten so, dass für jedes Dreieck 3 eindeutige Scheitelpunkte erstellt werden, deren Position durch die entsprechenden Scheitelpunkte des ursprünglichen Netzes bestimmt wird. Es ist anzumerken, dass ein solches Netz im allgemeinen Fall eine bedeutend größere Anzahl von Eckpunkten aufweist als das Original. Wenn diese Zahl 65.535 überschreitet, müssen Sie beim Erstellen des Netzes das entsprechende Indizierungsformat angeben.

Konvertieren Sie das ursprüngliche Netz in ein Netz mit Scheitelpunkten, die für jedes Polygon eindeutig sind
private static Mesh GetNotSmoothMesh(Mesh origin) { var oVertices = origin.vertices; var oTriangles = origin.triangles; var vertices = new Vector3[oTriangles.Length]; var triangles = new int[oTriangles.Length]; for (int i = 0; i < triangles.Length; i++) { vertices[i] = oVertices[oTriangles[i]]; triangles[i] = i; } return new Mesh() { indexFormat = vertices.Length > 65535 ? IndexFormat.UInt32 : IndexFormat.UInt16, vertices = vertices, triangles = triangles }; } 


Nun müssen Sie die Polygone dieses Netzes bestimmen, damit nach dem Rendern festgestellt werden kann, welches auf dem Bildschirm angezeigt wird. Wie bereits erwähnt, generieren wir eindeutige Farben für Polygone und malen jeweils drei Scheitelpunkte in der entsprechenden Farbe. Das Ergebnis ist ein neues Netz, das wir Byte-Colored Mesh nennen .


Byte-farbiges Netz

Maschenfärbung, bei der jeder Scheitelpunkt nur zu einem Polygon gehört
 private static void ColorizePolygons(Mesh mesh) { var pColors = ColorsOfPolygons(mesh); var colors = new Color[mesh.vertexCount]; for (int i = 0; i < colors.Length; i++) { colors[i] = pColors[i / 3]; } mesh.colors = colors; } private static Color[] GetColorsOfPolygons(Mesh mesh) { var colors = new Color[mesh.triangles.Length / 3]; for (int i = 0; i < colors.Length; i++) { var color = Int2Color(i);//         ,     Color2Int //      ,       int     Color32 colors[i] = color; } return colors; } 


Erinnere dich an die Färbung. Es ist Zeit zu rendern. Wir führen für alle Kamerawinkel ein 3D-Rendering durch und füllen bei jeder Verarbeitung den Puffer mit eindeutigen Polygonindizes auf, deren Farben im Rahmen erkannt wurden. Während der Berechnungen für die Kamera müssen Sie das Anti-Aliasing deaktivieren, um das Auftreten neuer Farben aufgrund der Interpolation benachbarter Pixel zu vermeiden.

Lesen und Speichern von Farben aus verschiedenen Kamerawinkeln
 // CameraTransform —         //      SetCameraTransform private static HashSet<Color> GetVisibleColors(Camera camera, CameraTransform[] cameraTransforms) { var renderTexture = new RenderTexture(1920, 1080, 24);//for example var rtRect = new Rect(0, 0, renderTexture.width, renderTexture.height); var frame = new Texture2D(renderTexture.width, renderTexture.height, TextureFormat.RGB24, false);//    -  RGB24   ,    RGBA32 var visibleColorsSet = new HashSet<Color>(); foreach (var cameraTransform in cameraTransforms) { SetCameraTransform(camera, cameraTransform); CreateScreenShot(camera, renderTexture, frame, rtRect); visibleColorsSet.UnionWith(GetTextureColors(frame)); } return visibleColorsSet; } public static void SetCameraTransform(Camera camera, CameraTransform camTransform) { camera.transform.position = camTransform.Position; camera.transform.rotation = camTransform.Rotation; camera.fieldOfView = camTransform.FieldOfView; camera.orthographic = camTransform.IsOrthographic; camera.nearClipPlane = camTransform.NearClippingPlane; camera.farClipPlane = camTransform.FarClippingPlane; } private static HashSet<Color> GetTextureColors(Texture2D texture) { return new HashSet<Color>(texture.GetPixels()); } private static void CreateScreenShot(Camera cam, RenderTexture renderTexture, Texture2D screenShot, Rect renderTextureRect) { cam.targetTexture = renderTexture; cam.Render(); RenderTexture.active = cam.targetTexture; screenShot.ReadPixels(renderTextureRect, 0, 0); RenderTexture.active = null; cam.targetTexture = null; } } 


Es ist erwähnenswert, dass aufgrund der Diskretisierung einige Dreiecke aufgrund der besonders geringen Größe ihrer Projektion auf den Bildschirm möglicherweise nicht angezeigt werden und nicht, weil sich etwas mit ihnen überlappt oder sie auf der falschen Seite gedreht sind. Wir haben eine konservative Version des Algorithmus implementiert. In diesem Fall wird die AABB der Projektion des Dreiecks auf dem Bildschirm berechnet, und wenn mindestens eine seiner Seiten kleiner als die Texelseite im Bild ist, wird ein solches Polygon als sichtbar markiert. Dieser Ansatz schützt vor Artefakten, wenn der Algorithmus mit einer Auflösung ausgeführt wird, die geringer ist als die Bildschirmauflösung des Zielgeräts. Wenn Sie kleine Polygone ignorieren, ist das Ergebnis auch akzeptabel, vorausgesetzt, die Auflösung der verwendeten Rendertextur ist höher als die Auflösung der Bildschirme der vorgesehenen Geräte.

Wir haben diesen Beschneidungsalgorithmus in Unity implementiert und verwenden ihn, um statische Objekte zu optimieren, deren Modelle in der Szene mehrmals an verschiedenen Positionen gefunden werden. Dies ist hauptsächlich die Szenerie: Steine, Bäume, Statuen, Vasen usw., die sich auf das häufig verwendete Fertighaus bezieht. Wir möchten solche Objekte bereits in der Phase der Erstellung eines 3D-Pakets optimieren, aber wer weiß, in welcher phantasmagorischen Pose der Leveldesigner seinen Lieblingskandelaber platzieren möchte.

Das Trimmen der Objektmenge desselben Typs mit einem solchen Werkzeug verringert die Größe der Szene, da während des statischen Stapelns die Daten des gemeinsamen vorgefertigten Netzes sowieso in der Erstellungsphase so oft kopiert werden, wie die aktiven gezeichneten Objekte mit diesem Netz in der Szene dargestellt werden. Unsere Methode gibt auch Speicherplatz in Texturatlanten wie Lightmap frei . Wir nutzen den gesparten Platz, um die Details der Teile der Modelle zu verbessern, die die Reinigung überstanden haben.

3D-Ernte


Trotzdem ist es besser, wenn der Künstler in seinem Editor alles Unnötige abschneidet und dadurch die Anzahl der Stufen der Inhaltserstellung verringert. Dies ist gerechtfertigt, wenn das Modell in einer Szene mit nur einer vorgegebenen Drehung relativ zur Kamera verwendet wird. Zuvor wurden Objekte, die dem Benutzer von einer Seite genau zugewandt waren, häufig manuell vereinfacht, bevor sie in das Projekt integriert wurden. Es ist wichtig zu beachten, dass die programmgesteuerte Implementierung einer solchen Vereinfachung in Unity aufgrund der Komplexität der UV- Verpackungsentwicklung sehr viel schwieriger ist. Daher erleichtert die Automatisierung auf der Stufe eines 3D-Pakets einem Künstler manchmal das Leben.

Eines der Werkzeuge für die Arbeit mit 3D-Modellen in unserem Unternehmen ist Blender . Wir stiegen hinein. Es scheint, dass eine solche "erwachsene" Software, wie Blender , eine ähnliche Funktionalität haben sollte. Es stellte sich jedoch heraus, dass er nicht sollte. Ich musste mein eigenes Fahrrad sehen.

Die erste Idee war, das vertraute Auswahlwerkzeug zu verwenden - im Wesentlichen einen Teil der manuellen Arbeit des Künstlers für einen Kamerawinkel zu wiederholen: sichtbare Polygone auswählen, Auswahl umkehren, löschen. Der Plan war: Bewegen Sie die Kamera, bestimmen Sie die AABB- Projektion des Modells an jeder Position, fordern Sie dann das Ergebnis der Auswahl der Polygone des Bereichs an, der dem AABB entspricht , ermitteln Sie die Vereinigung der Polygonsätze der aktuellen Ansicht mit den vorherigen und löschen Sie am Ende nicht ausgewählte Polygone.

Bei der Implementierung des Skripts wurde jedoch ein erheblicher Nachteil in Bezug auf die Aufgabe festgestellt. Auswahlwerkzeuge in Blender (Rechteckauswahl, Kreisauswahl) verlieren mit zunehmender Anzahl der ausgewählten Elemente pro Flächeneinheit des Bildschirms an Genauigkeit (einige Polygone bleiben nicht ausgewählt), was ihre Verwendung in unseren Automatisierungswerkzeugen unmöglich macht. Interessante Tatsache: In 3ds Max wird ein solches Problem nicht beobachtet.


Hervorheben aus der Ferne in Blender


Auswahlergebnis

Der nächste Versuch zielte darauf ab, das Problem auf der Stirn zu lösen: Wir sendeten Strahlen von der Kamera durch jedes Pixel des Ansichtsfensters und schauten, welche Polygone sich als erste mit mindestens einem Strahl schneiden. Wir haben nicht auf genaue Ergebnisse mit diesem Ansatz gehofft, aber es war einen Versuch wert. Das Ergebnis ist offensichtlich: Sehr geringe Produktivität bei der Verarbeitung auf der CPU oder den gleichen Löchern mit einer geringen Anzahl von Strahlen.

Trotzdem haben wir uns für die Umsetzung eines weiter fortgeschrittenen Ansatzes eingesetzt. Die Idee war, eine bestimmte Anzahl zufälliger Punkte auf jedem Polygon auszuwählen und dann Strahlen von der Kamera in ihre Richtung zu senden. Dieser Ansatz hat gut funktioniert, aber wir hatten einige Grenzfälle: Es wurden auch Polygone abgeschnitten, bei denen der Winkel zwischen dem Strahl und ihrer Normalen ungefähr π / 2 betrug. Wenn die Kamera aufgrund von perspektivischen Verzerrungen zoomt, können sich daher ausgeschnittene Bereiche öffnen.

Diese Methode war nach Meinung der Künstler zu aggressiv, sodass wir uns darauf konzentrierten, nur die Rückflächen zu beschneiden .

Fazit


Es ist kein Geheimnis, dass ein sorgfältiger Umgang mit den Ressourcen des Geräts beim Erstellen von Spielen der wichtigste Faktor ist, der die Qualität des Endprodukts beeinflusst. Dies gilt insbesondere für mobile Plattformen, die auf die aktive Nutzung des Arbeitsspeichers eingestellt sind. Wenn Sie die Anzahl der Polygone verringern, können Sie den Bereich der Texturatlanten effektiver ausfüllen und die Rechenlast etwas verringern.

Vergessen Sie auch nicht die Personalkosten und die Fehlerkosten, wenn Sie die oben beschriebenen Werkzeuge und dergleichen verwenden. Der vorgeschlagene Ansatz setzt eine gut funktionierende Pipeline für die Arbeit der Kunstabteilung voraus, insbesondere für Mitarbeiter, die an der Integration von Modellen in das Projekt beteiligt sind.

Mit den in diesem Artikel beschriebenen Bedingungen und Tools halten wir uns daher an die folgenden Regeln. Wenn davon ausgegangen wird, dass das erstellte Modell immer von einer Seite zum Benutzer gedreht wird und auch wenn aus diesen Winkeln die Überlappung einiger Teile des Modells durch andere recht gering ist, verwendet der Künstler unseren Rückflächentrimmer in einem 3D-Editor, überprüft die Richtigkeit und fährt mit der UV- Verpackungsentwicklung fort . Wenn das Modell häufig an verschiedenen Positionen verwendet wird oder eine komplexere Geometrie aufweist, führen Sie nach dem Import in das Projekt den im ersten Teil des Artikels beschriebenen Algorithmus aus und verarbeiten damit alle statischen Objekte in der Szene.

Source: https://habr.com/ru/post/de484792/


All Articles