Режимы Смешивания Изображений В Unity – Реализация, Плюсы И Минусы

Студия разработчика Студия Twisted Pilot Артём Советников, сейчас работает над инди-игрой.

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



Режимы смешивания изображений в Unity – реализация, плюсы и минусы

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

А как насчет игр? Допустим, UI-художник создал впечатляющую графику для игрового интерфейса, но в некоторых слоях используется своего рода Soft Light. Или пришла идея использовать смешивание Color Dodge для системы частиц.

Или, может быть, вам нужно было использовать Divide Blend для 3D-объекта, чтобы получить эффект прямо из фильма Линча? В этой статье мы рассмотрим, как работают популярные режимы наложения, и воссоздадим их эффект в игровом движке Unity.



Алгоритмы смешивания

Для начала давайте разберемся, что именно нам нужно сделать.

В качестве примера возьмем два графических элемента и расположим их так, чтобы один перекрывал другой: В режиме наложения «Нормальный» цвет каждого пикселя нижнего слоя ( а ) будет полностью заменен цветом пикселя слоя, который его «перекрывает» ( б ).

Здесь все тривиально: именно так «перемешивается» большинство графических объектов в играх.

В режиме «Экран» цвета пикселей обоих слоев инвертируются, умножаются, а затем снова инвертируются.

Мы реализуем этот алгоритм на Язык компьютерной графики : Обратите внимание, что альфа-компонент результирующего цвета ( р.

а ) мы передаем значение альфа верхнего слоя ( б.

а ) сохранить возможность самостоятельно контролировать уровень прозрачности материала.

Алгоритм наложения работает условно: для «темных» участков цвета умножаются, а для «светлых» используется аналог режима «Экран».

Режим наложения «Темнее» сравнивает значения каждого из трех цветовых каналов для двух слоев и оставляет тот, который «темнее».

Большинство остальных режимов работают по схожим схемам.

Если вам интересно, реализации еще 18 алгоритмов микширования в Cg можно найти.

Здесь .

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



Реализация с использованием GrabPass

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

Однако именно этот этап оказался наиболее проблематичным в практической реализации.

Дело в том, что во время выполнения фрагментного шейдера невозможно получить доступ к содержимому кадрового буфера, в котором находится «задний слой», из-за логики конвейера рендеринга: Окончательное изображение формируется после выполнения фрагментного шейдера; соответственно, мы не можем получить его непосредственно во время выполнения программы Cg. Это означает, что нам нужно искать обходные пути.

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

Например, реализация большинства эффектов постобработки немыслима без доступа к «конечной картинке».

Для таких случаев существует так называемый рендер в текстуру — данные из буфера кадра копируются в специальную текстуру, из которой затем считываются при следующем выполнении фрагментного шейдера: Существует несколько способов работы с текстурами рендеринга в Unity. В нашем случае наиболее целесообразным будет использование GabPass — особого типа «прохода», захватывающего содержимое экрана в текстуру, где будет отрисовываться объект. Именно то, что нам нужно.

Давайте создадим простой шейдер для UI-графики, добавим в него GrabPass и вернем результат смешивания цветов с помощью алгоритма Darken из функции фрагмента.

Полученный код можно найти Здесь .

Для оценки результата возьмем те же текстуры, которые мы использовали в графическом редакторе при демонстрации режимов наложения: Как видно на иллюстрации, результаты рендеринга UI-графики в Unity и документа в Photoshop идентичны.

На этом можно было бы остановиться, если бы не одно «но»: рендеринг в текстуру — довольно трудоемкая операция.

Даже на среднестатистическом ПК одновременное использование более 100 таких операций приводит к заметному падению частоты кадров.

Что еще хуже, скорость GrabPass обратно пропорциональна разрешению экрана.

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

В моем случае даже пара UI-объектов с «нетрадиционным» смешиванием в пустой сцене приводили к падению FPS ниже 20.

Реализация с использованием Unified Grab

Напрашивается одна оптимизация: почему бы не использовать один GrabPass? Исходное изображение в кадре остается неизменным, а это значит, что вы можете «захватить» его один раз, а затем использовать для всех последующих операций наложения.

Unity предоставляет нам удобный способ реализовать наши планы.

Достаточно передать в дизайн GrabPass строку с именем переменной, в которой мы хотим хранить «общую» текстуру рендеринга: Теперь любой экземпляр материала, использующий этот шейдер, будет получать информацию из общей текстуры рендеринга и не будет выполнять дорогостоящий GrabPass, если он уже был выполнен одним из экземпляров.

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

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

То есть такие объекты «не видят» друг друга и не учитывают эту информацию при смешивании.

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



Реализация с использованием BlendOp

Поскольку использование GrabPass в любом виде обходится слишком дорого, попробуем обойтись без него.

Один из вариантов — попробовать изменить режим смешивания, что происходит после фрагментного шейдера (как часть конвейера рендеринга Unity): Этот этап используется в основном для обработки полупрозрачных объектов и возможности его модификации весьма ограничены — туда нельзя вставлять инструкции в Cg. Вы можете использовать только набор ключевых слов, чтобы настроить, как цвет, полученный из фрагментного шейдера, должен (если вообще) взаимодействовать с цветом, который находится «за ним».

Операция определяется следующей конструкцией: Логика такова: исходный цвет (полученный из фрагментного шейдера) умножается на значение, возвращаемое первым операндом (SrcFactor), целевой цвет (цвет «заднего» слоя) умножается на второй операнд (DstFactor).

, и полученные значения складываются.

Список операндов, в свою очередь, довольно ограничен: можно оперировать единицами, нулями, исходными и целевыми цветами, а также результатами их инверсии.

Несколько расширяет возможности необязательная команда BlendOp, которая позволяет заменить сложение результата двух операндов вычитанием, взяв минимум или максимум.

Проявив немного воображения, мне удалось реализовать следующие алгоритмы микширования: Затемнение: BlendOp мин.

Смешайте один один Светлее: BlendOp Макс Смешайте один один Линейный ожог: BlendOp RevSub Смешайте один один Линейный уклон: Смешайте один один Умножение: смешивание DstColor OneMinusSrcAlpha Мы модифицируем наш шейдер смешивания графики пользовательского интерфейса в режиме Darken для использования BlendOp. Для демонстрации мы будем использовать те же текстуры: Проблема очевидна: из-за того, что мы используем этап смешивания «под свои нужды», альфа-смешивание делать негде и прозрачность объектов просто игнорируется.

С другой стороны, непрозрачные объекты смешиваются правильно и без потери производительности.

Так что если вам нужно использовать один из режимов, который можно воссоздать с помощью конструкции Blend, а у объекта нет прозрачных областей, это, пожалуй, лучший вариант.

Реализация с использованием Framebuffer Fetch

Ранее я упоминал, что из фрагментного шейдера невозможно получить доступ к фреймбуферу.

На самом деле это не так.

В 2013 году спецификация OpenGL ES был добавлен специальная функция , который позволяет получить доступ к данным фреймбуфера из фрагментного шейдера.

А несколько месяцев назад в релизе Unity 4.6.3 была анонсирована поддержка этой функции со стороны Cg. Мы модифицируем наш шейдер для использования Frame Buffer Fetch. Идеальный.

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

Только иллюстрация выше представляет собой фрагмент скриншота, сделанного с iPad Air. Но, например, наш шейдер просто откажется работать в редакторе Unity. Проблема в том, что поддержка спецификации OpenGL ES полностью реализована только в устройствах под управлением iOS. На других платформах (даже если их графическая подсистема использует OpenGL ES API) эта функция может не работать, поэтому на кроссплатформенность рассчитывать не приходится.



Заключение

Мы рассмотрели четыре реализации режимов наложения в игровом движке Unity:
  • GrabPass — самый ресурсоемкий, но максимально корректно воспроизводит все режимы микширования;
  • Unified Grab — это оптимизация GrabPass, она значительно повышает производительность при одновременном выполнении нескольких операций смешивания, но исключает возможность смешивания объектов друг с другом;
  • BlendOp — работает максимально быстро, но допускает лишь ограниченное количество режимов и не поддерживает полупрозрачные материалы;
  • Frame Buffer Fetch — работает так же быстро, корректно воспроизводит все режимы, но его использование возможно только на устройствах под управлением iOS.
Единого универсального и кроссплатформенного решения найти не удалось, но сочетание представленных вариантов позволит в большинстве случаев использовать «смешивание».

Если вы хотите поэкспериментировать с решениями, которые были описаны в статье, репозиторий проекта Unity доступный на Гитхабе.

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




Если вы работаете над технологией Unity, хотите рассказать о своем опыте разработки или задать вопрос экспертам, оставьте заявку нашим наставникам, используя специальная форма .

Вместе с данным постом часто просматривают:

Автор Статьи


Зарегистрирован: 2019-12-10 15:07:06
Баллов опыта: 0
Всего постов на сайте: 0
Всего комментарий на сайте: 0
Dima Manisha

Dima Manisha

Эксперт Wmlog. Профессиональный веб-мастер, SEO-специалист, дизайнер, маркетолог и интернет-предприниматель.