ASCII‑art Шейдер на Unity для новичков
Шейдера в юнити — это именно та часть, которой пугают джунов на собеседованиях. Однако если потратить немного времени становится ясно, что вся эта шейдерная магия на самом деле, как и любой программный код вашего проекта может быть слеплена из говна и палок. И сейчас я постараюсь личным примером вам это доказать.
Совсем недавно вышла статья в разделе геймдева, которая должна была нам объяснить как добиться такой магии:
Однако ничего кроме сложных слов из - разряда Luminance, Lut текстура и pikabu я в ней не нашёл. Она и побудила меня попробовать написать шейдер, пусть и откладывал это дело я наверное месяц.
Реализация данного шейдера будет состоять из пары шагов:
1) Разбивание изображения камеры на монолитные прямоугольники равного размера.
2) Замещение этих прямоугольников текстурами символов, подбираемых в зависимости от цвета.
Руки чешутся написать пару строк кода, так что давайте начнём с подготовки. Для охвата наибольшей аудитории эта часть будет расписана максимально подробно:
Подготовка к работе
Для начала создадим простой Unlit Shader, и удалим оттуда всё лишнее (связанное с туманом). А также вместо return col для начала вернём свой кастомный цвет float(0, 0, 0, 1). Получаем на выходе:
Обратите внимание на блок Properties - сюда мы будем кидать поля, блок над функцией v2f vert - здесь мы будем инициализировать поля, а также на функцию fixed4 frag - в ней мы с вами будем срать кодом.
Несмотря на то, что шейдер у нас уже готов, надо заставить камеру его как то рендерить. Для этого создаём скрипт - AsciiCamera, и переопределяем у него метод OnRenderImage, заставляя нашу камеру использовать кастомный материал. Кодом это будет выглядеть так:
Теперь создаём материал, и устанавливаем ему наш шейдер. На самой же сцене вешаем наш AsciiCamera скрипт на камеру, и линкуем материал внутрь монобеха. Если вы всё сделали правильно, то вы увидете, что камера рендерит чёрный экран. Ну разве не прекрасно?
Когда с приготовлениями покончено, можно в функции frag раскомментировать правильный ретурн, и убрать строку с возвратом float4(0, 0, 0, 1) куда подальше.
Шаг 1
Итак, мы подошли к первому шагу. Нам необходимо разбить исходное изображение:
На пиксели. Выставить видеоряд в 144р так сказать.
Для этого нам подойдёт функция frag из шейдера. Видите код:
Здесь наш шейдер берёт конкретный пиксель экрана. Uv - это по факту координата пикселя, а _MainTex - текстура, в данном случае наше изображение с камеры. Следует учесть, что uv - это относительная величина. Т.е. самый левый верхний пиксель имеет координату (0,0), а самый нижний правый - (1,1).
Давайте введём в шейдер такие понятия, как ширина и высота. В блоке properties шейдера, прямо под строкой "_MainTex ("Texture", 2D) = "white" {}" добавим вот такой код:
Цифры хоть и похожие на правдивые, таковыми не являются, ведь на вашем мониторе скорее всего другое разрешение. Поэтому, чтобы наш шейдер всегда работал с правильной диагональю монитора, в функции апдейта монобеха AsciiCamera запихнём принудительную установку этих значений внутрь материала:
А также над функцией шейдера v2f vert добавим 2 соответствующих поля:
Вообще говоря куда вы их поместите не принципиально, важно только чтобы они были в блоке Pass, и имели такое же имя и тип как в Property.
Теперь создайте для шейдера 2 параметра, ширины и высоты ячейки (CellWidth и CellHeight), на этот раз сами. Когда всё будет готово, вернёмся наконец к нашей функции frag. По факту нам нужно разбить всё изображение на блоки, и сделать, чтобы каждый блок был монолитного цвета. Это очень важно. Цветом блока я решил выбрать центральный пиксель. Код будет выглядеть вот так:
В результате мы получаем такую картину:
Шаг 2
Теперь нам необходимо заменить каждый блок из исходного изображения на символ соответсвующего цвета из второй текстуры. Немного повозившись в фотошопе я быстренько нарисовал себе таблицу символов 10х3.
Добавим в шейдер текстуру с символами (AddTex("Texture", 2D) = "white" {} внутри Property, и sampler2D AddTex в Pass'e').
Символ, который будет рисоваться вместо блока будет определяться следующим образом - суммируем rgb цвета исходного блока, и остаток от деления на 10 - это индекс по X символа, а на 3 - это Y замещаемого символа. В конечном итоге наша функция frag должна превратиться вот в такого небольшого монстра:
Результат? Ну, он немного стрёмный:
Всё потому что необходимо подбирать замещаемые символы не абы как, а в соотвествии с палитрой цветов исходного изображения. Например точка соответсвует самому тёмному символу, а решётка - самому светлому.
Немного перерисовав символы у меня получилось добиться такого результата:
На этом всё, пойду играть в Апекс. Ведь это у меня получается лучше чем написание шейдеров и статей. А вам желаю хорошо провести остаток этого воскресенья!
Upd: Залил улучшенную версию на гитхаб:
Комментарий недоступен
Когда мне был 21 год я мечтал делать трипл А на анриле, но скоро я осознал что деньги не пахнут и начал делать мобилки на юнити...
Комментарий недоступен
Комментарий недоступен
Комментарий недоступен
26.
Можно делать не ААА а оригинальные проекты, главное силы оценивать свои заранее)
ООО!!! Спасибо огромное!!!
Опять придётся садиться ковырять немножко шейдеры в юнити =)
Тоже так делаю когда появляется статья о них)
Просто поковырять можно и онлайн (:
https://www.shadertoy.com/view/wt3XR4
Знаю)) Там очень много крутых вещей. Но я переключаюсь на такие вещи только когда это прям интересно. Потом интерес может улететь и я снова переключаюсь на другое.)) Сейчас вот это можно глянуть. Для собственного развития)))
Пажжи, если может быть слеплена из говна и палок то зачем код, там же есть Shader Graph 🤔
слеплен
из говна и палок
поправил
Ой, да ладно, нормально все с шейдер графом.
У него конечно код стремный на выходе, но с удобством все хорошо.
На шейдер фордже так и вообще на выходе получался ровно тот код, который ты бы руками делал, только имена переменных странные.
Другое дело, что эти инструменты такие удобные и наглядные, что очень легко сделать слишком тяжелый шейдер. Но это не проблема редактора.
Принцип получается как и со всеми текущими абстракциями - чтобы сделать шейдер хорошо в визуальном редакторе, нужно хорошо знать, как он на самом деле работает.
У шейдер графа ужасный функционал, он спустя 2 года до сих пор амплифай даже близко не догоняет. То, что в шейдерграфе пайплайны захардкожены и он у тебя сломается, если ты SRP попробуешь переписать — вообще смехота. Вообще все, что касается SRP-стэка кроме, наверное, VFX-графа, полный провал и ужас, который уже сейчас надо переписывать с нуля с реальной гибкостью в основе.
Привет! Я вот только увидел ваш пост, здорово, что мои мысли без примеров кода побудили Вас сделать шейдер. Кстати, Вы могли бы погуглить что такое "luminance" (грубо говоря это просто grayscale значение исходной картинки), и учесть ее в вашем шейдере вместо рандомного выбора из таблицы, было бы красивее.
Если бы не ваша статья, я бы не попробовал, а по факту это оказалось весьма интересно и ничуть не трудно. Сам то я шейдерами не занимаюсь от слова совсем, только пару раз по работе приходилось писать совсем тривиальные вещи.
Да, именно с грейскейлом я и переделал после публикации статьи. Правда я тупо брал грейскейл блока, а не всей картинки.
Вообще я хотел делать через интенсивность света, но она не работала, так как скрипт висел на камере, а не на объекте.
Статья не для новичков, максимум они код копирнут) принцип работы для новичка будет не тривиален, много мусора в коде тоже делу не поможет. И мне кажется можно было атлас сделать однострочным, код был бы проще, яркость можно считать юнитевскими функциями
На самом деле я использовал в конечном итоге однострочный атлас, а блоки сделал квадратными, как итог код стал ещё меньше и понятнее, а результат стал бомбическим. Выше в комментах есть ссылка на гифку. Просто мне стало влом переписывать статью.
А вот считать яркость юнитёвскими функциями - это как забивать гвозди головой. Просто берёшь (0.3 * R) + (0.59 * G) + (0.11 * B) в качестве отступа, и сэмплишь по однострочной таблице.
Очень реквестую автора таки дописать и допилить статью, потому что задел просто отличный
Смысла в этом нет - статья уже утонула в тонне мемесов и шутеек про порно, новые пользователи врядли её найдут, а те кто прочитали возвращаться в неё не будут. Могу залить конечный вариант на гитхаб, и поделиться ссылкой в статье.
DTF, DTF никогда не меняется...
Вариант с гитом тоже хороший, мое почтение
Ковыряйся на здоровье:
https://github.com/Ignatus911/AsciiShader
Так она это и делает)
Точка самому темному? Эмм ... по моему гораздо логичнее самому темному цвету задать — пробел , то есть отсутствие символа .
Логично. Так и сделал. Вышло куда круче.
Результат в студию
Как всегда добавлю в закладки, которые никогда не читаю)
@Конь Геннадий замени самые темные области не на точку, а вообще на пустоту. Тогда добьешься такой же картинки, как в роликах Watch_Dogs 2
Да, сделал что то подобное.
https://psv4.userapi.com/c856436/u21561243/docs/d5/713d23667ce5/3213123.gif
Ссылка битая
Интересно, а если так
Ага, двоеточие замени на точку : —> .
Конечно. Вы сначала хоть почитайте статью, или хотя бы сравните картинки.
Ну, тип, в статье можно же написать "техническая реализация {ссылка на статью}".
Даже на превьюшку то же изображение кинули.
Классная статья, вообще отличная. "Всем здарова. Есть ASCII. Это круто. Из них можно делать преколы. Еще из них делали арты для кейгенов. Я бы сделал прекол нарисовав два овала, а потом сову. Всё пока"
Там дан алгоритм. Ну, и людям то она понравилась, один хрен.
В обеих статьях описан алгоритм реализации. Но моя статья имеет примечание - для новичков, т.к. в ней все шаги расписаны максимально подробно, а также из математики требуются только только знания о векторах и ничего больше.