Usando o Unity3D no aplicativo iOS / Android nativo para modelar a iluminação de espaços abertos

imagem

O Unity3D é uma plataforma de desenvolvimento de jogos 3D e 2D bem conhecida que ganhou popularidade em todo o mundo. Ao mesmo tempo, seus recursos não se limitam ao desenvolvimento de apenas aplicativos de jogos, mas são adequados para uso em outras áreas que exigem a criação de aplicativos de plataforma cruzada para trabalhar com gráficos. Neste artigo, falaremos sobre a experiência do uso do Unity3D para desenvolver um sistema para calcular a iluminação de espaços abertos.

A empresa com a qual colaboramos é a empresa de iluminação internacional BOOS LIGHTING GROUP . Para expandir a atratividade de seus produtos e simplificar a interação com os clientes, foi necessário desenvolver um aplicativo que permita simular visualmente a localização dos dispositivos de iluminação, executar cálculos de iluminação e exibir as informações técnicas necessárias no relatório. Supunha-se que o aplicativo foi lançado em um iPad ou tablet Android por um cliente em potencial ou representante de vendas e permite que o cliente tenha uma idéia imediata da possibilidade de iluminação das instalações.

O trabalho foi realizado em etapas com base na especificação desenvolvida de requisitos e consultas da empresa BOOS LIGHTING GROUP sobre o assunto do projeto.

Em termos gerais, o aplicativo é um editor que permite adicionar e editar elementos de iluminação, estradas, elementos decorativos, realizar cálculos de engenharia de iluminação da cena, exibir um relatório em pdf. Cada elemento possui seu próprio conjunto de parâmetros para edição e subtipos que afetam sua exibição e cálculo.

  • Existem vários tipos de mastros de iluminação, com vários tipos de acessórios de fixação, ângulos de inclinação das lâmpadas e comprimentos de extensão. Para certos tipos de luminárias, é possível um ajuste individual com uma indicação da direção da iluminação.

    imagem
  • As estradas podem ser seções lineares, elementos de arco, área, anel. Para cada elemento, dimensões, posição, tipo de layout, camada podem ser personalizadas.

    imagem
  • Elementos decorativos - carros, árvores, arbustos, sinais de trânsito

Todos os elementos da cena podem ser girados e movidos. Ações padrão para reverter ou tentar novamente também são suportadas. As configurações gerais do projeto permitem definir a textura do dorg, a superfície da terra, exibindo parâmetros adicionais. A cena é exibida nos modos 2D / 3D. E ao calcular a iluminação na superfície, um mapa da iluminação da superfície em cores fictícias é exibido.

imagem


Se possível, toda a interface do usuário deve ser feita com ferramentas nativas para iOS / Android.
O principal requisito técnico para a aplicação é poder calcular a iluminação do palco de acordo com as especificações técnicas dos equipamentos. Também era necessária a capacidade de cada equipamento exibir e visualizar seu padrão de radiação (curvas de intensidade de luz) nos modos 3D / 2D.

Seleção de plataforma


Para implementar o projeto, escolhemos o Unity como mais conveniente para implementarmos a funcionalidade necessária. Em geral, nossa empresa tinha experiência em trabalhar com outros mecanismos e plataformas 3D (OpenSceneGraph, Ogre3D, LibGdx) e tecnicamente todos podem lidar com a tarefa necessária, mas desta vez a escolha recaiu sobre o Unity, o que facilita o gerenciamento do cenário durante o desenvolvimento e a depuração no processo de trabalho.

As principais dificuldades


Não entraremos nos detalhes do desenvolvimento de todo o aplicativo, pois tecnicamente a funcionalidade para exibir e editar a cena é bastante padrão. Naturalmente, houve dificuldades com os mecanismos de edição específica de objetos, adicionando e excluindo-os, além de salvar uma cadeia de comandos para a possibilidade de repetir e cancelar ações.
Gostaríamos de abordar apenas os recursos do sistema relacionados ao trabalho com a interface do usuário nativa, gerando relatórios em pdf e trabalhando com cálculos de fotometria e iluminação.

Trabalhar com interface do usuário nativa


Na maioria dos casos, o Unity interage com as funções nativas do sistema usando o sistema de plug-in, o que permite incorporar a funcionalidade desejada no aplicativo. No entanto, no nosso caso, a situação é um pouco oposta. Precisávamos ter uma interface do usuário completa exibida na parte superior da janela do Unity.

imagem


Felizmente, o Unity pode exportar um projeto que pode ser usado como base para um aplicativo nativo. A principal dificuldade nesse caso é como integrar uma interface do usuário adicional ao projeto resultante. Além disso, é igualmente importante que, ao montar um projeto do Unity, seu formato e localização do arquivo sejam formados pelo Unity e parcialmente reescritos, o que limita a possibilidade de modificar o projeto.

Ao desenvolver um aplicativo iOS, usamos o mecanismo proposto no artigo . Durante o desenvolvimento, o Unity 5.5 foi usado e, no momento, o que é indicado nele pode perder relevância. Ao montar o projeto android, não houve problemas adicionais, com a exceção de que o Unity substitui o arquivo de manifesto e os arquivos de recursos a cada vez.
Um problema adicional é que o Unity pode funcionar apenas em uma janela. Ao mesmo tempo, precisávamos garantir que o Unity exibisse a cena inteira e, ao abrir a janela de configurações, um modelo 3D do corpo fotométrico da lâmpada deveria ser exibido. Para fazer isso, tive que usar um "hack" e usar o mesmo objeto UIView em janelas diferentes.

Para enviar mensagens, usamos a funcionalidade padrão oferecida pelo Unity. Ou seja, todas as mensagens estavam no formato json e transmitidas em linhas simples. Isso não impôs restrições ao desempenho, uma vez que o tamanho das mensagens atingiu um máximo de 100 caracteres e sua frequência foi determinada pela velocidade do trabalho com o programa. Ao mesmo tempo, em aplicativos mais exigentes, faz sentido criar seu próprio manipulador de mensagens, conforme apresentado no Haber aqui e aqui .

Cálculo de iluminação


Todas as fontes de luz usadas no aplicativo são fornecidas no formato IES padrão, que descreve a distribuição da luz em diferentes direções ( especificação ). Este formato é amplamente utilizado em sistemas profissionais de CAD e editores 3D. É um arquivo de texto indicando a intensidade da luz em várias direções e meta-informações adicionais indicando o tipo, intensidade total da fonte, eixos e planos de simetria. Dada a simetria dos equipamentos, o arquivo ies pode ser muito pequeno. Por exemplo, no caso de simetria axial, é suficiente indicar o traçado da luz em apenas um plano.

Exemplo de um arquivo IES simples
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 


Para exibir o padrão de radiação, dois tipos de exibição foram usados:

  • Curvas de intensidade de luz (KSS) é um gráfico bidimensional que mostra a intensidade da luz em um dos planos principais, dependendo da direção. Por conveniência, este gráfico pode ser representado no sistema de coordenadas polar e cartesiano.

    imagem
  • Corpo fotométrico - uma imagem tridimensional da intensidade da luz em diferentes direções

    imagem

Módulo de cálculo


Para calcular a iluminação, o cliente teve seu próprio módulo C ++ usado em outros produtos da empresa e, portanto, foi necessário integrá-lo ao projeto Unity. A ordem de conexão do módulo foi diferente da plataforma usada.

  • Na plataforma iOS, o Unitu pode chamar diretamente as funções C, portanto, basta copiar o código-fonte do módulo diretamente no projeto e adicionar classes para sua interação com o Unity. As classes podem ser armazenadas diretamente no projeto iOS e na pasta plugins, que são copiadas automaticamente quando o projeto é exportado para o Xcode. Um exemplo de chamada de funções C ++ é o seguinte:

     [DllImport("__Internal")] public static extern void calculateLight([MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] Light[] lights, int size, ref CalculationResult result); 
  • Na plataforma Android, o módulo C ++ deve ser pré-compilado em uma biblioteca separada. Isso pode ser feito diretamente adicionando fontes C ++ ao projeto e configurando o gradle para construí-las em bibliotecas.
  • Além disso, para depurar e testar a parte do Unity, o desenvolvimento foi realizado em uma máquina Windows, sendo necessário conectar também o código fonte do módulo no Windows. Isso é feito da mesma forma que no projeto android, apenas nesse caso os arquivos baixados são coletados na biblioteca dll e conectados ao projeto.

Exibição clara do mapa


A pedido do cliente, os resultados do cálculo da iluminação devem ser exibidos na superfície da cena. Na superfície das estradas, é necessário usar cores fictícias com a exibição de uma escala para combinar a intensidade da cor e da luz e, no restante da superfície, apenas exibir o brilho é suficiente.

imagem


Como mencionado anteriormente, todo o cálculo foi realizado por um plug-in C ++ para o qual foram transmitidos dados sobre fontes de cores. O resultado do cálculo foi uma matriz bidimensional de intensidade de luz em toda a superfície da cena com um determinado detalhe.

O mapa de irradiância resultante foi analisado para o valor mínimo e máximo pelo qual uma textura gradiente unidimensional (GradientRamp) foi construída. Usando essa textura, a intensidade da luz foi convertida em cores fictícias diretamente no shader do fragmento. Ao mesmo tempo, o mesmo sombreador foi usado para diferentes superfícies da estrada e da superfície da Terra, e a mudança do modo de iluminação foi fornecida usando o sombreador " multi-compile ".

Geração de arquivo PDF


De acordo com os requisitos técnicos para o usuário, era necessário gerar um relatório contendo informações sobre a cena geral (dimensões, imagens, parâmetros de iluminação) e informações sobre cada tipo de luminária utilizada, indicando sua posição, direção das características, bem como diagramas KCC e uma exibição fotométrica do corpo.
Como o relatório deveria ser exibido no iOS e no Android, era necessário gerar o relatório diretamente no módulo Unity e exibi-lo usando ferramentas nativas padrão.

imagem

Para criar o pdf, foi selecionada a biblioteca iTextSharp que atende aos nossos requisitos. Criar um relatório não é particularmente difícil e consiste em criar blocos de texto, tabelas e imagens diretamente do código. No entanto, durante o desenvolvimento, fomos confrontados com muitas nuances, cuja solução às vezes exigia um esforço considerável. O principal problema foi o lançamento da geração de relatórios no encadeamento em segundo plano.

Se, ao testar em uma máquina de mesa, a geração de pdf era da ordem de vários segundos, então, ao testar no iPad mini 3, esse tempo atingia facilmente 1-3 minutos. Naturalmente, a criação do relatório precisava ser transferida para um encadeamento separado, a fim de evitar problemas com a suspensão da interface. No caso geral, isso não é um problema, mas não é o caso ao usar o Unity, no qual é explicitamente proibido usar a API do Unity de fora do segmento principal. Ao mesmo tempo, para o relatório, precisávamos, no mínimo, renderizar o CSS e a imagem da cena, o que deveria ser feito apenas a partir do fluxo principal.

Portanto, para criar o relatório, precisamos executar as tarefas em uma determinada sequência e, ao mesmo tempo, algumas delas podem funcionar no encadeamento em segundo plano, e parte deve ser iniciada no principal.

À primeira vista, para resolver esse problema, você pode tentar usar o mecanismo padrão e executar cada operação em uma rotina separada. No entanto, isso não nos salva do problema de frear a interface. Como você sabe, as corotinas funcionam no encadeamento principal e não são adequadas para operações lentas. Ao mesmo tempo, ao gerar um relatório, muitas operações requerem um tempo significativo e, portanto, as corotinas não podem ajudar na solução do nosso problema.

UniRx


Outra solução é dividir o código em uma parte que precisa funcionar no encadeamento principal e uma parte que pode ser executada em um encadeamento separado. Nesse caso, por exemplo, as imagens podem ser construídas usando o mecanismo de rotina e, em seguida, podem ser incorporadas no relatório em um fluxo separado. No entanto, nesse caso, será necessário salvar resultados intermediários em algum lugar, o que impõe restrições adicionais à quantidade de memória usada ou espaço livre no dispositivo.

Em nosso aplicativo, preferimos seguir o caminho direto e executar tarefas sequencialmente nos threads principais ou nos segundo plano. O único problema era como organizar esse lançamento de tarefas para não ficar atolado nessa bagunça e sincronizar corretamente as operações.
Ajuda significativa na solução desse problema foi trazida pelo uso do Rx, sua incorporação como um ativo UniRx gratuito, que já foi descrito em detalhes aqui e aqui no hub .

Seu uso simplificou bastante a interação entre threads e o exemplo abaixo mostra que você pode executar vários métodos em sequência estrita, mas em threads diferentes

Exemplo 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); 

Neste exemplo, o método doHardWork () será executado sequencialmente no encadeamento em segundo plano. Após sua conclusão, renderImage () será iniciado no thread principal e, em seguida, doHardWork2 () será executado novamente no thread de segundo plano.

Também é importante notar que, durante a análise da geração de relatórios para desempenho, verificou-se que a parte mais lenta é a implementação de imagens no relatório. Uma pesquisa na Internet mostrou que não somos os únicos a enfrentar esse problema, mas não havia uma solução adequada para nós. Tivemos que reduzir um pouco a qualidade da imagem para um nível aceitável, o que deu um aumento na velocidade de 20 a 40%.

Assim, no aplicativo que criamos, foi possível introduzir com êxito o mecanismo de gráficos Unity no aplicativo iOS / Android nativo. Isso difere da abordagem tradicional quando o Unity é a parte principal do aplicativo e aborda as propriedades específicas do sistema através do sistema de plug-in. Ao mesmo tempo, nossa abordagem pode ser útil se você precisar desenvolver uma interface nativa complexa na qual deseja incorporar gráficos 3D não triviais.

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


All Articles