Лого vc.ru

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

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

Разработчик студии Twisted Pilot Studio Артём Советников, в данный момент работающий над инди-игрой Breached, специально для подраздела Unity написал колонку о том, как можно реализовать смешивание изображений на Unity, и описал плюсы и минусы разных подходов.

Поделиться

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

А что же в играх?

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

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

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

Сначала разберёмся, что именно нам необходимо сделать. Возьмём для примера два графических элемента и расположим их так, чтобы один перекрывал другой:

При обычном (Normal) режиме смешивания, цвет каждого пикселя нижнего слоя (a) полностью замещатся цветом пикселя слоя, который его «перекрывает» (b). Здесь всё тривиально: таким образом «смешивается» бо́льшая часть графических объектов в играх.

В режиме Screen цвета пикселей обоих слоев инвертируются, перемножаются, а затем снова инвертируются. Реализуем данный алгоритм на языке Cg:

Обратите внимание, что в альфа-компоненту результирующего цвета (r.a) мы передаём значение альфы верхнего слоя (b.a), чтобы сохранить возможность независимо контролировать уровень прозрачности материала.

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

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

Большинство остальных режимов функционируют по похожим схемам. Если вам интересно, реализацию ещё 18-ти алгоритмов смешивания на Cg можно найти здесь.

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

Реализация с помощью GrabPass

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

Дело в том, что получить доступ к содержимому кадрового буфера (frame buffer), в котором и находится тот самый «задний слой», во время выполнения фрагментного шейдера (fragment shader) невозможно в связи с логикой работы конвейера визуализации (rendering pipeline):

Финальное изображение (final image) формируется уже после исполнения фрагментного шейдера, соответственно, мы не можем напрямую получить его во время выполнения Cg-программы. Значит, нужно искать обходные пути.

На самом деле потребность в данных о финальном изображении в рамках фрагментного шейдера возникает довольно часто. Реализация большинства пост-эффектов (post processing effects), например, немыслима без доступа к «финальной картинке». Для подобных случаев существует так называемый рендер в текстуру (render to texture) — данные из кадрового буфера копируются в специальную текстуру, из которой затем выполняется чтение при следующем выполнении фрагментного шейдера:

В Unity существует несколько способов работы с рендер-текстурой. В нашем случае наиболее подходящим будет использование GabPass — специального типа «прохода» (pass), который захватывает содержимое экрана в текстуру там, где объект будет нарисован. Как раз то, что нам нужно.

Создадим простой шейдер для 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, которая позволяет заменить сложение результата двух операндов вычитанием, взятием минимума или максимума. Проявив немного фантазии, я смог реализовать следующие алгоритмы смешивания:

Darken:
BlendOp Min
Blend One One

Lighten:
BlendOp Max
Blend One One

Linear Burn:
BlendOp RevSub
Blend One One

Linear Dodge:
Blend One One

Multiply: Blend DstColor OneMinusSrcAlpha

Модифицируем наш шейдер смешивания UI-графики в режиме Darken для использования BlendOp.

Для демонстрации воспользуемся всё теми же текстурами:

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

Реализация с помощью Framebuffer Fetch

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

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

Модифицируем наш шейдер для использования Frame Buffer Fetch.

Идеально. Казалось бы, что ещё нужно? Никаких лишних операций, максимальная производительность, можно реализовать любую логику смешивания… Только вот иллюстрация выше — это фрагмент скриншота, снятого с iPad Air. А вот, например, в редакторе Unity наш шейдер работать просто откажется.

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

Заключение

Мы рассмотрели четыре реализации режимов смешивания на игровом движке Unity:

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

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

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

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

Благодарю за внимание и с удовольствием отвечу на любые вопросы в комментариях.


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

Статьи по теме
Роман Менякин, Unity: Чего ждать от интеграции с WebGL и что такое Extended Unity Cache30 июля 2014, 14:11
Советы по выбору игрового движка от генерального продюсера AminiLab Ильи Пшеничного09 июня 2015, 11:37
Инструкция по созданию демо-ландшафта в Unity за 24 часа04 июня 2015, 17:06
Популярные статьи
Показать еще
Комментарии отсортированы
как обычно по времени по популярности

ЦП = Habr
Круто ;)

0

"специально для подраздела Unity"

это ж на харбре было еще полтора месяца назад
habrahabr.ru/post/256439/

0

Возможность комментирования статьи доступна только в первые две недели после публикации.

Сейчас обсуждают
Актуальная Птица

у соседа на заборе тоже написано :)
исполнятся одни желания, появятся другие
а если желаний нет то вы уже почти святой.

«Добро пожаловать в 2030 год»: член датского парламента о счастливой жизни без приватности и личных вещей
0
Kirill Nikolaev

Да тут та же самая проблема, как на фрилансе. Смотрим портфель, смотрим квалификацию.
Заплатили за шлак - пишем в ТП.

Bramio — поиск экспертов и решение задач с помощью видеозвонков
0
Kirill Nikolaev

Короче, вот:
1. Я зарегистрировался. Почему-то смутило, что в блоке "номер телефона", на вкладке "верификация" номер телефона отображался, как верифицированный. Хотя я его только лишь ввёл.
2. Ребята, пожалуйста, UX, UX, UX в личном кабинете.
3. А где работа-то? Одна заявка двухмесячной давности. Или я не вовремя зашёл?
4. Деньги через пейр. Только пейр. Вы серьёзно? В 2016м? А что не онли догекоины? Я честно создал второй акк, чтобы потестить, как происходит сам "процесс", но, к сожалению, после составления контакта баланс я так и не смог пополнить, хотя в пейре я всё-таки зарегистрировался (ну за что)
5. Опять же, классификаторы и рубрикаторы очень надо адекватные делать. Очень. Надо.

Резюмирую (очень субъективно):
Очень сыро, но задатки хорошие.
Самая боль - это ux и биллинг. Я понимаю, что вы можете заставлять пройти 9 кругов ада, чтобы вывести деньги. Но дайте мне, чёрт возьми, возможность их туда завести так, как мне удобно.

Bramio — поиск экспертов и решение задач с помощью видеозвонков
0
Andre Vlasov

Эцсамое. Во первых Amy Tunick это женщина, а во вторых ник fuck Jerry - это не "переспать с Джерри", а "ну его Джерри ***** (на половой член)". Причём там Под Джерри имеется в виду комик Джерри Сайнфелд. Ну и ещё, beigeCardigan это вроде его девушка. А в остальном все верно. Спасибо, пожалуйста.

«Брендам должно быть комфортно»: как американский блогер под псевдонимом FuckJerry завоевал популярность рекламодателей
0
Слава Діонісьєв

К роскомдозору стоит очередь

Роскомнадзор заявил об отсутствии претензий к Netflix из-за «непопулярности сервиса» в России
0
Показать еще