В этой статье речь пойдет об оптимизации Unity-сцены проекта Plantsim 1.0.: о визуальной части цифровой копии предприятия Tennessee Eastman Process, реализованного на Unity 2017.1.1f1.  Заметка от партнера IT-центра МАИ и организатора магистерской программы “VR/AR & AI” — компании PHYGITALISM. В первой версии работа осуществлялась с использованием мощного компьютера, а так же стороннего вычислительного устройства — PLC. Отличие новой версии заключается в том, что теперь это WEB-приложение, реализованное с помощью WebGL, и для его работы потребуется только ноутбук. Таблица сравнения разработки проектов В сравнении с приложением PlantSim 1.0, заметно снижение требований к характеристикам компьютера. Поэтому оптимизация стала главной задачей для PlantSim 2.0. 
Пайплайн разработки PlantSim 2.0 WebGL — это работа 3D графики с использованием возможностей браузеров Google Chrome, Mozilla, Safari. Первостепенными задачами для нас являлись оптимизация, сохранение того же уровня реализма и поддержание работоспособности приложения на 30FPS+. Для этого нам предстояло работать в пайплайне мобильной разработки. 
Unity: WebGL Build Пример проекта с изображении выше можно посмотреть здесь и самому ощутить качество графики и скорость отрисовки. Красивой визуализации в WebGL добиться можно, но сложно. Имеется ряд особенностей, о которых желательно знать, если вы собираетесь сделать подобный проект самостоятельно и/или работаете с 3D графикой впервые. Перед тем, как начать непосредственно работу над 3D, мы выстроили четкий план со списком моделей, которые необходимо оптимизировать. 
Таблица с планом оптимизации для отслеживания хода работы Вот краткий список моделей, что присутствовали в сцене раньше: Оценивая старые модели, я заметил, что некоторые объекты были разделены и представляли собой уникальный меш, с собственными материалами, а иногда даже несколькими. Например: 
Модель Reactor (PlantSim 1.0) — анализ иерархии и сетки Сам Reactor имел 21998 треугольников. По плану нам было необходимо избавиться от всего лишнего и малозначительного, и получить 8800 треугольников. Перед тем, как начать оптимизировать, мы проанализировали объекты вновь и заметили новую важную деталь: модели OutPutTank, Reactor и Striper имели одни и те же элементы — основание и лестницу. Это означало, что эти элементы можно дублировать и использовать для них один материал. В итоге, после работы над Reactor объектом (это первый объект, с которого началась оптимизация), мы получили новую модель в 7258 треугольников. Напомню, что по плану было 8000. При условии, что теперь UV карта одна, объект представляет собой объединенный меш и один материал — стало ясно, что задачу мы сможем реализовать, а итоговая сумма треугольников окажется намного меньше, чем планировали изначально. 
Модель Reactor (PlantSim 2.0) —оптимизированная иерархия и сетка Объект ReactorExplosion имел сложную анимацию взрыва, красивую и эффектную — разлет маленьких кусков реактора с сохранением только его основания. После первой полной сборки сцены наш FPS сильно проседал непосредственно на моменте взрыва, который помимо этой анимации меша так же имел и анимацию, состоящую из 1000 частиц инструмента Particle System. Появился вопрос, что создавало трудности для отрисовки: анимация или частицы? Мы обсудили задачу с Unity разработчиком и выявили, что оба фактора слишком тяжелые для WebGL движка, поэтому было решено оптимизировать эффект взрыва, используя 5 частиц (это возможно благодаря FlipBook текстурам). Со стороны разработки так же была необходима оптимизация анимации внутри Unity. FlipBook — это покадровая анимация, вложенная в одну текстуру. Соответственно, чем больше кадров, тем больше разрешение самой текстуры и ее вес. Этот способ отрисовки анимации наиболее оптимальный, так как вместо огромного количества частиц и сложных симуляций мы обходимся одной или несколькими частицами, в которых за один цикл проигрывается наш взрыв. Таким образом, используя всего 5 частиц, был оптимизирован эффект с исходными 1000. С анимацией было сложнее, так как каждый осколок Reactor был индивидуальным и имел свою запеченную анимацию — это и тянуло WebGL вниз по FPS. Со стороны Unity разработки было предложено запечь всю анимацию в один объект, а метод отрисовки с Mesh renderer переключить на Skinned mesh renderer. Таким образом вместо одновременного перемещения тысячи transform points у нас была цепочка костей и один transform points (Origin самого объекта). Как итог, FPS вырос с 3–5 во время воспроизведения эффекта до 15–20 во время запуска анимации, благо этот фриз сохранялся лишь 0,5 секунды. Кстати, ReactorExplosion объект имел 41220 треугольников, и в угоду экономии времени, которого было мало, было решено не создавать новую анимацию с нуля, а использовать старую, оптимизируя описанным способом. В итоге работа по оптимизации меша закончилась успешно. Выше мы упоминали, что на сцене располагалось 189736 треугольников (если не учитывать ReactorExplosion, то 148516 треугольников). Отнимая 60% всей геометрии, мы хотим получить на выходе 59403 треугольника (после оптимизации). У нас получилось 61064 треугольника, что превысило наши планы по оптимизации, но все равно было намного ниже полигонального бюджета, выставленного в самом начале проекта. С учетом ReactorExplosion было 102284 треугольника, что так же было около границы обозначенного бюджета. Задача выполнена, переходим к текстурированию. 
График расчета суммарного фактического количества полигонов после оптимизации Текстурирование играет важную роль в визуализации моделей. Если вы никогда не слышали о том, из чего состоит модель в ее привычном виде, то давайте вспомним: Благодаря текстурным картам имитируются поверхности на моделях. После анализа списка можно заметить сходства — помимо RGB текстур мы имеем много черно-белых карт. Что же это значит для оптимизации? Дело в том, что отдельно R G и B, а также A каналы и представляют собой черно-белый слой. А значит отдельно в одну текстуру RGBа можно зашифровать сразу четыре текстуры. Как это работает: 
Комбинирование четырех ч/б текстурных карт в одну текстуру Далее эту Combine texture расшифровывают и отдают в отдельные каналы материала в Unity.
Также хотелось бы упомянуть Normal Map. Эта текстура — мощный инструмент для оптимизации модели, с помощью нее возможно избавиться от мелкой детализации модели и впоследствии отобразить при рендеринге на сцене. 
Сравнение двух моделей Compressor На этом примере наглядно видна разница поверхностей старой модели и новой, оптимизированной. Данную текстуру создают двумя способами: Использование одного и того же контента повторно — это экономия места на UV развертках, которая позволяет уделить больше внимания деталям в освободившемся пространстве. В нашем случае это были элементы основания реакторов и лестницы. 
Набор материалов на различных объектах. Один цвет — один материал На примере с помощью цвета отображено, какие материалы используются на том или ином объекте. Можно заметить, что один и тот же материал используются на разных объектах, то есть они имеют одну UV развертку. Это важный момент, так как если объект маленький и незначительный, значит сама UV развертка будет маленькой и менее детальной для объекта.  Объединение подобных объектов с другими позволяет экономить на количестве суммарно используемых текстурных карт. WebGL ограничен в отрисовке графики, а также в реализации функционала. Но если учитывать эти особенности в разработке, то можно найти варианты замены или обхода ограничений. Главное — проведение исследования каждой возникающей проблемы. Давайте поговорим о том, как работает свет в Unity, а именно о типе освещения Global Illumination. Это “честный” способ отображения отражения света от поверхности объекта и создания теней. В Unity тени строятся по двум типам объектов — Dynamic и Static объекты. В случае динамических объектов тени просчитываются вновь и вновь, вместе с каждой анимацией и движением. В случае статичных объектов подразумевается, что они находятся в покое, а тени всегда сохраняют свое положение. Как раз для таких объектов можно запечь тень объекта на пространство в специальный Lightmap текстуры. Учитывая задачу оптимизации и отказ от Realtime просчета теней, это наиболее оптимальный подход.

Представление сцены без Lightmap текстурной карты и после ее создания
В PlantSim 2.0 мы запекали тени от всех объектов, если они не участвовали в анимациях. К финалу разработки мы стали сражаться за количество FPS уже в собранных билдах, отслеживая напрямую в Google Chrome. Сохранялась проблема — почему FPS на ноутбуке c i7 процессором и GTX 1070ti не повышается больше 30, а наоборот, иногда даже проседает. Ведь не может быть так, что всей проделанной работы было недостаточно. Перед тем, как запускать проект на референсном ноутбуке заказчика, мы проводили тесты на различных устройствах (ноутбуки, персональные компьютеры), и анализировали FPS, которое показывало приложение. 
Таблица стресс-тестов на различных устройствах. Скриншоты фиксируют одинаковую ситуацию и количество FPS в данный момент. В тестировании участвовали как мощные ноутбуки на базе Windows и Apple MacBook PRO, так и слабые ноутбуки, на которых не предполагалась работа с 3D графикой. Для чистоты тестирования мы так же использовали стационарный компьютер с RTX 2080ti (кстати, на нем FPS был стабильно 60). FPS оказался разным у всех устройств. Разгадка оказалась куда проще, чем предполагалось изначально. Дело в том, что работу браузера Google Chrome определяет некоторые факторы: Проверить первый пункт достаточно легко, второй — уже сложнее. Мы попробовали дать конкретную задачу для приложения Google Chrome, сделать запуск с параметрами повышенной мощности, однако результатов это не давало. Видеокарты фирмы NVIDIA имеют отдельное приложение по управлению ресурсами видеокарты на устройстве — NVIDIA control panel. В этом приложении мы нашли Google Chrome и вручную выставили работу с помощью дискретной видеокарты, а не интегрированной. 
Окно Nvidia Control Panel — настройки производительности для отдельного приложения В итоге, после очередного тестирования билда на Google Chrome, счетчик FPS победно показывал 70–90 — это означало, что во всех предыдущих тестированиях в отрисовке 3D графики браузера участвовала куда более слабая видеокарта (интегрированная), из-за стараний ноутбука сэкономить заряд батареи. Разработка проекта такого рода специфична, но во многом интересна, так как WebGL — это новое направление работы с 3D графикой и представление ее в WEB. Все описанные методы выше не новые, но важные и влияют на скорость загрузки приложения, и насколько будет комфортно пользователю просматривать в браузере.