
Unity3D es una conocida plataforma de desarrollo de juegos en 3D y 2D que ha ganado popularidad en todo el mundo. Al mismo tiempo, sus capacidades no se limitan al desarrollo de solo aplicaciones de juegos, sino que son adecuadas para su uso en cualquier otra área que requiera la creación de aplicaciones multiplataforma para trabajar con gráficos. En este artículo hablaremos sobre la experiencia de usar Unity3D para desarrollar un sistema para calcular la iluminación de espacios abiertos.
La compañía con la que colaboramos es la corporación internacional de iluminación
BOOS LIGHTING GROUP . Para ampliar el atractivo de sus productos y simplificar la interacción con los clientes, fue necesario desarrollar una aplicación que le permita simular visualmente la ubicación de los dispositivos de iluminación, así como realizar cálculos de iluminación y mostrar la información técnica necesaria en el informe. Se asumió que la aplicación fue lanzada en una tableta iPad o Android por un cliente potencial o representante de ventas y le permite al cliente tener una idea inmediata sobre la posibilidad de instalaciones de iluminación.
El trabajo se llevó a cabo en etapas sobre la base de la especificación desarrollada de requisitos y consultas de la corporación
BOOS LIGHTING GROUP sobre el tema del proyecto.
En términos generales, la aplicación es un editor que le permite agregar y editar elementos de iluminación, carreteras, elementos decorativos, realizar cálculos de ingeniería de iluminación de la escena, mostrar un informe en pdf. Cada elemento tiene su propio conjunto de parámetros para editar y subtipos que afectan su visualización y cálculo.
- Hay varios tipos de mástiles de iluminación, con varios tipos de accesorios, ángulos de inclinación de lámparas y longitudes de extensión. Para ciertos tipos de luminarias, el ajuste individual es posible con una indicación de la dirección de la iluminación.
- Las carreteras pueden ser secciones lineales, elementos de arco, área, anillo. Para cada elemento, las dimensiones, la posición, el tipo de diseño y la capa se pueden personalizar.
- Elementos decorativos: automóviles, árboles, arbustos, señales de tráfico.
Todos los elementos de la escena se pueden girar y mover. También se admiten acciones estándar para revertir o volver a intentar. La configuración general del proyecto le permite especificar la textura del dorg, la superficie de la tierra, la visualización de parámetros adicionales. La escena se muestra en modos 2D / 3D. Y al calcular la iluminación en la superficie, se muestra un mapa de la iluminación de la superficie en colores ficticios.
Si es posible, toda la interfaz de usuario debe hacerse con herramientas nativas de iOS / Android.
El principal requisito técnico para la aplicación es poder calcular la iluminación del escenario de acuerdo con las especificaciones técnicas de los accesorios. También se requería la capacidad de cada dispositivo para mostrar y ver su patrón de radiación (curvas de intensidad de luz) en modos 3D / 2D.
Selección de plataforma
Para implementar el proyecto, elegimos Unity como más conveniente para que implementemos la funcionalidad requerida. En general, nuestra empresa tenía experiencia trabajando con otros motores y plataformas 3D (OpenSceneGraph, Ogre3D, LibGdx) y técnicamente todos pueden hacer frente a la tarea requerida, pero esta vez la elección recayó en Unity, lo que facilita la administración de la escena durante el desarrollo y la depuración en el proceso de trabajo.
Las principales dificultades
No entraremos en las complejidades del desarrollo de toda la aplicación, ya que técnicamente la funcionalidad para mostrar y editar la escena es bastante estándar. Naturalmente, hubo dificultades con los mecanismos de edición específica de objetos, agregarlos y eliminarlos, así como guardar una cadena de comandos para la posibilidad de repetir y cancelar acciones.
Nos gustaría detenernos solo en las características del sistema relacionadas con el trabajo con la interfaz de usuario nativa, generar informes en PDF y trabajar con fotometría y cálculos de iluminación.
Trabajar con IU nativa
En la mayoría de los casos, Unity interactúa con las funciones nativas del sistema utilizando el sistema de complemento, que le permite integrar la funcionalidad deseada en la aplicación. Sin embargo, en nuestro caso la situación es algo opuesta. Necesitábamos mostrar una IU completa en la parte superior de la ventana de Unity.
Afortunadamente, Unity puede exportar un proyecto que puede usarse como base para una aplicación nativa. La principal dificultad en este caso es cómo integrar una IU adicional en el proyecto resultante. Además, es igualmente importante que al ensamblar un proyecto de Unity, su formato y ubicación de archivo estén formados por Unity y se reescriban parcialmente, lo que limita la posibilidad de modificar el proyecto.
Al desarrollar una aplicación para iOS, utilizamos el mecanismo propuesto en el
artículo . Durante el desarrollo, se utilizó Unity 5.5 y, por el momento, lo que se indica en él puede perder relevancia. Al ensamblar el proyecto de Android, no hubo problemas adicionales, con la excepción de que Unity sobrescribe el archivo de manifiesto y los archivos de recursos cada vez.
Un problema adicional es que Unity solo puede funcionar en una ventana. Al mismo tiempo, necesitábamos asegurarnos de que Unity mostrara toda la escena, y al abrir la ventana de configuración, debería mostrarse un modelo 3D del cuerpo fotométrico de la lámpara. Para lograr esto, tuve que usar un "hack" y usar el mismo objeto UIView en diferentes ventanas.
Para enviar mensajes, utilizamos la funcionalidad estándar que ofrece Unity. Es decir, todos los mensajes estaban en formato json y se transmitían en líneas simples. Esto no impuso ninguna restricción en el rendimiento ya que el tamaño de los mensajes alcanzó un máximo de 100 caracteres, y su frecuencia fue determinada por la velocidad del trabajo con el programa. Al mismo tiempo, en aplicaciones más exigentes, tiene sentido crear su propio controlador de mensajes tal como se presenta en Haber
aquí y
aquí .
Cálculo de iluminación
Todas las fuentes de luz utilizadas en la aplicación se entregan en formato
IES estándar, que describe la distribución de la luz en diferentes direcciones (
especificación ). Este formato es ampliamente utilizado en sistemas CAD profesionales y editores 3D. Es un archivo de texto que indica la intensidad de la luz en varias direcciones y una metainformación adicional que indica el tipo, la intensidad total de la fuente, los ejes y los planos de simetría. Dada la simetría de los aparatos, el archivo ies puede ser muy pequeño. Por ejemplo, en el caso de la simetría axial, es suficiente indicar el trazado de la luz en un solo plano.
Ejemplo de un archivo IES simpleIESNA91[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
Para mostrar el patrón de radiación, se utilizaron dos tipos de pantalla:
- Las curvas de intensidad de luz (KSS) es un gráfico bidimensional que muestra la intensidad de la luz en uno de los planos principales según la dirección. Por conveniencia, este gráfico puede representarse tanto en el sistema de coordenadas polares como en el cartesiano.
- Cuerpo fotométrico: una imagen tridimensional de la intensidad de la luz en diferentes direcciones.
Módulo de cálculo
Para calcular la iluminación, el cliente tenía su propio módulo C ++ utilizado en otros productos de la compañía y, por lo tanto, debía integrarlo en el proyecto de Unity. El orden de conexión del módulo fue diferente de la plataforma utilizada.
- En la plataforma iOS, Unitu puede llamar directamente a las funciones C, así que simplemente copie el código fuente del módulo directamente en el proyecto y agregue clases para su interacción con Unity. Las clases se pueden almacenar directamente en el proyecto de iOS y en la carpeta de complementos, que se copian automáticamente cuando el proyecto se exporta a Xcode. Un ejemplo de llamar a funciones de C ++ es el siguiente:
[DllImport("__Internal")] public static extern void calculateLight([MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] Light[] lights, int size, ref CalculationResult result);
- En la plataforma Android, el módulo C ++ debe estar precompilado en una biblioteca separada. Esto se puede hacer directamente agregando fuentes C ++ al proyecto y configurando gradle para construirlas en bibliotecas.
- Además, para depurar y probar la parte de Unity, el desarrollo se llevó a cabo en una máquina con Windows, por lo que también fue necesario conectar el código fuente del módulo en Windows. Esto se hace de manera similar al proyecto de Android, solo en este caso los archivos descargados se recopilan en la biblioteca dll y se conectan al proyecto.
Pantalla de mapa de luz
A petición del cliente, los resultados del cálculo de la iluminación deben mostrarse en la superficie de la escena. En la superficie de las carreteras, es necesario usar colores ficticios con la visualización de una escala para igualar el color y la intensidad de la luz, y en el resto de la superficie, basta con mostrar su brillo.
Como se mencionó anteriormente, todo el cálculo se realizó mediante un complemento C ++ al que se transmitieron los datos sobre las fuentes de color. El resultado del cálculo fue una matriz bidimensional de intensidad de luz sobre toda la superficie de la escena con un detalle dado.
El mapa de irradiancia resultante se analizó para determinar el valor mínimo y máximo mediante el cual se construyó una textura de gradiente unidimensional (GradientRamp). Usando esta textura, la intensidad de la luz se convirtió en colores ficticios directamente en el sombreador de fragmentos. Al mismo tiempo, se usó el mismo sombreador para diferentes superficies de carreteras y la superficie de la tierra, y se cambió el modo de iluminación utilizando el sombreador de "
compilación múltiple ".
Generación de archivos PDF
De acuerdo con los requisitos técnicos, se generaría un informe para el usuario que contenía información sobre la escena general (dimensiones, sus imágenes, parámetros de iluminación) e información sobre cada tipo de luminaria utilizada, indicando su posición, dirección de las características, así como diagramas KCC y una pantalla fotométrica del cuerpo.
Dado que el informe debía mostrarse tanto en iOS como en Android, era necesario generar su informe directamente en el módulo Unity y luego mostrarlo utilizando herramientas nativas estándar.
Para compilar pdf, se seleccionó la biblioteca
iTextSharp que cumple con nuestros requisitos. Crear un informe en él no es particularmente difícil y consiste en crear bloques de texto, tablas e imágenes directamente desde el código. Sin embargo, durante el desarrollo, nos enfrentamos a muchos matices, cuya solución a veces requería un esfuerzo considerable. El principal problema fue el lanzamiento de la generación de informes en el hilo de fondo.
Si al probar en una máquina de escritorio, la generación de pdf fue del orden de varios segundos, entonces, al probar en iPad mini 3, esta vez alcanzó fácilmente 1-3 minutos. Naturalmente, la creación del informe necesitaba ser transferida a un hilo separado, para evitar problemas con la suspensión de la interfaz. En el caso general, esto no es un problema, pero este no es el caso cuando se usa Unity, en el que está explícitamente prohibido usar la API de Unity desde fuera del hilo principal. Al mismo tiempo, para el informe necesitábamos, como mínimo, renderizar el CSS y la imagen de la escena, lo que debería hacerse solo desde la transmisión principal.
Por lo tanto, para construir el informe, necesitamos ejecutar las tareas en una secuencia determinada y, al mismo tiempo, algunas de ellas pueden funcionar en el hilo de fondo y parte debe iniciarse en la principal.
A primera vista, para resolver este problema, puede intentar usar el mecanismo estándar y ejecutar cada operación en una rutina separada. Sin embargo, esto no nos salva del problema de frenar la interfaz. Como sabe, las corutinas funcionan en el hilo principal y no son adecuadas para operaciones lentas. Al mismo tiempo, al generar un informe, muchas operaciones requieren un tiempo significativo y, por lo tanto, las rutinas no pueden ayudar a resolver nuestro problema.
UniRx
Otra solución es dividir el código en una parte que necesita trabajar en el hilo principal y una parte que se puede ejecutar en un hilo separado. En este caso, por ejemplo, las imágenes se pueden construir utilizando el mecanismo de rutina y luego se pueden incrustar en el informe en una secuencia separada. Sin embargo, en este caso, será necesario guardar resultados intermedios en algún lugar, lo que impone restricciones adicionales en la cantidad de memoria utilizada o espacio libre en el dispositivo.
En nuestra aplicación, preferimos seguir el camino directo y ejecutar tareas secuencialmente en los hilos principales o en segundo plano. El único problema era cómo organizar tal lanzamiento de tareas para no estancarse en este desastre y sincronizar correctamente las operaciones.
El uso de Rx, su encarnación como un
activo gratuito de UniRx, que ya se describió en detalle
aquí y
aquí en el
centro , brindó una ayuda importante para resolver este problema.
Su uso ha simplificado enormemente la interacción entre hilos y el siguiente ejemplo muestra que puede ejecutar varios métodos en secuencia estricta, pero en diferentes hilos
Ejemplo de código 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);
En este ejemplo, el método doHardWork () se ejecutará secuencialmente en el hilo de fondo. Después de su finalización, renderImage () se iniciará en el hilo principal, y luego doHardWork2 () se ejecutará nuevamente en el hilo de fondo.
También vale la pena señalar que durante el análisis de la generación de informes para el rendimiento se descubrió que la parte más lenta es la implementación de imágenes en el informe. Una búsqueda en Internet mostró que no somos los únicos que enfrentamos este problema, pero que no había una solución adecuada para nosotros. Tuvimos que reducir ligeramente la calidad de la imagen a un nivel aceptable, lo que aumentó la velocidad en un 20-40%.
Por lo tanto, en la aplicación que creamos, fue posible introducir con éxito el motor de gráficos Unity en la aplicación nativa de iOS / Android. Esto difiere del enfoque tradicional cuando Unity es la parte principal de la aplicación y aborda las propiedades específicas del sistema a través del sistema de complemento. Al mismo tiempo, nuestro enfoque puede ser útil si necesita desarrollar una interfaz nativa compleja en la que desee incrustar gráficos 3D no triviales.