Utilisation d'Unity3D dans une application iOS / Android native pour modéliser l'éclairage des espaces ouverts

image

Unity3D est une plateforme de développement de jeux 3D et 2D bien connue qui a gagné en popularité dans le monde entier. Dans le même temps, ses capacités ne se limitent pas au développement d'applications de jeu uniquement, mais conviennent à une utilisation dans tous les autres domaines qui nécessitent la création d'applications multiplateformes pour travailler avec des graphiques. Dans cet article, nous parlerons de l'expérience d'utilisation d'Unity3D pour développer un système de calcul de l'éclairage des espaces ouverts.

L'entreprise avec laquelle nous avons collaboré est la société internationale d'éclairage BOOS LIGHTING GROUP . Afin d'étendre l'attractivité de ses produits et de simplifier l'interaction avec les clients, il était nécessaire de développer une application qui permet de simuler visuellement l'emplacement des dispositifs d'éclairage, ainsi que d'effectuer des calculs d'éclairage et d'afficher les informations techniques nécessaires dans le rapport. Il a été supposé que l'application a été lancée sur un iPad ou une tablette Android par un client potentiel ou un représentant commercial et permet au client de se faire une idée immédiate de la possibilité d'éclairage.

Le travail a été réalisé par étapes sur la base du cahier des charges élaboré et des consultations de la société BOOS LIGHTING GROUP sur le sujet du projet.

De manière générale, l'application est un éditeur qui vous permet d'ajouter et de modifier des éléments d'éclairage, des routes, des éléments décoratifs, d'effectuer des calculs d'ingénierie d'éclairage de la scène, d'afficher un rapport en pdf. Chaque élément possède son propre ensemble de paramètres d'édition et de sous-types qui affectent son affichage et son calcul.

  • Il existe plusieurs types de mâts d'éclairage, avec différents types de luminaires, des angles d'inclinaison des lampes et des longueurs d'extension. Pour certains types de luminaires, un réglage individuel est possible avec indication de la direction de l'éclairage.

    image
  • Les routes peuvent être des sections linéaires, des éléments d'arc, une zone, un anneau. Pour chaque élément, les dimensions, la position, le type de disposition, la couche peuvent être personnalisés.

    image
  • Éléments décoratifs - voitures, arbres, buissons, panneaux de signalisation

Tous les éléments de la scène peuvent être tournés et déplacés. Les actions standard pour revenir en arrière ou réessayer sont également prises en charge. Les paramètres généraux du projet vous permettent de définir la texture du dorg, la surface de la terre, en affichant des paramètres supplémentaires. La scène est affichée en modes 2D / 3D. Et lors du calcul de l'illumination sur la surface, une carte de l'illumination de la surface en couleurs fictives est affichée.

image


Si possible, l'interface utilisateur complète doit être réalisée avec des outils iOS / Android natifs.
La principale exigence technique de l'application est de pouvoir calculer l'éclairage de la scène en fonction des spécifications techniques des luminaires. La capacité de chaque appareil à afficher et à visualiser son diagramme de rayonnement (courbes d'intensité lumineuse) en modes 3D / 2D était également requise.

Sélection de la plateforme


Pour implémenter le projet, nous avons choisi Unity comme plus pratique pour nous d'implémenter les fonctionnalités requises. En général, notre entreprise avait de l'expérience avec d'autres moteurs et plates-formes 3D (OpenSceneGraph, Ogre3D, LibGdx) et techniquement, ils peuvent tous faire face à la tâche requise, mais cette fois, le choix s'est porté sur Unity, ce qui facilite la gestion de la scène lors du développement et du débogage. en cours de travail.

Les principales difficultés


Nous n'entrerons pas dans les subtilités du développement de l'application entière, car la fonctionnalité technique pour afficher et éditer la scène est assez standard. Naturellement, il y avait des difficultés avec les mécanismes d'édition spécifique d'objets, en les ajoutant et en les supprimant, ainsi qu'en sauvegardant une chaîne de commandes pour la possibilité de répéter et d'annuler des actions.
Nous aimerions nous attarder uniquement sur les caractéristiques du système liées à l'utilisation de l'interface utilisateur native, à la génération de rapports pdf et au travail avec les calculs de photométrie et d'éclairage.

Travailler avec l'interface utilisateur native


Dans la plupart des cas, Unity interagit avec les fonctions natives du système à l'aide du système de plug-in, ce qui vous permet d'incorporer les fonctionnalités souhaitées dans l'application. Cependant, dans notre cas, la situation est quelque peu opposée. Nous devions avoir une interface utilisateur complète affichée en haut de la fenêtre Unity.

image


Heureusement, Unity peut exporter un projet qui peut être utilisé comme base pour une application native. La principale difficulté dans ce cas est de savoir comment intégrer une interface utilisateur supplémentaire dans le projet résultant. En outre, il est tout aussi important que lors de l'assemblage d'un projet Unity, son format et l'emplacement du fichier soient formés par Unity et partiellement réécrits, ce qui limite la possibilité de modifier le projet.

Lors du développement d'une application iOS, nous avons utilisé le mécanisme proposé dans l' article . Pendant le développement, Unity 5.5 a été utilisé et pour le moment, ce qui y est indiqué peut perdre de sa pertinence. Lors de l'assemblage du projet Android, il n'y a eu aucun problème supplémentaire, à l'exception que Unity écrase le fichier manifeste et les fichiers de ressources à chaque fois.
Un problème supplémentaire est que Unity ne peut fonctionner que dans une seule fenêtre. Dans le même temps, nous devions nous assurer que Unity affiche la scène entière, et lors de l'ouverture de la fenêtre des paramètres, un modèle 3D du corps photométrique de la lampe doit être affiché. Pour ce faire, j'ai dû utiliser un «hack» et utiliser le même objet UIView dans différentes fenêtres.

Pour envoyer des messages, nous avons utilisé la fonctionnalité standard offerte par Unity. Autrement dit, tous les messages étaient au format json et transmis en lignes simples. Cela n'imposait aucune restriction sur les performances, car la taille des messages atteignait un maximum de 100 caractères et leur fréquence était déterminée par la vitesse du programme. Dans le même temps, dans des applications plus exigeantes, il est logique de créer votre propre gestionnaire de messages tel que présenté sur Haber ici et ici .

Calcul d'éclairage


Toutes les sources lumineuses utilisées dans l'application sont livrées au format IES standard, qui décrit la répartition de la lumière dans différentes directions ( spécification ). Ce format est largement utilisé dans les systèmes de CAO professionnels et les éditeurs 3D. Il s'agit d'un fichier texte indiquant l'intensité lumineuse dans différentes directions et des méta-informations supplémentaires indiquant le type, l'intensité totale de la source, les axes et les plans de symétrie. Étant donné la symétrie des appareils, le fichier ies peut être très petit. Par exemple, dans le cas d'une symétrie axiale, il suffit d'indiquer le tracé de la lumière dans un seul plan.

Exemple d'un simple fichier IES
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 


Pour afficher le diagramme de rayonnement, deux types d'affichage ont été utilisés:

  • Les courbes d'intensité lumineuse (KSS) est un graphique à deux dimensions qui montre l'intensité lumineuse dans l'un des plans principaux en fonction de la direction. Pour plus de commodité, ce graphique peut être représenté à la fois dans le système de coordonnées polaire et cartésien.

    image
  • Corps photométrique - une image tridimensionnelle de l'intensité lumineuse dans différentes directions

    image

Module de calcul


Pour calculer l'éclairage, le client avait son propre module C ++ utilisé dans d'autres produits de l'entreprise, et il a donc été nécessaire de l'intégrer dans le projet Unity. L'ordre de connexion des modules était différent de la plate-forme utilisée.

  • Sur la plate-forme iOS, Unitu peut appeler directement les fonctions C, il suffit donc de copier le code source du module directement dans le projet et d'ajouter des classes pour son interaction avec Unity. Les classes peuvent être stockées à la fois directement dans le projet iOS et dans le dossier des plugins, qui sont automatiquement copiés lorsque le projet est exporté vers Xcode. Un exemple d'appel de fonctions C ++ est le suivant:

     [DllImport("__Internal")] public static extern void calculateLight([MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] Light[] lights, int size, ref CalculationResult result); 
  • Sur la plate-forme Android, le module C ++ doit être précompilé dans une bibliothèque distincte. Cela peut être fait directement en ajoutant des sources C ++ au projet et en configurant gradle pour les construire dans des bibliothèques so.
  • De plus, pour le débogage et le test de la partie Unity, le développement a été effectué sur une machine Windows, il était donc nécessaire de connecter le code source du module sous Windows également. Cela se fait de manière similaire au projet Android, seulement dans ce cas, les fichiers téléchargés sont collectés dans la bibliothèque dll et connectés au projet.

Affichage de la carte lumineuse


À la demande du client, les résultats du calcul de l'éclairage doivent être affichés à la surface de la scène. Sur la surface des routes, il est nécessaire d'utiliser des couleurs fictives avec l'affichage d'une échelle pour faire correspondre la couleur et l'intensité lumineuse, et sur le reste de la surface, il suffit d'afficher sa luminosité.

image


Comme mentionné précédemment, le calcul complet a été effectué par un plug-in C ++ auquel les données sur les sources de couleurs ont été transmises. Le résultat du calcul était un réseau bidimensionnel d'intensité lumineuse sur toute la surface de la scène avec un détail donné.

La carte d'irradiance résultante a été analysée pour la valeur minimale et maximale par laquelle une texture de gradient unidimensionnelle (GradientRamp) a été construite. En utilisant cette texture, l'intensité lumineuse a été convertie en couleurs fictives directement dans le fragment shader. Dans le même temps, le même shader a été utilisé pour différentes surfaces de route et la surface de la terre, et la commutation du mode d'éclairage a été fournie en utilisant le shader " multi-compilation ".

Génération de fichiers PDF


Conformément aux exigences techniques, un rapport devait être généré pour l'utilisateur, contenant des informations sur la scène générale (dimensions, ses images, paramètres d'éclairage) et des informations sur chaque type de luminaire utilisé, indiquant leur position, la direction des caractéristiques, ainsi que des diagrammes KCC et l'affichage du corps photométrique.
Comme le rapport devait être affiché sur iOS et Android, il était nécessaire de générer son rapport directement dans le module Unity, puis de l'afficher à l'aide d'outils natifs standard.

image

Pour construire le pdf, la bibliothèque iTextSharp a été sélectionnée qui répond à nos exigences. La création d'un rapport n'est pas particulièrement difficile et consiste à créer des blocs de texte, des tableaux, des images directement à partir du code. Cependant, au cours du développement, nous avons été confrontés à de nombreuses nuances, dont la solution nécessitait parfois des efforts considérables. Le problème principal était le lancement de la génération de rapports dans le fil de fond.

Si lors des tests sur une machine de bureau, la génération de pdf était de l'ordre de quelques secondes, alors lors des tests sur iPad mini 3, cette fois atteignait facilement 1 à 3 minutes. Naturellement, la création du rapport devait être transférée dans un thread séparé, afin d'éviter des problèmes avec la suspension de l'interface. Dans le cas général, ce n'est pas un problème, mais ce n'est pas le cas lors de l'utilisation de Unity, dans lequel il est explicitement interdit d'utiliser l'API Unity en dehors du thread principal. Dans le même temps, pour le rapport, nous avions besoin, au minimum, de rendre le CSS et l'image de la scène, ce qui ne devrait être fait qu'à partir du flux principal.

Ainsi, pour créer le rapport, nous devons exécuter les tâches dans une certaine séquence, et en même temps, certaines d'entre elles peuvent fonctionner dans le thread d'arrière-plan, et une partie doit être lancée dans le thread principal.

À première vue, pour résoudre ce problème, vous pouvez essayer d'utiliser le mécanisme standard et exécuter chaque opération dans une coroutine distincte. Cependant, cela ne nous évite pas le problème du freinage de l'interface. Comme vous le savez, les coroutines fonctionnent dans le thread principal et ne conviennent pas aux opérations lentes. Dans le même temps, lors de la génération d'un rapport, de nombreuses opérations nécessitent beaucoup de temps, et donc les coroutines ne peuvent pas aider à résoudre notre problème.

UniRx


Une autre solution consiste à diviser le code en une partie qui doit fonctionner dans le thread principal et une partie qui peut être exécutée dans un thread séparé. Dans ce cas, par exemple, les images peuvent être construites à l'aide du mécanisme coroutine, puis elles peuvent être intégrées dans le rapport dans un flux distinct. Cependant, dans ce cas, il sera nécessaire de sauvegarder les résultats intermédiaires quelque part, ce qui impose des restrictions supplémentaires sur la quantité de mémoire utilisée ou l'espace libre sur l'appareil.

Dans notre application, nous avons préféré aller directement et exécuter les tâches séquentiellement dans les threads principaux ou d'arrière-plan. Le seul problème était de savoir comment organiser un tel lancement de tâches pour ne pas s'enliser dans ce pétrin et synchroniser correctement les opérations.
Une aide significative pour résoudre ce problème a été apportée par l'utilisation de Rx, son mode de réalisation en tant qu'actif UniRx gratuit, qui a déjà été décrit en détail ici et ici sur le hub .

Son utilisation a grandement simplifié l'interaction entre les threads et l'exemple ci-dessous montre que vous pouvez exécuter plusieurs méthodes dans un ordre strict, mais dans des threads différents

Exemple de code
 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); 

Dans cet exemple, la méthode doHardWork () sera exécutée séquentiellement dans le thread d'arrière-plan. Après son achèvement, renderImage () sera lancé dans le thread principal, et après cela doHardWork2 () sera à nouveau exécuté dans le thread d'arrière-plan.

Il convient également de noter que lors de l'analyse de la génération de rapport pour les performances, il a été constaté que la partie la plus lente est la mise en œuvre des images dans le rapport. Une recherche sur Internet a montré que nous ne sommes pas les seuls à faire face à ce problème, mais il n'y avait pas de solution appropriée pour nous. Nous avons dû réduire légèrement la qualité de l'image à un niveau acceptable, ce qui a entraîné une augmentation de la vitesse de 20 à 40%.

Ainsi, dans l'application que nous avons créée, il a été possible d'introduire avec succès le moteur graphique Unity dans l'application iOS / Android native. Cela diffère de l'approche traditionnelle lorsque Unity est la partie principale de l'application et traite les propriétés spécifiques du système via le système de plug-in. Dans le même temps, notre approche peut être utile si vous avez besoin de développer une interface native complexe dans laquelle vous souhaitez intégrer des graphiques 3D non triviaux.

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


All Articles