Опыт хоббита

Оживляем Средиземье с помощью Mobile WebGL

Исторически сложилось так, что внедрение интерактивных веб-технологий с большим количеством мультимедиа на мобильные телефоны и планшеты было непростой задачей. Основными ограничениями были производительность, доступность API, ограничения аудио HTML5 на устройствах и отсутствие плавного встроенного воспроизведения видео.

Ранее в этом году мы с друзьями из Google и Warner Bros. начали проект по созданию мобильной версии нового фильма о Хоббите «Хоббит: Пустошь Смауга» . Создание мобильного Chrome Experiment с насыщенным мультимедиа оказалось действительно вдохновляющей и сложной задачей.

Этот интерфейс оптимизирован для Chrome для Android на новых устройствах Nexus, где теперь есть доступ к WebGL и веб-аудио. Однако большая часть возможностей доступна на устройствах и браузерах, отличных от WebGL, благодаря аппаратному ускорению композиции и CSS-анимации.

Весь опыт основан на карте Средиземья, а также локациях и персонажах из фильмов «Хоббит». Использование WebGL позволило нам драматизировать и исследовать богатый мир трилогии «Хоббит», а также предоставить пользователям возможность контролировать этот процесс.

Проблемы WebGL на мобильных устройствах

Во-первых, термин «мобильные устройства» очень широк. Технические характеристики устройств сильно различаются. Итак, как разработчик, вам нужно решить, хотите ли вы поддерживать больше устройств с менее сложным интерфейсом или, как мы сделали в этом случае, ограничить поддерживаемые устройства теми, которые способны отображать более реалистичный трехмерный мир. В «Путешествии по Средиземью» мы сосредоточились на устройствах Nexus и пяти популярных Android-смартфонах.

В эксперименте мы использовали Three.js , как и в некоторых наших предыдущих проектах WebGL. Мы начали реализацию с создания начальной версии игры Trollshaw , которая хорошо работала бы на планшете Nexus 10. После некоторого первоначального тестирования устройства у нас был список оптимизаций, который очень похож на то, что мы обычно используем для ноутбука с низкими характеристиками:

  • Используйте низкополигональные модели.
  • Используйте текстуры низкого разрешения.
  • Максимально сократите количество вызовов отрисовки за счет объединения геометрии.
  • Упростите материалы и освещение
  • Удалить пост-эффекты и отключить сглаживание
  • Оптимизация производительности Javascript
  • Отобразите холст WebGL в половину размера и увеличьте его с помощью CSS.

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

Используйте низкополигональные модели.

Начнем с моделей. Использование низкополигональных моделей, безусловно, помогает сократить время загрузки, а также время, необходимое для инициализации сцены. Мы обнаружили, что можем значительно увеличить сложность, не сильно влияя на производительность. Модели троллей, которые мы используем в этой игре, имеют около 5 тысяч лиц, а сцена — около 40 тысяч лиц, и это прекрасно работает.

Один из троллей леса Троллшоу.
Один из троллей леса Троллшоу.

В другом (еще не опубликованном) месте мы увидели большее влияние на производительность от уменьшения количества полигонов. В этом случае мы загружали объекты с меньшим количеством полигонов для мобильных устройств, чем объекты, которые мы загружали для настольных компьютеров. Создание различных наборов 3D-моделей требует дополнительной работы и не всегда требуется. Это действительно зависит от того, насколько сложны ваши модели.

Работая над большими сценами с большим количеством объектов, мы старались стратегически подходить к разделению геометрии. Это позволило нам быстро включать и выключать менее важные сетки, чтобы найти настройку, которая работала бы для всех мобильных устройств. Затем мы могли бы объединить геометрию в JavaScript во время выполнения для динамической оптимизации или объединить ее в предварительной версии для сохранения запросов.

Используйте текстуры низкого разрешения.

Чтобы сократить время загрузки на мобильных устройствах, мы решили загружать различные текстуры, размер которых вдвое меньше текстур на настольных компьютерах. Оказывается, все устройства могут обрабатывать текстуры размером до 2048x2048 пикселей, а большинство — 4096x4096 пикселей. Поиск отдельных текстур не представляет проблемы после их загрузки в графический процессор. Общий размер текстур должен умещаться в памяти графического процессора, чтобы избежать постоянной загрузки и загрузки текстур, но это, вероятно, не является большой проблемой для большинства веб-приложений. Однако объединение текстур в как можно меньшее количество спрайтов важно для уменьшения количества вызовов отрисовки — это оказывает большое влияние на производительность на мобильных устройствах.

Текстура для одного из троллей леса Троллшоу.
Текстура для одного из троллей леса Троллшоу.
(исходный размер 512x512 пикселей)

Упростите материалы и освещение

Выбор материалов также может сильно повлиять на производительность, и к нему необходимо разумно подходить на мобильных устройствах. Использование MeshLambertMaterial (расчет освещения по вершинам) в Three.js вместо MeshPhongMaterial (расчет освещения по текселям) — это одна из вещей, которую мы использовали для оптимизации производительности. По сути, мы старались использовать как можно более простые шейдеры с минимальным расчетом освещения.

Чтобы увидеть, как используемые вами материалы влияют на производительность сцены, вы можете переопределить материалы сцены с помощью MeshBasicMaterial . Это даст вам хорошее сравнение.

scene.overrideMaterial = new THREE.MeshBasicMaterial({color:0x333333, wireframe:true});

Оптимизация производительности JavaScript

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

Обновление предварительно выделенных объектов в циклах вместо создания новых объектов — важный шаг, позволяющий избежать «сбоев» сборки мусора во время игры.

Например, рассмотрим такой код:

var currentPos = new THREE.Vector3();

function gameLoop() {
  currentPos = new THREE.Vector3(0+offsetX,100,0);
}

Улучшенная версия этого цикла позволяет избежать создания новых объектов, которые необходимо собирать мусором:

var originPos = new THREE.Vector3(0,100,0);
var currentPos = new THREE.Vector3();
function gameLoop() {
  currentPos.copy(originPos).x += offsetX;
  //or
  currentPos.set(originPos.x+offsetX,originPos.y,originPos.z);
}

Насколько это возможно, обработчики событий должны только обновлять свойства и позволять циклу рендеринга requestAnimationFrame обрабатывать обновление сцены.

Еще один совет — оптимизировать и/или предварительно рассчитать операции преобразования лучей. Например, если вам нужно прикрепить объект к сетке во время статического движения по траектории, вы можете «записать» позиции в течение одного цикла, а затем прочитать эти данные вместо проведения лучевого приведения к сетке. Или, как мы это делаем в опыте Ривенделла , raycast для поиска взаимодействия мыши с более простой низкополигональной невидимой сеткой. Поиск коллизий на высокополигональной сетке очень медленный, и его вообще следует избегать в игровом цикле.

Отобразите холст WebGL в половину размера и увеличьте его с помощью CSS.

Размер холста WebGL, вероятно, является единственным наиболее эффективным параметром, который вы можете настроить для оптимизации производительности. Чем больше холст, который вы используете для рисования 3D-сцены, тем больше пикселей необходимо отрисовать в каждом кадре. Это, конечно, влияет на производительность. Nexus 10 с дисплеем высокой плотности 2560x1600 пикселей должен отображать в 4 раза больше пикселей, чем планшет с низкой плотностью пикселей. Чтобы оптимизировать это для мобильных устройств, мы используем трюк , при котором мы устанавливаем холст на половину размера (50%), а затем масштабируем его до предполагаемого размера (100%) с помощью 3D-преобразований CSS с аппаратным ускорением. Обратной стороной этого является пиксельное изображение, где тонкие линии могут стать проблемой, но на экране с высоким разрешением эффект не так уж и плох. Это абсолютно стоит дополнительной производительности.

Та же сцена без масштабирования холста на Nexus 10 (16 кадров в секунду) и масштабе до 50 % (33 кадра в секунду).
Та же сцена без масштабирования холста на Nexus 10 (16 кадров в секунду) и масштабирована до 50 % (33 кадра в секунду).

Объекты как строительные блоки

Чтобы создать большой лабиринт замка Дол Гулдур и бесконечную долину Ривенделл, мы создали набор 3D-моделей строительных блоков, которые повторно используем. Повторное использование объектов позволяет нам гарантировать, что объекты создаются и загружаются в начале опыта, а не в его середине.

Строительные блоки 3D-объектов, используемые в лабиринте Дол Гулдура.
Строительные блоки 3D-объектов, используемые в лабиринте Дол Гулдура.

В Ривенделле у нас есть несколько участков земли, которые мы постоянно перемещаем по оси Z по мере продвижения пользователя. Когда пользователь проходит разделы, они перемещаются на дальнее расстояние.

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

Объединение всей структуры в одну большую сетку с самого начала приводит к очень большой сцене и плохой производительности. Чтобы решить эту проблему, мы решили скрывать и показывать строительные блоки в зависимости от того, находятся ли они в поле зрения. С самого начала у нас была идея использовать 2D-скрипт raycaster, но в итоге мы применили встроенный в Three.js frustrum culling . Мы повторно использовали сценарий raycaster, чтобы приблизить «опасность», с которой сталкивается игрок.

Следующая важная вещь, которую нужно решить, — это взаимодействие с пользователем. На рабочем столе у ​​вас есть ввод с помощью мыши и клавиатуры; на мобильных устройствах ваши пользователи взаимодействуют с помощью касаний, пролистывания, разведения пальцев, ориентации устройства и т. д.

Использование сенсорного взаимодействия в мобильных веб-интерфейсах

Добавить поддержку сенсорного ввода несложно. На эту тему можно прочитать отличные статьи . Но есть некоторые мелочи, которые могут усложнить задачу.

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

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

Помните, что это мультитач: event.touches — это массив всех касаний. В некоторых случаях интереснее вместо этого посмотреть на event.targetTouches или event.changedTouches и просто реагировать на интересующие вас касания. Чтобы отделить касания от пролистывания, мы используем задержку, прежде чем мы проверим, переместилось ли касание (проведите пальцем по экрану) или это все еще (нажмите). Чтобы получить щепотку, мы измеряем расстояние между двумя первыми прикосновениями и то, как оно меняется со временем.

В трехмерном мире вам нужно решить, как ваша камера реагирует на действия мыши, а не на смахивание. Один из распространенных способов добавить движение камеры — следовать за движением мыши. Это можно сделать либо прямым управлением с помощью положения мыши, либо дельта-перемещением (изменением положения). Вам не всегда нужно такое же поведение на мобильном устройстве, как и в настольном браузере. Мы провели тщательное тестирование, чтобы решить, что подходит для каждой версии.

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

Краткое содержание

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

Ориентация на мобильные устройства также означает, что вам нужно привыкнуть думать о сенсорном взаимодействии, и что речь идет не только о размере пикселей, но и о физическом размере экрана. В некоторых случаях нам приходилось приближать 3D-камеру, чтобы увидеть, что происходит.

Эксперимент запущен, и это было фантастическое путешествие. Надеюсь, тебе понравится!

Хотите попробовать? Совершите собственное путешествие в Средиземье .