Создание шейдера обратного фи-феномена в Unity: мой опыт
Все кто страдает фоточувствительной эпилепсией или боится программного кода или математических формул эта статья не для вас!
Все кто страдает фоточувствительной эпилепсией
Визуальная составляющая играет ключевую роль в разработке игр. Один из наиболее уникальных и недооцененных приемов - использование оптических иллюзий.В контексте разработки игр, оптические иллюзии могут быть использованы для создания уникальных визуальных эффектов и добавления глубины и сложности в игровой мир. Они могут помочь создать уникальную атмосферу, улучшить визуальное восприятие игрока или даже стать ключевым элементом геймплея.
Случайно наткнувшись год назад на статью об оптических иллюзиях (основанных на обратном фи-феномене), я загорелся повторить этот эффект. Но на тот момент я не обладал достаточным опытом и знаниями (тогда еще не было доступа к ChatGPT), поэтому к реализации я приступил только сейчас, и у меня вышел вполне рабочий прототип, о чем и будет данная статья.
Обратный фи-феномен — это иллюзия движения, которая достигается благодаря быстрому изменению цвета и контрастности элементов изображения (в данном случае контура).
В той же статье じゃがりきん, любезно раскрывает тайны своего творческого процесса, предоставляя материалы для изучения. Из этих материалов стало ясно, что основа его работы - это цветовая палитра, которую он применяет к дублированным изображениям со смещением. Эти изображения затем окрашиваются в цвета, равноудаленные друг от друга на заданном цветовом спектре.
На первый взгляд, задачка может показаться очень простой. Однако, как оказалось, это задача "со звездочкой", требующая не только знаний, но и творческого подхода.
Когда я увидел, что решение уже подано на серебряном блюдечке и требуется лишь его программная реализация, я решил немного усложнить себе жизнь и создать шейдер для Unity. Этот шейдер должен воссоздавать данный эффект на любом спрайте, что значительно упростило бы его использование в реальных проектах.
Шейдеры в Unity - это небольшие программы, написанные на специальном языке программирования, называемом GLSL (или HLSL для DirectX). Они выполняются на графическом процессоре (GPU) и используются для определения внешнего вида и отображения объектов на экране.
Далее будет подробный процесс разработки шейдера, начиная от идеи и заканчивая конечной реализацией. Мне кажется, что статей, описывающих полный цикл разработки, сегодня недостаточно. В качестве исходного материала я выбрал эту иллюзию, поскольку она показалась самой простой в реализации.
Итак, моя первая ошибка заключалась в том, что я сразу же отбросил идею использования цветового спектра автора и решил обратиться к стандартной HSV-палитре Unity. Я обратился к ChatGPT 4 с просьбой помочь мне создать шаблон шейдера, который бы дублировал спрайт и устанавливал координаты для дублированного элемента. Второй элемент просто брал эти значения с противоположным знаком, чтобы оказаться на противоположной стороне. Значения цвета также задавались отдельно для каждого элемента в переменной цвета. (Не привожу код многим он будет не интересен, а занимает достаточно большой объем текста)
Получив чистый и, что стоит отметить, тщательно прокомментированный код (я не могу оценить его качество, так как я не специалист в области шейдеров, но если вы разбираетесь, буду рад услышать ваше мнение в комментариях), я решил реализовать остальную логику на привычном мне C#. Самый простой способ выбора цветов по стандартному спектру выглядит примерно так: мы берем текущее время, умножаем его на переменную скорости и полученное число используем в качестве оттенка в системе цветов HSV.
Ну и для дублируемых спрайтов берем значения = hue - coloroffset и hue + coloroffset (В коде выше это не указано) "Изи", - подумал я, и запустил программу…
Так, кружочки есть - есть, цвета меняются - меняются, эффект похож - ну как бы да, но как будто его собрали китайские дети в гараже) И тут я понял, что это задачка не на один вечер( Я открыл Photoshop, загрузил исходную гифку и решил проверить первый кадр. Судя по цветовому спектру HSB в Photoshop (который аналогичен HSV в Unity все верно, цвета находятся на равном удалении спектра и все верно.
Однако, переключившись на следующий кадр, я начал осознавать, что этот спектр не совсем подходит. Цвета спрайтов уже располагались на неравном удалении по спектру и значения яркости и насыщенности разных цветов варьировались от кадра к кадру совершенно непредсказуемо, не подчиняясь какой-либо очевидной логике.
Пришло время начать все сначала. Мы возвращаемся к самому первому скриншоту, предоставленному автором этой иллюзии. Зная, как в Unity формируется значение цвета с помощью RGB, мы немного дополняем рисунок. В Unity для задания цвета используются значения каждого из цветов R, G и B в диапазоне от 0 до 1 - это будет ось Y, в то время как ось X будет отвечать за время.
И вот здесь наступает момент истинного удовлетворения - момент, когда мы вспоминаем школьную математику и осознаем, что все эти синусы и косинусы изучались не зря! После некоторого размышления мы приходим к следующим формулам:
И дописываем в код следующие функции:
И задавать цвет будем соответственно через RGB:
Ну все, теперь то точно заработает! Пуск…
WTF! Ну вот что может быть не так! Я даже спектр взял правильный, поэкспериментировал со скоростью смены цвета, значением отклонения по спектру, ну все должно быть верно! … Лезем обратно в Фотошоп… Берем первый цвет на первом кадре и видим, что значения R и G совпадают, а вот значение B взято неправильно! То есть по этому графику и невозможно было повторить эффект!
В этот момент задача окончательно заслужила свою звездочку сложности, а я лишился спокойного сна( Что могло пойти не так? Я даже выбрал правильный спектр, настроил скорость смены цвета, значение отклонения по спектру... все должно было быть верно!
Возвращаемся обратно в Фотошоп… Я выбрал первый цвет на первом кадре и обнаружил, что значения R и G совпадают, но значение B было выбрано неправильно! Таким образом, по этому графику было невозможно воспроизвести эффект! (P.S на остальных кадрах была такая же картина)
В правильном спектре синий канал должен быть в противофазе красному, как-то так:
Меняем функцию для Синего канала на y = 0.5 cos(0.5π*(x-1))+0.5
Снова запускаем программу, и вот он - желаемый эффект начинает работать! Все цвета по значениям RGB теперь точно соответствуют исходному файлу. Осталось лишь более тщательно подобрать скорость смены цвета и значение отклонения по спектру.
Вау! Но, как я упоминал в самом начале, моя цель - создать шейдер, а не C# скрипт. Конечно, результаты уже впечатляют, но давайте наконец объединим все это вместе! За эти пару дней, проведенных в компании с ChatGPT, я значительно продвинулся в понимании шейдеров и теперь готов собрать этого франкенштейна.
Начнем с определения переменных. Нам нужно, чтобы в материале Unity мы могли контролировать скорость смены цвета, радиус отклонения дубликатов спрайта, угол отклонения, разницу в спектре для дубликатов:
В нашем шейдере мы будем рисовать все за три прохода (пасса) - основной спрайт и два его дубликата по отдельности. Давайте рассмотрим пример отрисовки одного из пассов:
Сначала мы определяем координаты для отрисовки левого дубликата. Поскольку мы решили упростить себе задачу, задавая отклонение спрайта через радиус, а не через координаты x и y, нам нужно решить простую школьную задачу по нахождению координат точки на окружности.
Для начала возьмем переменную float4 _Offset. Это вектор из четырех значений с плавающей запятой, который мы будем использовать как хранилище для координат X и Y.
Находим координату X через косинус, а Y через синус:
X=cos(α)∗RX=cos(α)∗R
Y=sin(α)∗RY=sin(α)∗R
Где α - это значение угла поворота нашей иллюзии, а R - радиус отклонения дубликатов спрайта.
Далее устанавливаем цвет и координаты для нашего спрайта
Здесь мы задаем цвет через каналы RGB по тем же самым формулам, которые уже реализовывали в C#
Вот и все, детская задачака со звездочкой успешно решена! Поздравляю вас, и, конечно, поздравляю себя!
После того, как шейдер был написан, я провел много времени, тестируя и настраивая его, чтобы убедиться, что иллюзия работает правильно. Это включало в себя изменение различных параметров, таких как цвета, интенсивность света и скорость анимации, чтобы достичь желаемого эффекта.
Я считаю, что этот шейдер может прекрасно вписаться в игры в стиле shmup или топ-даун шутеры, например "The Binding of Isaac". Он также может добавить сложности играм, вдохновленным "Geometry Dash". Но в качестве дополнительного элемента, добавляющего сложности игровому процессу, он, безусловно, может найти свое применение.
Какие у вас мысли на этот счет? В каких играх, по вашему мнению, такие оптические иллюзии будут уместны? И готовы ли вы принять вызов и окунуться в игру с такими элементами?
Полный код шейдера я скоро скину в свой телеграмм канал там же в скором времени будет еще много интересного и полезного контента по разработке игр, программированию и моделированию, подписывайтесь!
P.S: Я уже доработал шейдер и в нем теперь можно выбирать режим работы (смещение, увеличение, уменьшение объекта)