{"id":4093,"url":"\/distributions\/4093\/click?bit=1&hash=572414f048fd940828f35b0d46d6210a2c91bd9267922fbe28005f6aba01f505","title":"\u0417\u0434\u0435\u0441\u044c \u0432\u0441\u0435 \u2014 \u00ab\u0421\u0431\u0435\u0440\u00bb, \u00ab\u042f\u043d\u0434\u0435\u043a\u0441\u00bb, VK \u0438 Kaspersky","buttonText":"","imageUuid":""}

Создание Glitch Effect«а» Джонни Сильверхенда на Unity

Доброго времени суток всем Unity нетранерам, а также тем, кто случайно наткнулся на эту статью!

Как бы многие на этом ресурсе не критиковали киберпанк, одно у него точно не отнять — это восхитительную работу всех специалистов, занимавшихся графической составляющей проекта. Лично я был в восторге от освещения в данной игре (даже с выключенным RTX"ом"), а эмоции от графических гличей, кои появляются в моменты сбоя чипа или возникновения голлограммы Джонни Сильверхенда никак кроме восторженными словами у меня описать не получится.

По этой причине, после прохождения игры у меня возникло непреодолимое желание разобрать один из эффектов появления Киану Ривза. Надо признать, что его голлаграмма это комплексный механизм, состоящий из десятков спецэффектов и намерянных гличей. Мы же сегодня постараемся реплицировать лишь один из них — а именно эффект появление голлограммы из вертикальных "линий", идущих в разнобой, но по итогу совмещаемых в одно единое целое.

Именно этот эффект и побудил меня на создание данного туториала.

Небольшой Viewer discretion перед тем, как я начну разбирать механизм. Данный прототип будет работать немного иначе, чем в оригинальной игре. Там, судя по моему наблюдению, для эффектов Джонни использовался BillBoard с грабпасом. Достаточно элегантное и экономичное решение в плане затраты ресурсов, но честно говоря моих текущих навыков пока не хватает чтобы реализовать это. Однако, если вдруг появятся знающие люди в комментариях, и у них будет желание пообщаться со мной по этому поводу — буду искринне рад.

Также данный алгоритм никак не претендует на вообще что либо, в нём скорее всего будет совершена куча ошибок, поэтому если появятся предложения по его улучшению — я только за. Вообще будет супер, если после этой статью я научусь чему — нибудь.

Ну и последнее — этот механизм будет сделан с помощью Шейдер Графа. По тому что во — первых sg более нагляден для представления, во — вторых менее сложен чем язык шейдеров, и в третьих — у такого шейдера на hsls всего пару строк кода, а из такого материала не удастся вытянуть целую статью!

Ну что — ж, поехали!

Глава 1: Первые шаги

Опустим шаги с импортом шейдер графа, установкой Render Pipelin«a» и разбрасыванием геймобджектов на сцене. В конечном итоге создаём простенькую композицию, на которой и будем тестировать наш эффект.

Вот так вот получилось у меня

Первая итерация нашего механизма будет работать следующим образом: Рендерим только объекты с "гличами" в одну рендер текстуру, а все оставшиеся — в другую. В первой текстуре рисуем эффекты, а потом просто смешиваем 2 этих изображения и выводим его на экран. А теперь более конкретно:
Создаём отдельный слой, называемый Glitch.

Помечаем им все наши объекты, которые будут «корёжиться». У нашей главной камеры в свойстве «Culling Mask» убираем слой «Glitch». Также создаём в проекте Render Textur'у', и линкуем её в свойство Target Texture у камеры.

Дублируем нашу камеру, ставим в Culling Mask уже только слой "Glitch", а "Clear Flags" устанавливаем в Solid Color и сэтаем цвет бэкраунда в чОрны, а также линкуем другую рендер текстуру в поле Target Texture. В конечном итоге у вас должно получиться что — то нападобие этого:

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

Наконец — то настало время заняться шейдерами! Так как наш шейдер будет проецировать уже отрендеренные изображения, никакая работа со светом, нормалями и прочим ему не нужна, поэтому создаём Unlit Graph.
В качестве первого шага нам нужно просто объединить эти две текстуры в одну. Для этого создаём 2 ноды Sample Texture, и объединяем их в ноде Lerp. Ну а результат пихаем в Color ноды Unlit Master. И получаем:

Проще и придумать сложно

А теперь немного теории, максимально упрощённой для понимания. Функция Lerp (линейная интерполяция) — крайне удобная штука в геймдеве, которая "преобразует" A в B с силой T, где T принимает значения от 0 до 1.

Как я показал на изображении, Альфа канал Glitch текстуры как раз отлично подходит под определение параметра T. А получился у нас такой красивый канал потому что у камеры мы поменяли значение "Clear Flags" с Background на Solid Color.

Осталось как то теперь это вывести на экран. Ну, вы можете воспользоваться третьей камерой, а можете схитрить как я — создать канвас, растянуть на нём Raw Image и установить туда материал созданный из нашиего шейдера. В конечном итоге мы получаем такое изображение:

Готов поклястся он ещё и в зеркалах не отражается.

Как вы можете заметить, пропали тени... Ну, ввиду того что мы делаем голлограму, а они, как известно, не отбрасывают тень, предлагаю забить на это дело. Хотя конечно такой результат выглядит неорганично для нашего восприятия (я думаю именно по этой причине Джонни в киберпанке отбрасывает тень). Если хотите получить её — могу предложить вам сдублировать объекты гличей, устанавить им default layer и в настройках рендера установить им Cast Shadows как Shadows Only.

Глава 2: Эффект Глича

Опять немного теории — расположение каждого пикселя на нашей текстуре определяется его UV координатой. Значения UV является абсолютной величиной, а значит у самого правого пикселя по оси Х будет значение 1, а левого — 0. Аналогично и на оси ординат. Вот вам наглядный пример цветовой палитры UV по OY:

Нода Split позволяет вычленить из 4х мерного вектора любой канал, в данном случае Y.

Двигая координаты UV мы можем смещать наше изображение. Как уже нетрудно догадаться, нам нужно просто добавить в Y какое — нибудь значение, не трогая Х. В нашем случае мы двигаем координаты целыми полосками, поэтому я заранее заготовил текстуру шума. Хотя конечно для рандомности её можно было бы и сгенерировать...

Для создания такого эффекта в Shader Graph«е» создадим ноду «UV», разделим функцией Split её на каналы, прибавим к Y каналу значение нашего шума (R канала шума будет вполне достаточно) и снова объединим значения в вектор UV, а затем укажем нашей текстуре полученный UV. Выглядеть это будет так:

По факту это продолжение графа из предыдущего скрина

Ну и результат:

Похоже на то как Вергилий в DMC разрубает экран.

Честно говоря выглядит не очень впечатляюще. Хотелось бы больше контроля. Для этого перед тем как добавлять к Y наш шум умножим его значение на какое — нибудь число, а сам наш шум затайлим по горизонтали. Оба этих значения вынесем в Property, и вот теперь мы можем дёргая эти значения получать ожидаемый результат:

Справа гифки видно, как я меняю значения

Кстати, та часть графа теперь стала вот такой:

Я всего - лишь хотел больше контроля, а граф разросся в 2 раза. Бррр...

Глава 3: Баг глубины

Знаете, я ведь не просто так ничего не ставил впереди наших объектов с гличом. Давайте попробуем поставить что — нибудь перед ботом:

Главный принцип любой хорошей призентации - умолчать об проблемах

Очевидно это возникает потому, что мы бездумно накладываем одно изображение на второе. Мы совсем забыли о таком понятии, как глубина изображения. На юнити из себя она представляет красный канал, в котором дальность пикселя определяется его яркостью.

Хммм... Похоже на крутой графический эффект для другого урока!

А вот теперь я совершу каминг аут — я понятия не имею как одной камерой рендерить отдельно цвет и глубину в разные рендер текстуры. Вроде у камеры есть метод camera.setTargetBuffers, в котором по идее можно определить буферы для вывода в разные рендертекстуры, но что — то он у меня не работает.

Поэтому сегодня, и только сегодня мы добавим на сцену ещё 2 камеры — одну для глубины мира, вторую — гличей. На этот раз обойдёмся без примеров, просто не забудьте у новых рендер текстур указать Color Format как Depth.

В нашем графе добавляем соответствующие Sample Textur«ы», и в текстуру глубины глича передаём ту же UV, что и у текстуры глича.

Дальше нам поможет волшебная функция Step — она принимает у себя 2 параметра — A и B, и возвращает 0 Если А > B, и 1 — если меньше. Кароче, я всегда в ней путаюсь. А стоит просто запомнить, что:

step(a, b) = a - b > 0 ? 0 : 1

Но так как результат у нас получается немного вверх тормашками, просто проносим наш его через ноду One Minus, которая под капотом отнимает от единицы входящий параметр, и меняем альфу из ноды Lerp на наш новый результат. Выглядеть в Shader Graphe это будет следующим образом:

Кто - нибудь знает как объединить Step и One Minus в один шаг?

Ну, а в самом проекте вот так:

They see me rollin'. Rotating...

Глава 4: Баг повторения текстуры (clamping’a')

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

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

Почему так происходит? Ну, конкретно в нашем случае мы сдвигаем все пиксели по оси Y на определённое расстояние, но когда шейдёр заходит за границы текстуры, вместо того чтобы передать пустоту, он тянет самые крайние пиксели. Слава богу мы сами знаем на сколько мы смещаем UV по Y (это значение которое мы помещаем в вектор UV.Y на втором шаге), поэтому вооружившись этим знанием, а также «степами» для верхней и нижней границ, мы узнаем какие координаты, которые необходимо удалить из текущих текстур глича и глубины глича. Вот что в итоге получится:

Красными полосками я указал изменения от предыдущей итерации.

Заключение

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

Но с HDR"ом" выглядел бы лучше...

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

Повторюсь, если в комментариях будут знающие люди, которые знают ответ на:
1) Как достать из одной камеры глубину и цвет изображения.
2) Как реализовать в юнити билборд, который рендерит всё, что за ним находится и может менять эти пиксели на ходу.
3) Заменить Step и OneMinus на одну ноду.
Да и просто обсудить настроение и планы на новый год — Буду счастлив пообщаться в комментариях :)

Ну а так — удачи всем в новом году! Оттянитесь сегодня на полную! :)

0
5 комментариев
Аккаунт удален

Комментарий недоступен

Ответить
Развернуть ветку
Конь Геннадий
Автор

Весьма интересная заметка, обязательно попробую прокачать себя в вертексах на графе, но... Вам не кажется что двигая вертексы не удастся получить 'ровные' вертикальные линии, даже если выкрутить тесселяцию на всю катушку? А уж тем более сделать разрыв между линиями.
P.S. могу быть не прав.

Ответить
Развернуть ветку
Никита Лукьянов

Определенно не получится через смещение вертексов такое сделать

Ответить
Развернуть ветку
Аккаунт удален

Комментарий недоступен

Ответить
Развернуть ветку
TVEYE

Glitch-эффекта

Ответить
Развернуть ветку
-2 комментариев
Раскрывать всегда
null