Verwenden von Unity3D in einer nativen iOS / Android-Anwendung zum Modellieren der Beleuchtung von Freiflächen

Bild

Unity3D ist eine bekannte 3D- und 2D-Spieleentwicklungsplattform, die weltweit an Popularität gewonnen hat. Gleichzeitig beschränken sich seine Funktionen nicht nur auf die Entwicklung von Spieleanwendungen, sondern eignen sich auch für den Einsatz in anderen Bereichen, in denen plattformübergreifende Anwendungen für die Arbeit mit Grafiken erstellt werden müssen. In diesem Artikel werden wir über die Erfahrungen mit Unity3D sprechen, um ein System zur Berechnung der Beleuchtung offener Räume zu entwickeln.

Das Unternehmen, mit dem wir zusammengearbeitet haben, ist das internationale Beleuchtungsunternehmen BOOS LIGHTING GROUP . Um die Attraktivität seiner Produkte zu erhöhen und die Interaktion mit Kunden zu vereinfachen, musste eine Anwendung entwickelt werden, mit der Sie den Standort von Beleuchtungsgeräten visuell simulieren, Beleuchtungsberechnungen durchführen und die erforderlichen technischen Informationen im Bericht anzeigen können. Es wurde angenommen, dass die Anwendung von einem potenziellen Kunden oder Vertriebsmitarbeiter auf einem iPad oder Android-Tablet gestartet wurde und dem Kunden ermöglicht, sich sofort ein Bild über die Möglichkeit von Beleuchtungsinstallationen zu machen.

Die Arbeiten wurden schrittweise auf der Grundlage der von der BOOS LIGHTING GROUP Corporation entwickelten Spezifikation von Anforderungen und Konsultationen zum Thema des Projekts durchgeführt.

Im Allgemeinen ist die Anwendung ein Editor, mit dem Sie Beleuchtungselemente, Straßen und dekorative Elemente hinzufügen und bearbeiten, eine lichttechnische Berechnung der Szene durchführen und einen Bericht im PDF-Format anzeigen können. Jedes Element verfügt über eigene Parameter für die Bearbeitung und Untertypen, die sich auf die Anzeige und Berechnung auswirken.

  • Es gibt verschiedene Arten von Beleuchtungsmasten mit verschiedenen Arten von Leuchten, Neigungswinkeln von Lampen und Verlängerungslängen. Bei bestimmten Leuchtentypen ist eine individuelle Einstellung mit Angabe der Beleuchtungsrichtung möglich.

    Bild
  • Straßen können lineare Abschnitte, Bogenelemente, Flächen oder Ringe sein. Für jedes Element können Abmessungen, Position, Layouttyp und Ebene angepasst werden.

    Bild
  • Dekorative Elemente - Autos, Bäume, Büsche, Verkehrszeichen

Alle Elemente der Szene können gedreht und verschoben werden. Standardaktionen zum Zurücksetzen oder erneuten Versuchen werden ebenfalls unterstützt. Mit den allgemeinen Einstellungen des Projekts können Sie die Textur des Dorg, die Erdoberfläche und die Anzeige zusätzlicher Parameter festlegen. Die Szene wird im 2D / 3D-Modus angezeigt. Bei der Berechnung der Beleuchtung auf der Oberfläche wird eine Karte der Beleuchtung der Oberfläche in fiktiven Farben angezeigt.

Bild


Wenn möglich, sollte die gesamte Benutzeroberfläche mit nativen iOS / Android-Tools erstellt werden.
Die wichtigste technische Anforderung für die Anwendung besteht darin, die Bühnenbeleuchtung gemäß den technischen Spezifikationen der Leuchten berechnen zu können. Erforderlich war auch die Fähigkeit jedes Geräts, sein Strahlungsmuster (Lichtintensitätskurven) im 3D / 2D-Modus anzuzeigen und anzuzeigen.

Plattformauswahl


Um das Projekt zu implementieren, haben wir Unity als bequemer für die Implementierung der erforderlichen Funktionalität ausgewählt. Im Allgemeinen hatte unser Unternehmen Erfahrung mit anderen 3D-Engines und -Plattformen (OpenSceneGraph, Ogre3D, LibGdx) und technisch können sie alle die erforderliche Aufgabe bewältigen. Diesmal fiel die Wahl jedoch auf Unity, was die Verwaltung der Szene während der Entwicklung und des Debugs erleichtert im Prozess der Arbeit.

Die Hauptschwierigkeiten


Wir werden nicht auf die Feinheiten der Entwicklung der gesamten Anwendung eingehen, da die Funktionalität zum Anzeigen und Bearbeiten der Szene technisch gesehen Standard ist. Natürlich gab es Schwierigkeiten mit den Mechanismen der spezifischen Bearbeitung von Objekten, dem Hinzufügen und Löschen von Objekten sowie dem Speichern einer Befehlskette für die Möglichkeit, Aktionen zu wiederholen und abzubrechen.
Wir möchten uns nur mit den Funktionen des Systems befassen, die mit der Arbeit mit der nativen Benutzeroberfläche, der Erstellung von PDF-Berichten und der Arbeit mit Photometrie- und Beleuchtungsberechnungen zusammenhängen.

Arbeiten Sie mit der nativen Benutzeroberfläche


In den meisten Fällen interagiert Unity mithilfe des Plug-In-Systems mit den nativen Funktionen des Systems, sodass Sie die gewünschten Funktionen in die Anwendung einbetten können. In unserem Fall ist die Situation jedoch etwas anders. Wir mussten eine vollständige Benutzeroberfläche über dem Unity-Fenster anzeigen.

Bild


Glücklicherweise kann Unity ein Projekt exportieren, das als Grundlage für eine native Anwendung verwendet werden kann. Die Hauptschwierigkeit in diesem Fall besteht darin, eine zusätzliche Benutzeroberfläche in das resultierende Projekt zu integrieren. Ebenso wichtig ist es, dass beim Zusammenstellen eines Unity-Projekts dessen Format und Speicherort von Unity gebildet und teilweise neu geschrieben werden, was die Möglichkeit zum Ändern des Projekts einschränkt.

Bei der Entwicklung einer iOS-Anwendung haben wir den im Artikel vorgeschlagenen Mechanismus verwendet. Während der Entwicklung wurde Unity 5.5 verwendet, und im Moment kann das, was darin angegeben ist, an Relevanz verlieren. Beim Zusammenstellen des Android-Projekts traten keine zusätzlichen Probleme auf, mit der Ausnahme, dass Unity die Manifestdatei und die Ressourcendateien jedes Mal überschreibt.
Ein weiteres Problem ist, dass Unity nur in einem Fenster arbeiten kann. Gleichzeitig mussten wir sicherstellen, dass Unity die gesamte Szene anzeigt, und beim Öffnen des Einstellungsfensters sollte ein 3D-Modell des photometrischen Körpers der Lampe angezeigt werden. Um dies zu erreichen, musste ich einen „Hack“ verwenden und dasselbe UIView-Objekt in verschiedenen Fenstern verwenden.

Zum Senden von Nachrichten haben wir die von Unity angebotene Standardfunktionalität verwendet. Das heißt, alle Nachrichten waren im JSON-Format und wurden in einfachen Zeilen übertragen. Dies führte zu keinen Leistungseinschränkungen, da die Größe der Nachrichten maximal 100 Zeichen erreichte und ihre Häufigkeit durch die Geschwindigkeit des Programms bestimmt wurde. Gleichzeitig ist es in anspruchsvolleren Anwendungen sinnvoll, einen eigenen Nachrichtenhandler zu erstellen, wie er hier und hier auf dem Haber dargestellt wird.

Beleuchtungsberechnung


Alle in der Anwendung verwendeten Lichtquellen werden im Standard- IES- Format geliefert, das die Verteilung des Lichts in verschiedene Richtungen beschreibt ( Spezifikation ). Dieses Format wird häufig in professionellen CAD-Systemen und 3D-Editoren verwendet. Es ist eine Textdatei, die die Lichtintensität in verschiedene Richtungen und zusätzliche Metainformationen angibt, die den Typ, die Gesamtintensität der Quelle, die Achsen und Symmetrieebenen angeben. Aufgrund der Symmetrie der Geräte kann die ies-Datei sehr klein sein. Beispielsweise reicht es bei axialer Symmetrie aus, die Lichtspur nur in einer Ebene anzuzeigen.

Beispiel einer einfachen IES-Datei
IESNA91[TEST] Simple demo intensity distribution [MANUFAC] Lightscape Technologies, Inc. TILT=NONE 1 -1 1 8 1 1 2 0.0 0.0 0.0 1.0 1.0 0.0 0.0 5.0 10.0 20.0 30.0 45.0 65.0 90.0 0.0 1000.0 1100.0 1300.0 1150.0 930.0 650.0 350.0 0.0 


Zur Anzeige des Strahlungsmusters wurden zwei Arten der Anzeige verwendet:

  • Lichtintensitätskurven (KSS) ist ein zweidimensionales Diagramm, das die Lichtintensität in einer der Hauptebenen in Abhängigkeit von der Richtung zeigt. Der Einfachheit halber kann dieser Graph sowohl im polaren als auch im kartesischen Koordinatensystem dargestellt werden.

    Bild
  • Photometrischer Körper - ein dreidimensionales Bild der Lichtintensität in verschiedene Richtungen

    Bild

Berechnungsmodul


Um die Beleuchtung zu berechnen, hatte der Kunde sein eigenes C ++ - Modul, das in anderen Produkten des Unternehmens verwendet wurde, und musste es daher in das Unity-Projekt integrieren. Die Reihenfolge der Modulverbindungen unterschied sich von der verwendeten Plattform.

  • Auf der iOS-Plattform kann Unitu C-Funktionen direkt aufrufen. Kopieren Sie einfach den Quellcode des Moduls direkt in das Projekt und fügen Sie Klassen für die Interaktion mit Unity hinzu. Klassen können sowohl direkt im iOS-Projekt als auch im Plugins-Ordner gespeichert werden, die beim Exportieren des Projekts nach Xcode automatisch kopiert werden. Ein Beispiel für den Aufruf von C ++ - Funktionen lautet wie folgt:

     [DllImport("__Internal")] public static extern void calculateLight([MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] Light[] lights, int size, ref CalculationResult result); 
  • Auf der Android-Plattform muss das C ++ - Modul in eine separate Bibliothek vorkompiliert werden. Dies kann direkt erfolgen, indem dem Projekt C ++ - Quellen hinzugefügt und gradle eingerichtet werden, um sie in solchen Bibliotheken zu erstellen.
  • Zum Debuggen und Testen des Unity-Teils wurde die Entwicklung auf einem Windows-Computer durchgeführt, sodass der Quellcode des Moduls auch in Windows verbunden werden musste. Dies geschieht ähnlich wie beim Android-Projekt, nur dass in diesem Fall die heruntergeladenen Dateien in der DLL-Bibliothek gesammelt und mit dem Projekt verbunden werden.

Leichte Kartenanzeige


Auf Wunsch des Kunden sollten die Ergebnisse der Beleuchtungsberechnung auf der Oberfläche der Szene angezeigt werden. Auf der Oberfläche von Straßen ist es notwendig, fiktive Farben mit der Anzeige einer Skala zur Anpassung von Farbe und Lichtintensität zu verwenden, und auf dem Rest der Oberfläche ist es ausreichend, nur die Helligkeit anzuzeigen.

Bild


Wie bereits erwähnt, wurde die gesamte Berechnung von einem C ++ - Plug-In durchgeführt, an das Daten zu Farbquellen übertragen wurden. Das Ergebnis der Berechnung war eine zweidimensionale Anordnung der Lichtintensität über die gesamte Oberfläche der Szene mit einem bestimmten Detail.

Die resultierende Bestrahlungsstärkekarte wurde auf den minimalen, maximalen Wert analysiert, mit dem eine eindimensionale Gradiententextur (GradientRamp) erstellt wurde. Unter Verwendung dieser Textur wurde die Lichtintensität direkt im Fragment-Shader in fiktive Farben umgewandelt. Zur gleichen Zeit wurde der gleiche Shader für verschiedene Straßenoberflächen und die Erdoberfläche verwendet, und das Umschalten des Beleuchtungsmodus wurde unter Verwendung des " Multi-Compile " -Shaders bereitgestellt.

Generierung von PDF-Dateien


In Übereinstimmung mit den technischen Anforderungen sollte für den Benutzer ein Bericht erstellt werden, der Informationen über die allgemeine Szene (Abmessungen, Bilder, Beleuchtungsparameter) und Informationen zu jedem verwendeten Leuchtentyp enthält und deren Position, Richtung der Eigenschaften sowie KCC-Diagramme und die Anzeige des photometrischen Körpers angibt.
Da der Bericht sowohl auf iOS als auch auf Android angezeigt werden sollte, musste der Bericht direkt im Unity-Modul generiert und anschließend mit nativen Standardtools angezeigt werden.

Bild

Um PDF zu erstellen, wurde die iTextSharp- Bibliothek ausgewählt, die unseren Anforderungen entspricht. Das Erstellen eines Berichts darin ist nicht besonders schwierig und besteht darin, Textblöcke, Tabellen und Bilder direkt aus dem Code zu erstellen. Während der Entwicklung waren wir jedoch mit vielen Nuancen konfrontiert, deren Lösung manchmal erhebliche Anstrengungen erforderte. Das Hauptproblem war der Start der Berichterstellung im Hintergrundthread.

Wenn beim Testen auf einem Desktop-Computer die PDF-Generierung in der Größenordnung von mehreren Sekunden lag, erreichte diese Zeit beim Testen auf dem iPad mini 3 leicht 1-3 Minuten. Natürlich musste die Erstellung des Berichts in einen separaten Thread übertragen werden, um Probleme mit der Unterbrechung der Schnittstelle zu vermeiden. Im allgemeinen Fall ist dies kein Problem, dies ist jedoch nicht der Fall, wenn Unity verwendet wird, bei dem die Verwendung der Unity-API von außerhalb des Hauptthreads ausdrücklich untersagt ist. Gleichzeitig mussten wir für den Bericht mindestens das CSS und das Szenenbild rendern, was nur aus dem Hauptstrom erfolgen sollte.

Um den Bericht zu erstellen, müssen wir die Aufgaben in einer bestimmten Reihenfolge ausführen. Gleichzeitig können einige von ihnen im Hintergrundthread arbeiten, und ein Teil muss im Hauptthread gestartet werden.

Um dieses Problem zu lösen, können Sie auf den ersten Blick versuchen, den Standardmechanismus zu verwenden und jede Operation in einer separaten Coroutine auszuführen. Dies erspart uns jedoch nicht das Problem des Bremsens der Schnittstelle. Wie Sie wissen, arbeiten Coroutinen im Hauptthread und sind nicht für langsame Operationen geeignet. Gleichzeitig erfordern viele Vorgänge beim Erstellen eines Berichts viel Zeit, und daher können Coroutinen nicht zur Lösung unseres Problems beitragen.

UniRx


Eine andere Lösung besteht darin, den Code in einen Teil aufzuteilen, der im Hauptthread arbeiten muss, und einen Teil, der in einem separaten Thread ausgeführt werden kann. In diesem Fall können beispielsweise Bilder mithilfe des Coroutine-Mechanismus erstellt und anschließend in einem separaten Stream in den Bericht eingebettet werden. In diesem Fall müssen jedoch irgendwo Zwischenergebnisse gespeichert werden, wodurch die Menge des verwendeten Speichers oder der freie Speicherplatz auf dem Gerät zusätzlich eingeschränkt wird.

In unserer Anwendung haben wir es vorgezogen, den direkten Weg zu gehen und Aufgaben nacheinander im Haupt- oder im Hintergrund-Thread auszuführen. Das einzige Problem bestand darin, einen solchen Start von Aufgaben zu organisieren, um nicht in diesem Durcheinander hängen zu bleiben und Vorgänge korrekt zu synchronisieren.
Eine wichtige Hilfe bei der Lösung dieses Problems war die Verwendung von Rx, seiner Verkörperung als kostenloses UniRx- Asset , das hier und hier auf dem Hub bereits ausführlich beschrieben wurde.

Die Verwendung hat die Interaktion zwischen Threads erheblich vereinfacht. Das folgende Beispiel zeigt, dass Sie mehrere Methoden in strikter Reihenfolge, jedoch in unterschiedlichen Threads ausführen können

Codebeispiel
 var initializer = Observable.FromCoroutine(initMethod); var heavyMethod1 = Observable.Start(() => doHardWork()); var mainThread1 = Observable.FromCoroutine(renderImage); var heavyMethod2 = Observable.Start(() => doHardWork2()); initializer.SelectMany(heavyMethod1) .SelectMany(mainThread1) .SelectMany(heavyMethod2) .ObserveOnMainThread() .Subscribe((x) => done()) .AddTo(this); 

In diesem Beispiel wird die Methode doHardWork () nacheinander im Hintergrundthread ausgeführt. Nach Abschluss wird renderImage () im Hauptthread gestartet und danach wird doHardWork2 () im Hintergrundthread erneut ausgeführt.

Es ist auch erwähnenswert, dass bei der Analyse der Berichtserstellung auf Leistung festgestellt wurde, dass der langsamste Teil die Implementierung von Bildern im Bericht ist. Eine Suche im Internet hat gezeigt, dass wir nicht die einzigen sind, die mit diesem Problem konfrontiert sind, aber es gab keine geeignete Lösung für uns. Wir mussten die Bildqualität leicht auf ein akzeptables Maß reduzieren, was zu einer Geschwindigkeitssteigerung von 20-40% führte.

So war es in der von uns erstellten Anwendung möglich, die Unity-Grafik-Engine erfolgreich in die native iOS / Android-Anwendung einzuführen. Dies unterscheidet sich vom herkömmlichen Ansatz, wenn Unity der Hauptteil der Anwendung ist und die spezifischen Eigenschaften des Systems über das Plug-In-System berücksichtigt werden. Gleichzeitig kann unser Ansatz nützlich sein, wenn Sie eine komplexe native Oberfläche entwickeln müssen, in die Sie nicht triviale 3D-Grafiken einbetten möchten.

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


All Articles