Так часто бывает, что техники оптимизации, хорошо работающие для обычной десктопной или мобильной графики, не всегда дают нужный эффект в случае WebGL. В этой статье я собрал (а точнее перевёл на русский язык и изложил в текстовом виде нашу презентацию с Verge3Day) те методы повышения производительности, которые хорошо себя зарекомендовали при создании интерактивных веб-приложений.
![](https://habrastorage.org/webt/f3/-h/po/f3-hpomrfalqm_dx6bd7wucnbgu.png)
Правильная геометрия — это ключ к производительности любого трехмерного приложения. Чтобы получить ровный шейдинг и быстрый рендеринг, вы должны держать полигональную сетку как можно более равномерной. В самом начале работы вы должны определиться с уровнем детализации вашей сцены и придерживаться его при моделировании.
![](https://habrastorage.org/webt/5p/zq/uj/5pzqujnn_wdjuabotnmqswesfi8.jpeg)
При моделировании складок лучше использовать шейдинг-группы, а не добавлять больше полигонов.
![](https://habrastorage.org/webt/x3/eh/7u/x3eh7ulhzw5kwlfz5ii6yys2jng.jpeg)
При работе с цилиндрической моделью постарайтесь уменьшить количество полигонов ближе к центру.
![](https://habrastorage.org/webt/an/4e/n8/an4en8ebhrlarhxwvxfco8-vx88.jpeg)
Не перегружайте модель дополнительными деталями, которые пользователь все равно не увидит. Как показано на рисунке ниже, край, выделенный оранжевым цветом, определяет уровень детализации для всей модели, поэтому вы можете использовать его в качестве ориентира.
![](https://habrastorage.org/webt/d9/-p/yc/d9-pycf3wujunw3fuuryyotmfsa.jpeg)
Распространенным способом оптимизации производительности WebGL является уменьшение количества полигонов путем создания карт нормалей.
![](https://habrastorage.org/webt/cm/ll/ym/cmllymx9s4rem9ynwyvywunmqxy.jpeg)
Однако карты нормалей могут создавать видимые артефакты из-за ограниченной точности 8-битного изображения. Возможные решения этой проблемы показаны на картинке ниже, но их довольно сложно осуществить: использование изображения с более высокой точностью приведет к увеличению размера загружаемого файла, в то время как второй подход довольно трудоемкий и не гарантирует чистого результата. Третий подход иногда срабатывает в случае с шероховатыми поверхностями. В этом случае мы рекомендуем добавлять шум к вашим материалам, чтобы уменьшить возможные артефакты.
![](https://habrastorage.org/webt/cm/1q/rn/cm1qrnqpt1qs13qi0oavun0l2ec.jpeg)
Исходя из нашего опыта, мы сделали вывод, что лучшим решением для глянцевых объектов является использование геометрии средней сложности с гладкими вертексными группами, причём без использования какой-либо карты нормалей.
![](https://habrastorage.org/webt/nd/bv/uw/ndbvuwvn3ao61dafaphqah7nhs0.jpeg)
Наконец, вот несколько случаев, когда вы можете использовать карту нормалей, а не очень подробный меш:
![](https://habrastorage.org/webt/b9/-b/1y/b9-b1y8tfbf1yfs-vuss6vfuaky.jpeg)
Вот типичный набор текстур, используемых в современной PBR модели освещения (и не только).
![](https://habrastorage.org/webt/hu/ip/07/huip07zd2h1-gjr3pioqzmcnwxw.jpeg)
Как видите, большинство из них черно-белые. Поэтому вы можете комбинировать ч/б текстуры в RGBA-каналах одного изображения (всего до 4-х карт на одно изображение).
![](https://habrastorage.org/webt/bv/hw/cu/bvhwcuec0dapzvsygiox7gpei2a.jpeg)
Если у вас есть только одна ч/б текстура, вы можете объединить ее с любой существующей текстурой RGB, упаковав ее в альфа-канал. Наконец, если у вас нет изображения для объединения, вы можете преобразовать ваше черно-белое изображение в формат jpeg со сжатием 95% и включенным режимом оттенков серого.
![](https://habrastorage.org/webt/jz/ws/sy/jzwssyyopq_jswi7ahzny0zwhsa.jpeg)
Еще один способ уменьшить размер текстуры — оптимизировать UV-развёртку. Чем компактнее ваша развертка, тем эффективнее ваше изображение будет использовать пространство текстуры. Это позволяет вам иметь более легковесные изображения без потери качества.
![](https://habrastorage.org/webt/yv/_k/dh/yv_kdhifbs63s5xai2om5z37avi.jpeg)
Использование вершинных цветов вместо изображений — это эффективный способ ускорить загрузку и повысить общую производительность ваших WebGL-приложений. Единственный недостаток — вам придётся добавить несколько дополнительных ребер в вашу модель, чтобы разделить цвета одних и тех же вершин.
![](https://habrastorage.org/webt/4j/6w/_x/4j6w_xkffgfixggeghebye5tl_4.jpeg)
Вы также можете использовать цвета вершин для определения шероховатости, металличности или зеркальных поверхностей или любых других параметров. Ниже вы можете увидеть пример такого материала, в котором используются только цвета вершин.
![](https://habrastorage.org/webt/if/di/-s/ifdi-sbv5i300f7zkpjwie8cfd8.jpeg)
Очень важно, чтобы на вашей сцене было меньше разных материалов / шейдеров. Компиляция шейдеров в WebGL приводит к длительной загрузке, что особенно заметно в Windows. Кроме того, если у вас меньше шейдеров, движок будет тратить меньше времени на переключение между ними во время рендеринга, тем самым улучшая производительность.
Если у вас есть похожие материалы, которые отличаются только текстурами, вы можете использовать только один материал и загружать / менять его текстуры во время выполнения. Для этого вы можете использовать JavaScript или взять визуальный редактор логики, имеющийся в некоторых WebGL-фреймворках. Это не только оптимизирует количество шейдеров, но и уменьшит количество изображений, загружаемых при запуске приложения.
![](https://habrastorage.org/webt/dm/3w/vs/dm3wvs45kjv3deg4w0342os2o7e.jpeg)
Вот пример такой оптимизации. Все четыре разновидности одной шины представлены одним материалом, и настраиваются путем замены текстур.
![](https://habrastorage.org/webt/8l/9d/vg/8l9dvgl_jlessslo7qozr7fnlfe.jpeg)
Чтобы уменьшить количество шейдеров, вы можете объединить 2 или более простых материала в один более сложный. Этот метод особенно эффективен, если вы планируете переключаться между этими материалами (например, создаете приложение-конфигуратор), и не просто переключаться, а плавно и красиво анимировать переход от одного материала к другому.
![](https://habrastorage.org/webt/tg/qj/nk/tgqjnky59qyocifqwtgxtvotsae.jpeg)
Кроме того, есть еще один важный аспект — количество Draw-вызовов (они же draw calls и вызовы отрисовки). Это примерно соответствует количеству отдельных объектов, если для каждого объекта назначен только один материал, в то время как объектам с несколькими материалами требуется еще больше вызовов для их визуализации.
Поэтому вы должны стремиться объединять сетки, когда это возможно, и использовать меньше уникальных материалов, чтобы уменьшить количество вызовов отрисовки и повысить производительность.
![](https://habrastorage.org/webt/2s/62/if/2s62ifvsueoudvxkdaa6o4ilwse.jpeg)
Если у вас есть анимированный объект, вы все равно можете соединить его части и использовать кости для анимации, что иногда даже удобнее при анимации отдельных объектов.
![](https://habrastorage.org/webt/rm/sj/wi/rmsjwiqlei4hbedtbuafmxbrwsk.jpeg)
Это помогает значительно улучшить производительность, если вы освещаете свою сцену только с помощью изображения HDR, без использования каких-либо источников света. Файл HDR может весить менее 1 Мб.
![](https://habrastorage.org/webt/pm/j_/rl/pmj_rli9pkrhkwg4jgh0aggk5mu.jpeg)
Используйте динамические тени только тогда, когда они помогают представить ваш объект в лучшем виде. На рисунке ниже показаны динамические тени из нашей демки Industrial Robot. Поскольку в этом приложении вращается сама модель, а не камера, тени не могут скрыть какую-либо часть объекта от пользователя и отлично подчёркивают форму робота. С другой стороны, такие же тени в демке Scooter будут затенять многие детали модели.
![](https://habrastorage.org/webt/o4/px/ik/o4pxikw8wlgmidid3mlswai50bq.jpeg)
Отсюда мы делаем вывод: если ваш объект не перемещается в приложении, вы можете запечь карты теней и ambient occlusion и назначить их на плоскость, расположенную под объектом. Такое решение будет более производительным и выглядеть качественнее, чем при использовании динамических теней.
![](https://habrastorage.org/webt/a4/5d/j1/a45dj1fnhbxpi4u8tnrpmqaptai.jpeg)
На этом всё. Если у вас есть ещё какие-нибудь советы, которые могут помочь с производительностью WebGL, пишите в комментариях.
![](https://habrastorage.org/webt/f3/-h/po/f3-hpomrfalqm_dx6bd7wucnbgu.png)
Геометрия / Меши
Правильная геометрия — это ключ к производительности любого трехмерного приложения. Чтобы получить ровный шейдинг и быстрый рендеринг, вы должны держать полигональную сетку как можно более равномерной. В самом начале работы вы должны определиться с уровнем детализации вашей сцены и придерживаться его при моделировании.
![](https://habrastorage.org/webt/5p/zq/uj/5pzqujnn_wdjuabotnmqswesfi8.jpeg)
При моделировании складок лучше использовать шейдинг-группы, а не добавлять больше полигонов.
![](https://habrastorage.org/webt/x3/eh/7u/x3eh7ulhzw5kwlfz5ii6yys2jng.jpeg)
При работе с цилиндрической моделью постарайтесь уменьшить количество полигонов ближе к центру.
![](https://habrastorage.org/webt/an/4e/n8/an4en8ebhrlarhxwvxfco8-vx88.jpeg)
Не перегружайте модель дополнительными деталями, которые пользователь все равно не увидит. Как показано на рисунке ниже, край, выделенный оранжевым цветом, определяет уровень детализации для всей модели, поэтому вы можете использовать его в качестве ориентира.
![](https://habrastorage.org/webt/d9/-p/yc/d9-pycf3wujunw3fuuryyotmfsa.jpeg)
Карты нормалей
Распространенным способом оптимизации производительности WebGL является уменьшение количества полигонов путем создания карт нормалей.
![](https://habrastorage.org/webt/cm/ll/ym/cmllymx9s4rem9ynwyvywunmqxy.jpeg)
Однако карты нормалей могут создавать видимые артефакты из-за ограниченной точности 8-битного изображения. Возможные решения этой проблемы показаны на картинке ниже, но их довольно сложно осуществить: использование изображения с более высокой точностью приведет к увеличению размера загружаемого файла, в то время как второй подход довольно трудоемкий и не гарантирует чистого результата. Третий подход иногда срабатывает в случае с шероховатыми поверхностями. В этом случае мы рекомендуем добавлять шум к вашим материалам, чтобы уменьшить возможные артефакты.
![](https://habrastorage.org/webt/cm/1q/rn/cm1qrnqpt1qs13qi0oavun0l2ec.jpeg)
Исходя из нашего опыта, мы сделали вывод, что лучшим решением для глянцевых объектов является использование геометрии средней сложности с гладкими вертексными группами, причём без использования какой-либо карты нормалей.
![](https://habrastorage.org/webt/nd/bv/uw/ndbvuwvn3ao61dafaphqah7nhs0.jpeg)
Наконец, вот несколько случаев, когда вы можете использовать карту нормалей, а не очень подробный меш:
- Ваш объект состоит из множества различных поверхностей.
- У вас шероховатая поверхность, которая не дает видимых дефектов.
- Ваши объекты такие отдаленные или маленькие, что пользователь не в состоянии заметить какие-либо артефакты.
![](https://habrastorage.org/webt/b9/-b/1y/b9-b1y8tfbf1yfs-vuss6vfuaky.jpeg)
Текстурирование
Вот типичный набор текстур, используемых в современной PBR модели освещения (и не только).
![](https://habrastorage.org/webt/hu/ip/07/huip07zd2h1-gjr3pioqzmcnwxw.jpeg)
Как видите, большинство из них черно-белые. Поэтому вы можете комбинировать ч/б текстуры в RGBA-каналах одного изображения (всего до 4-х карт на одно изображение).
![](https://habrastorage.org/webt/bv/hw/cu/bvhwcuec0dapzvsygiox7gpei2a.jpeg)
Если у вас есть только одна ч/б текстура, вы можете объединить ее с любой существующей текстурой RGB, упаковав ее в альфа-канал. Наконец, если у вас нет изображения для объединения, вы можете преобразовать ваше черно-белое изображение в формат jpeg со сжатием 95% и включенным режимом оттенков серого.
![](https://habrastorage.org/webt/jz/ws/sy/jzwssyyopq_jswi7ahzny0zwhsa.jpeg)
Еще один способ уменьшить размер текстуры — оптимизировать UV-развёртку. Чем компактнее ваша развертка, тем эффективнее ваше изображение будет использовать пространство текстуры. Это позволяет вам иметь более легковесные изображения без потери качества.
![](https://habrastorage.org/webt/yv/_k/dh/yv_kdhifbs63s5xai2om5z37avi.jpeg)
Вертексные цвета
Использование вершинных цветов вместо изображений — это эффективный способ ускорить загрузку и повысить общую производительность ваших WebGL-приложений. Единственный недостаток — вам придётся добавить несколько дополнительных ребер в вашу модель, чтобы разделить цвета одних и тех же вершин.
![](https://habrastorage.org/webt/4j/6w/_x/4j6w_xkffgfixggeghebye5tl_4.jpeg)
Вы также можете использовать цвета вершин для определения шероховатости, металличности или зеркальных поверхностей или любых других параметров. Ниже вы можете увидеть пример такого материала, в котором используются только цвета вершин.
![](https://habrastorage.org/webt/if/di/-s/ifdi-sbv5i300f7zkpjwie8cfd8.jpeg)
Количество шейдеров
Очень важно, чтобы на вашей сцене было меньше разных материалов / шейдеров. Компиляция шейдеров в WebGL приводит к длительной загрузке, что особенно заметно в Windows. Кроме того, если у вас меньше шейдеров, движок будет тратить меньше времени на переключение между ними во время рендеринга, тем самым улучшая производительность.
Если у вас есть похожие материалы, которые отличаются только текстурами, вы можете использовать только один материал и загружать / менять его текстуры во время выполнения. Для этого вы можете использовать JavaScript или взять визуальный редактор логики, имеющийся в некоторых WebGL-фреймворках. Это не только оптимизирует количество шейдеров, но и уменьшит количество изображений, загружаемых при запуске приложения.
![](https://habrastorage.org/webt/dm/3w/vs/dm3wvs45kjv3deg4w0342os2o7e.jpeg)
Вот пример такой оптимизации. Все четыре разновидности одной шины представлены одним материалом, и настраиваются путем замены текстур.
![](https://habrastorage.org/webt/8l/9d/vg/8l9dvgl_jlessslo7qozr7fnlfe.jpeg)
Чтобы уменьшить количество шейдеров, вы можете объединить 2 или более простых материала в один более сложный. Этот метод особенно эффективен, если вы планируете переключаться между этими материалами (например, создаете приложение-конфигуратор), и не просто переключаться, а плавно и красиво анимировать переход от одного материала к другому.
![](https://habrastorage.org/webt/tg/qj/nk/tgqjnky59qyocifqwtgxtvotsae.jpeg)
Draw-вызовы
Кроме того, есть еще один важный аспект — количество Draw-вызовов (они же draw calls и вызовы отрисовки). Это примерно соответствует количеству отдельных объектов, если для каждого объекта назначен только один материал, в то время как объектам с несколькими материалами требуется еще больше вызовов для их визуализации.
Поэтому вы должны стремиться объединять сетки, когда это возможно, и использовать меньше уникальных материалов, чтобы уменьшить количество вызовов отрисовки и повысить производительность.
![](https://habrastorage.org/webt/2s/62/if/2s62ifvsueoudvxkdaa6o4ilwse.jpeg)
Если у вас есть анимированный объект, вы все равно можете соединить его части и использовать кости для анимации, что иногда даже удобнее при анимации отдельных объектов.
![](https://habrastorage.org/webt/rm/sj/wi/rmsjwiqlei4hbedtbuafmxbrwsk.jpeg)
HDR освещение
Это помогает значительно улучшить производительность, если вы освещаете свою сцену только с помощью изображения HDR, без использования каких-либо источников света. Файл HDR может весить менее 1 Мб.
![](https://habrastorage.org/webt/pm/j_/rl/pmj_rli9pkrhkwg4jgh0aggk5mu.jpeg)
Тени
Используйте динамические тени только тогда, когда они помогают представить ваш объект в лучшем виде. На рисунке ниже показаны динамические тени из нашей демки Industrial Robot. Поскольку в этом приложении вращается сама модель, а не камера, тени не могут скрыть какую-либо часть объекта от пользователя и отлично подчёркивают форму робота. С другой стороны, такие же тени в демке Scooter будут затенять многие детали модели.
![](https://habrastorage.org/webt/o4/px/ik/o4pxikw8wlgmidid3mlswai50bq.jpeg)
Отсюда мы делаем вывод: если ваш объект не перемещается в приложении, вы можете запечь карты теней и ambient occlusion и назначить их на плоскость, расположенную под объектом. Такое решение будет более производительным и выглядеть качественнее, чем при использовании динамических теней.
![](https://habrastorage.org/webt/a4/5d/j1/a45dj1fnhbxpi4u8tnrpmqaptai.jpeg)
На этом всё. Если у вас есть ещё какие-нибудь советы, которые могут помочь с производительностью WebGL, пишите в комментариях.
GCU
Хороший набор советов.
P.S. Если можно — как-то пояснить что такое Smooth Group?
«шейдинг-группы», «с гладкими вертексными группами»
Это выглядит как интерполяция нормалей, привязанных к вершинам, а в чём смысл разных групп?..
alexkowel Автор
Спасибо. Smooth Group — это отдельные категории вертексов, в пределах которых нормали интерполируются независимо от других групп. Они позволяют комбинировать плоские и объёмные элементы в одной модели и при этом не увеличивать полигонаж слишком сильно.
GCU
Всё равно непонятно.
Например кубик с фаской:
В каждой вершине задана ещё и нормаль к поверхности, поверхность гладкая. На боковых гранях все вершины (минимум 4) имеют одну и ту же нормаль, на фаске — разную. Зачем там целых 4 группы?
При рендеринге это же всё равно будут треугольники и интерполяция нормалей будет у каждого треугольника своя, независимо от других.
P.S. Кажись дошло
В самом WebGL никаких групп нету, эта сущность только в CAD нужна чтобы правильно выставить нормали в вершинах
alexkowel Автор
Именно так, а в сам движок экспортируются уже готовые нормали.