Использование Unity3D в нативном iOS/Android приложении для моделирования освещения открытых пространств

image

Unity3D известнейшая платформа для разработки 3D и 2D игр, завоевавшая популярность во всем мире. В то же время ее возможности не ограничены разработкой только игровых приложений, а подходят для применения в любых других областях, требующих создания кроссплатформенных приложений для работы с графикой. В этой статье мы расскажем об опыте использования Unity3D для разработки системы расчета освещения открытых пространств.
Заказчик нашего проекта — производитель осветительного оборудования международная корпорация БЛ ГРУПП. В целях расширения привлекательности своей продукции и упрощения взаимодействия с клиентами ему потребовалось разработать приложение, позволяющее визуально смоделировать расположение осветительных приборов, а также провести расчет освещенности поверхности и вывести необходимую техническую информацию в отчете. Предполагалось, что приложение запускается на iPad или Android планшете потенциальным клиентом или торговым представителем и позволяет клиенту сразу получить представление о возможности осветительных установок.

В общем виде, приложение представляет собой редактор, позволяющий добавлять и редактировать элементы освещения, дорог, декоративных элементов, произвести светотехнический расчет сцены, вывести отчет в pdf. Каждый элемент имеет свой набор параметров для редактирования и подтипы, влияющие на его отображение и расчет.

  • Имеются несколько видов мачт освещения, с различными типам крепления светильников, углами наклона светильников и длиной выноса. Для определенных типов светильников возможна индивидуальная настройка с указанием направления освещения.
    image
     
  • Дороги могут быть линейными участками, элементами дуги, площадью, кольцом. Для каждого элемента может быть настроены размеры, положение, тип разметки, слой.
    image
  • Декоративные элементы — машины, деревья, кустарники, дорожные знаки


Все элементы сцены можно вращать и перемещать. Также поддерживаются стандартные действия вернуть назад или повторить операцию. Общие настройки проекта позволяют задать текстуру дорг, поверхности земли, отображение дополнительных параметров. Сцена отображается в 2D/3D режимах. А при расчете освещения на поверхности отображается карта освещенности поверхности в фиктивных цветах.

image

Весь UI по возможности требовалось делать нативными средствами iOS/Android.
Основное техническое требование к приложению — это уметь рассчитать освещение сцены согласно технической спецификации светильников. Также требовалась возможность для каждого светильника отображать и просматривать его диаграмму направленности (кривые сил света) в 3D/2D режимах.

Выбор платформы


Для реализации проекта нами был выбран Unity как более удобный нам для реализации требуемого функционала. Вообще в нашей компании имелся опыт работы и с другими 3D движками и платформами (OpenSceneGraph, Ogre3D, LibGdx) и технически они все могут справится с требуемой задачей, но в этот раз выбор пал на Unity, который позволяет удобнее управлять сценой в процессе разработки и отлаживать в процессе работы.

Основные сложности


Мы не будем вдаваться в тонкости разработки всего приложения, поскольку технически функционал по отображению и редактированию сцены вполне стандартный. Естественно имелись сложности с механизмами специфического редактирования объектов, добавления и их удаления, а также сохранения цепочки команд для возможности повторения и отмены действий.
Нам бы хотелось остановится только на особенностях системы связанных с работой с нативным UI, генерацией pdf отчетов и работой с фотометрией и расчетом освещения.

Работа с нативным UI


В большинстве случаях взаимодействие Unity c нативными функциями системы происходит с использованием системы плагинов, что позволяет встроит нужный функционал в приложение. Однако, в нашем случае ситуация несколько обратная. Нам требовалось иметь полноценное UI, отображаемое поверх окна Unity.

image

К счастью, Unity умеет экспортировать проект, который можно использовать как основу для нативного приложения. Основная сложность в данном случае заключается в том как интегрировать в полученный проект дополнительный UI. Также, не менее важно то, что при сборке проекта Unity его формат и расположение файлов формируются Unity и частично перезаписывается, что ограничивает возможность модификации проекта.

При разработке iOS приложения мы воспользовались механизмом предложенным в статье. Во время разработки использовалась Unity 5.5 и на данный момент указанное в ней может потерять актуальность. При сборке андроид проекта дополнительных проблем не возникало за исключение того, что Unity каждый раз перезаписывает манифест файл и файлы ресурсов.
Дополнительная проблема состоит в том, что Unity может работать только в одном окне. В тоже время, нам требовалось обеспечить работу Unity для отображения всей сцены, а также при открытии окна настроек должна была отображаться 3D модель фотометрического тела светильника. Для осуществления этого пришлось воспользоваться «хаком» и использовать один и тот же объект UIView в различных окнах.

Для передачи сообщений мы воспользовались стандартным функционалом предлагаемым Unity. То есть, все сообщения были в формате json и передавались простыми строками. На производительность это не накладывало никаких ограничений поскольку размер сообщений максимум доходил до 100 символов, а их частота определялась скоростью работы с программой. В тоже время, в более требовательных приложениях имеет смысл сделать собственный обработчик сообщений как представлено на хабре здесь и здесь.

Расчет освещения


Все источники освещения, используемые в приложении поставляются в стандартном IES формате, который описывает распределение света в различном направлениию (спецификация). Этот формат широко используется в профессиональных CAD системах и 3D редакторах. Он представляет собой текстовой файл с указанием интенсивности света в различных направлениях и дополнительную метаинформацию с указанием типа, общей интенсивности источника, осей и плоскостей симметрии. Учитывая симметрию светильников, ies файл может быть очень маленьким. Например, в случае осевой симметрии, достаточно указать раследеление света только в одной плоскости.

Пример простого 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


Для отображения диаграммы направленности света использовались два вида отображения:

  • Кривые сил света (КСС) — двумерный график на котором представлена интенсивность света в одной из главных плоскостей в зависимости от направления. Этот график для удобства может быть представлен как в полярной так и декартовой системе координат
    image
  • Фотометрическое тело — трехмерное изображение интенсивности света в разном направлении
    image


Расчетный модуль


Для расчета освещения у заказчика имелся собственный C++ модуль, используемый в других продуктах компании, а поэтому требовалось интегрировать его в Unity проект. Порядок подключения модуля отличался от используемой платформы.

  • На iOS платформе Unitу умеет непосредственно вызывать С функции, поэтому достаточно скопировать исходники модуля непосредственно в проект и добавить классы для его взаимодействия с Unity. Классы возможно хранить как непосредственно в проекте iOS, так и в папке плагинов, которые автоматически копируются при экспорта проекта в XCode. Пример вызова C++ функций следующий:
    [DllImport("__Internal")]
    public static extern void calculateLight([MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] Light[] lights, int size, ref CalculationResult result);
    
  • На Android платформе С++ модуль должен быть предварительно скомпилирован в отдельную библиотеку. Это можно сделать непосредственно добавлением исходников С++ в проект и настройкой gradle для их сборки в so библиотеки.
  • Также, для отладки и тестирования Unity части разработка велась на windows машине, поэтому потребовалось подключить исходники модуля и в Windows. Это делается аналогично андроид проекту, только в этом случае файлы скачала собираются в dll библиотеку и подключались к проекту.


Отображение карты освещенности


По требованию заказчика результаты расчета освещенности должны быть отображены на поверхности сцены. На поверхности дорог необходимо использовать фиктивные цвета с отображением шкалы соответствия цвета и интенсивности освещения, а на остальной поверхности достаточно просто отображение ее яркости.

image

Как ранее упоминалось весь расчет производился подключаемым модулем C++ в который передавались данные об источниках цвета. Результатом расчета был двумерный массив интенсивности света по всей поверхности сцены с заданной детализацией.

Полученная карта освещенности анализировалась на минимальное, максимальное значение, по которым строилась одномерная текстура градиента (GradientRamp). Используя эту текстуру интенсивность освещения преобразовывалась в фиктивные цвета непосредственно во фрагментном шейдере. При этом для различных поверхностей дорог и поверхности земли использовался один и тот же шейдер, а переключение режима освещения обеспечивалось с использованием «multi-compile» шейдера.

Генерация Pdf файла


В соответствии с техническими требованиями для пользователя должен был генерироваться отчет, содержащий информацию об общей сцене (размеры, ее изображения, параметры освещения) и информацию о каждом использованном типе светильников с указанием их положения, направления характеристик, а также диаграммами КСС и отображением фотометрического тела.
Поскольку отчет должен был отображаться как на iOS так и Android, то его генерацию необходимо было производить непосредственно в Unity модуле, а потом уже отображать стандартными нативными средствами.

image


Для построения pdf была выбрана библиотека iTextSharp, отвечающая нашим требованиям. Создание отчета в ней не представляет особой сложности и заключается в создание блоков текста, таблиц, изображений непосредственно из кода. Однако, в ходе разработки мы столкнулись с множеством нюансов, решение которых иногда требовало значительных усилий. Основной проблемой из которых был запуск генерации отчета в фоновом потоке.

Если при тестировании на десктопной машине генерация pdf составляла порядка нескольких секунд, то при тестировании на iPad mini 3 это время легко достигло 1–3 минут. Естественно, создание отчета потребовалось перенести в отдельный поток, для избегания проблем с подвисанием интерфейса. В общем случае это не является проблемой, но это не так при использовании Unity, в котором явно запрещено использовать Unity API не из главного потока. В тоже время, для отчета нам требовалось, как минимум, рендерить КСС и изображение сцены, что необходимо делать только из главного потока.

Таким образом, для построения отчета нам необходимо запускать задачи в определенной последовательности и при этом часть из них может работать в фоновом потоке, а часть обязательно должна быть запущена в главном.

На первый взгляд для решения этой проблемы можно попробовать использовать стандартный механизм и запускать каждую операцию в отдельной корутине. Однако, это не избавляет нас от проблемы с торможением интерфейса. Как известно, корутины работают в основном потоке и не подходят для медленных операций. В тоже время, при генерации отчета многие операции требуют существенного времени, а поэтому корутины не могут помочь в решении нашей проблемы.

UniRx


Другое решение это разделить код на часть, которой необходимо работать в основном потоке и часть, которая может быть запущена в отдельном потоке. В таком случае, например, изображения можно построить с использованием механизма корутин, а далее встроить их в отчет уже в отдельном потоке. Однако, в этом случае потребуется где-то сохранять промежуточные результаты, что накладывает дополнительные ограничения на объем используемой памяти или свободного места на устройстве.

В нашем приложении мы предпочли пойти прямым путем и запускать задачи последовательно то в главном, то в фоновом потоках. Проблема была только в том, как именно организовать такой запуск задач, чтобы не погрязнуть в этой мешанине и корректно синхронизировать операции.
Существенную помощь в решении этой проблемы принесло использование Rx его воплощение в виде бесплатного ассета UniRx, о котором на хабре уже подробно рассказывалось здесь и здесь.

Его использование позволило значительно упростить взаимодействие между потоками и в примере ниже показано можно запустить несколько методов в строгой последовательности, но в разных потоках

Пример кода
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);


В этом примере последовательно будет запущен метод doHardWork () в фоновом потоке. После его завершения запустится renderImage () в основном потоке, а после выполнится doHardWork2() опять в фоновом потоке.


Также стоит отметить, что в ходе анализа генерации отчета на быстродействие было обнаружено, что наиболее медленная часть это внедрение изображений в отчет. Поиск в интернете показал, что мы не единственные кто сталкиваются с этой проблемой, но подходящего нам решения не нашлось. Нам пришлось несколько снизить качество изображений до приемлемого уровня, что дало прирост в скорости на 20–40%.

Таким образом, в созданном нами приложении получилось удачно внедрить графический движок Unity в нативное приложение iOS/Android. Это отличается от традиционного подхода когда Unity является главной частью приложения и обращается к специфичным свойствам системы через систему плагинов. В тоже время наш подход может быть полезен при необходимости разработки сложного нативного интерфейса, в который требуется встроить нетривиальную 3D графику.

© Habrahabr.ru