Так часто бывает, что техники оптимизации, хорошо работающие для обычной десктопной или мобильной графики, не всегда дают нужный эффект в случае WebGL. В этой статье я собрал (а точнее перевёл на русский язык и изложил в текстовом виде нашу презентацию с Verge3Day) те методы повышения производительности, которые хорошо себя зарекомендовали при создании интерактивных веб-приложений.
Правильная геометрия — это ключ к производительности любого трехмерного приложения. Чтобы получить ровный шейдинг и быстрый рендеринг, вы должны держать полигональную сетку как можно более равномерной. В самом начале работы вы должны определиться с уровнем детализации вашей сцены и придерживаться его при моделировании.
При моделировании складок лучше использовать шейдинг-группы, а не добавлять больше полигонов.
При работе с цилиндрической моделью постарайтесь уменьшить количество полигонов ближе к центру.
Не перегружайте модель дополнительными деталями, которые пользователь все равно не увидит. Как показано на рисунке ниже, край, выделенный оранжевым цветом, определяет уровень детализации для всей модели, поэтому вы можете использовать его в качестве ориентира.
Распространенным способом оптимизации производительности WebGL является уменьшение количества полигонов путем создания карт нормалей.
Однако карты нормалей могут создавать видимые артефакты из-за ограниченной точности 8-битного изображения. Возможные решения этой проблемы показаны на картинке ниже, но их довольно сложно осуществить: использование изображения с более высокой точностью приведет к увеличению размера загружаемого файла, в то время как второй подход довольно трудоемкий и не гарантирует чистого результата. Третий подход иногда срабатывает в случае с шероховатыми поверхностями. В этом случае мы рекомендуем добавлять шум к вашим материалам, чтобы уменьшить возможные артефакты.
Исходя из нашего опыта, мы сделали вывод, что лучшим решением для глянцевых объектов является использование геометрии средней сложности с гладкими вертексными группами, причём без использования какой-либо карты нормалей.
Наконец, вот несколько случаев, когда вы можете использовать карту нормалей, а не очень подробный меш:
Вот типичный набор текстур, используемых в современной PBR модели освещения (и не только).
Как видите, большинство из них черно-белые. Поэтому вы можете комбинировать ч/б текстуры в RGBA-каналах одного изображения (всего до 4-х карт на одно изображение).
Если у вас есть только одна ч/б текстура, вы можете объединить ее с любой существующей текстурой RGB, упаковав ее в альфа-канал. Наконец, если у вас нет изображения для объединения, вы можете преобразовать ваше черно-белое изображение в формат jpeg со сжатием 95% и включенным режимом оттенков серого.
Еще один способ уменьшить размер текстуры — оптимизировать UV-развёртку. Чем компактнее ваша развертка, тем эффективнее ваше изображение будет использовать пространство текстуры. Это позволяет вам иметь более легковесные изображения без потери качества.
Использование вершинных цветов вместо изображений — это эффективный способ ускорить загрузку и повысить общую производительность ваших WebGL-приложений. Единственный недостаток — вам придётся добавить несколько дополнительных ребер в вашу модель, чтобы разделить цвета одних и тех же вершин.
Вы также можете использовать цвета вершин для определения шероховатости, металличности или зеркальных поверхностей или любых других параметров. Ниже вы можете увидеть пример такого материала, в котором используются только цвета вершин.
Очень важно, чтобы на вашей сцене было меньше разных материалов / шейдеров. Компиляция шейдеров в WebGL приводит к длительной загрузке, что особенно заметно в Windows. Кроме того, если у вас меньше шейдеров, движок будет тратить меньше времени на переключение между ними во время рендеринга, тем самым улучшая производительность.
Если у вас есть похожие материалы, которые отличаются только текстурами, вы можете использовать только один материал и загружать / менять его текстуры во время выполнения. Для этого вы можете использовать JavaScript или взять визуальный редактор логики, имеющийся в некоторых WebGL-фреймворках. Это не только оптимизирует количество шейдеров, но и уменьшит количество изображений, загружаемых при запуске приложения.
Вот пример такой оптимизации. Все четыре разновидности одной шины представлены одним материалом, и настраиваются путем замены текстур.
Чтобы уменьшить количество шейдеров, вы можете объединить 2 или более простых материала в один более сложный. Этот метод особенно эффективен, если вы планируете переключаться между этими материалами (например, создаете приложение-конфигуратор), и не просто переключаться, а плавно и красиво анимировать переход от одного материала к другому.
Кроме того, есть еще один важный аспект — количество Draw-вызовов (они же draw calls и вызовы отрисовки). Это примерно соответствует количеству отдельных объектов, если для каждого объекта назначен только один материал, в то время как объектам с несколькими материалами требуется еще больше вызовов для их визуализации.
Поэтому вы должны стремиться объединять сетки, когда это возможно, и использовать меньше уникальных материалов, чтобы уменьшить количество вызовов отрисовки и повысить производительность.
Если у вас есть анимированный объект, вы все равно можете соединить его части и использовать кости для анимации, что иногда даже удобнее при анимации отдельных объектов.
Это помогает значительно улучшить производительность, если вы освещаете свою сцену только с помощью изображения HDR, без использования каких-либо источников света. Файл HDR может весить менее 1 Мб.
Используйте динамические тени только тогда, когда они помогают представить ваш объект в лучшем виде. На рисунке ниже показаны динамические тени из нашей демки Industrial Robot. Поскольку в этом приложении вращается сама модель, а не камера, тени не могут скрыть какую-либо часть объекта от пользователя и отлично подчёркивают форму робота. С другой стороны, такие же тени в демке Scooter будут затенять многие детали модели.
Отсюда мы делаем вывод: если ваш объект не перемещается в приложении, вы можете запечь карты теней и ambient occlusion и назначить их на плоскость, расположенную под объектом. Такое решение будет более производительным и выглядеть качественнее, чем при использовании динамических теней.
На этом всё. Если у вас есть ещё какие-нибудь советы, которые могут помочь с производительностью WebGL, пишите в комментариях.
Геометрия / Меши
Правильная геометрия — это ключ к производительности любого трехмерного приложения. Чтобы получить ровный шейдинг и быстрый рендеринг, вы должны держать полигональную сетку как можно более равномерной. В самом начале работы вы должны определиться с уровнем детализации вашей сцены и придерживаться его при моделировании.
При моделировании складок лучше использовать шейдинг-группы, а не добавлять больше полигонов.
При работе с цилиндрической моделью постарайтесь уменьшить количество полигонов ближе к центру.
Не перегружайте модель дополнительными деталями, которые пользователь все равно не увидит. Как показано на рисунке ниже, край, выделенный оранжевым цветом, определяет уровень детализации для всей модели, поэтому вы можете использовать его в качестве ориентира.
Карты нормалей
Распространенным способом оптимизации производительности WebGL является уменьшение количества полигонов путем создания карт нормалей.
Однако карты нормалей могут создавать видимые артефакты из-за ограниченной точности 8-битного изображения. Возможные решения этой проблемы показаны на картинке ниже, но их довольно сложно осуществить: использование изображения с более высокой точностью приведет к увеличению размера загружаемого файла, в то время как второй подход довольно трудоемкий и не гарантирует чистого результата. Третий подход иногда срабатывает в случае с шероховатыми поверхностями. В этом случае мы рекомендуем добавлять шум к вашим материалам, чтобы уменьшить возможные артефакты.
Исходя из нашего опыта, мы сделали вывод, что лучшим решением для глянцевых объектов является использование геометрии средней сложности с гладкими вертексными группами, причём без использования какой-либо карты нормалей.
Наконец, вот несколько случаев, когда вы можете использовать карту нормалей, а не очень подробный меш:
- Ваш объект состоит из множества различных поверхностей.
- У вас шероховатая поверхность, которая не дает видимых дефектов.
- Ваши объекты такие отдаленные или маленькие, что пользователь не в состоянии заметить какие-либо артефакты.
Текстурирование
Вот типичный набор текстур, используемых в современной PBR модели освещения (и не только).
Как видите, большинство из них черно-белые. Поэтому вы можете комбинировать ч/б текстуры в RGBA-каналах одного изображения (всего до 4-х карт на одно изображение).
Если у вас есть только одна ч/б текстура, вы можете объединить ее с любой существующей текстурой RGB, упаковав ее в альфа-канал. Наконец, если у вас нет изображения для объединения, вы можете преобразовать ваше черно-белое изображение в формат jpeg со сжатием 95% и включенным режимом оттенков серого.
Еще один способ уменьшить размер текстуры — оптимизировать UV-развёртку. Чем компактнее ваша развертка, тем эффективнее ваше изображение будет использовать пространство текстуры. Это позволяет вам иметь более легковесные изображения без потери качества.
Вертексные цвета
Использование вершинных цветов вместо изображений — это эффективный способ ускорить загрузку и повысить общую производительность ваших WebGL-приложений. Единственный недостаток — вам придётся добавить несколько дополнительных ребер в вашу модель, чтобы разделить цвета одних и тех же вершин.
Вы также можете использовать цвета вершин для определения шероховатости, металличности или зеркальных поверхностей или любых других параметров. Ниже вы можете увидеть пример такого материала, в котором используются только цвета вершин.
Количество шейдеров
Очень важно, чтобы на вашей сцене было меньше разных материалов / шейдеров. Компиляция шейдеров в WebGL приводит к длительной загрузке, что особенно заметно в Windows. Кроме того, если у вас меньше шейдеров, движок будет тратить меньше времени на переключение между ними во время рендеринга, тем самым улучшая производительность.
Если у вас есть похожие материалы, которые отличаются только текстурами, вы можете использовать только один материал и загружать / менять его текстуры во время выполнения. Для этого вы можете использовать JavaScript или взять визуальный редактор логики, имеющийся в некоторых WebGL-фреймворках. Это не только оптимизирует количество шейдеров, но и уменьшит количество изображений, загружаемых при запуске приложения.
Вот пример такой оптимизации. Все четыре разновидности одной шины представлены одним материалом, и настраиваются путем замены текстур.
Чтобы уменьшить количество шейдеров, вы можете объединить 2 или более простых материала в один более сложный. Этот метод особенно эффективен, если вы планируете переключаться между этими материалами (например, создаете приложение-конфигуратор), и не просто переключаться, а плавно и красиво анимировать переход от одного материала к другому.
Draw-вызовы
Кроме того, есть еще один важный аспект — количество Draw-вызовов (они же draw calls и вызовы отрисовки). Это примерно соответствует количеству отдельных объектов, если для каждого объекта назначен только один материал, в то время как объектам с несколькими материалами требуется еще больше вызовов для их визуализации.
Поэтому вы должны стремиться объединять сетки, когда это возможно, и использовать меньше уникальных материалов, чтобы уменьшить количество вызовов отрисовки и повысить производительность.
Если у вас есть анимированный объект, вы все равно можете соединить его части и использовать кости для анимации, что иногда даже удобнее при анимации отдельных объектов.
HDR освещение
Это помогает значительно улучшить производительность, если вы освещаете свою сцену только с помощью изображения HDR, без использования каких-либо источников света. Файл HDR может весить менее 1 Мб.
Тени
Используйте динамические тени только тогда, когда они помогают представить ваш объект в лучшем виде. На рисунке ниже показаны динамические тени из нашей демки Industrial Robot. Поскольку в этом приложении вращается сама модель, а не камера, тени не могут скрыть какую-либо часть объекта от пользователя и отлично подчёркивают форму робота. С другой стороны, такие же тени в демке Scooter будут затенять многие детали модели.
Отсюда мы делаем вывод: если ваш объект не перемещается в приложении, вы можете запечь карты теней и ambient occlusion и назначить их на плоскость, расположенную под объектом. Такое решение будет более производительным и выглядеть качественнее, чем при использовании динамических теней.
На этом всё. Если у вас есть ещё какие-нибудь советы, которые могут помочь с производительностью WebGL, пишите в комментариях.