Разделение цвета или хроматическая аберрация
Шейдеры, которые мы рассматривали до сих пор, применялись к отдельным объектам сцены. Эффект постобработки (часто называемый «постэффектом» или в Unity «Screen-эффектом») — это эффект, который применяется сразу ко всему экрану, как если бы вы сделали снимок экрана с игрой и пропустили его через фильтр в Фотошопе.
Вы, вероятно, видели, некоторые распространенные постэффекты: разделение цвета, свечение (предметы светятся!) и глубину резкости (вещи размытые или резкие в зависимости от расстояния).
Сегодня мы напишем простой постэффект — разделение цвета по каналам RGB. Его можно использовать, чтобы придать игре слегка глючную ретро атмосферу. Я упомянул, что постэффекты похожи на запуск фильтра на скриншоте, и в основном так они и работают. Мы напишем шейдер, который позволяет нам брать то, что видит камера, и обращаться с этим изображением как мы бы обращались с основной текстурой в любом другом шейдере. Затем мы используем результат как окончательное изображение, которое показывается игроку.
Настройка
Откройте сцену, которую мы использовали в прошлый раз, или создайте новую сцену и перетащите в нее ваши любимые модели. (Я буду использовать ту же сцену, что и в прошлый раз.)
В папке Assets / Shaders создайте новую папку с именем PostEffects и в этой папке создайте новый шейдер ImageEffect под названием ColorSplit.
Затем создайте новый скрипт на C #, который также называется ColorSplit.
Выберите вашу основную камеру в иерархии, чтобы вы могли видеть ее в инспекторе. В инспекторе нажмите «Добавить компонент», затем начните печатать «ColorSplit», пока не увидите новый скрипт. Нажмите на него, чтобы добавить в качестве компонента вашей камеры.
Супер! Теперь давайте переключимся на Visual Studio. Если вы откроете шейдер ColorSplit, вы обнаружите, что в шаблоне уже есть записанный эффект. Как отмечалось, тот инвертирует цвета.
Давайте изменим название вверху на «Xibanya / Effects / ColorSplit» и сохраним.
Вы заметите, что прежнее имя было «Hidden / ColorSplit». Если Hidden находится в пути шейдера, он не будет отображаться в выпадающем списке шейдеров на наших материалах. Обычно это полезно для шейдеров постэффектов. Поскольку мы никогда не применим их к 3d-объекту, они не будут загромождать выпадающий список. Но сейчас я его вывел, чтобы мы могли сразу же протестировать наш шейдер. Если вы создадите новый материал и поместите этот материал в меш с кодом шаблона по умолчанию, вот что вы получите.
Вот так выглядит постэффект, но на всем экране. И раз мы тут собрались, мы можем пойти дальше и выпустить шейдерный код! Давайте разделим эти цвета!
Разделение цвета (Color Split)
Сам шейдер будет довольно простым. Мы сэмплируем основную текстуру три раза с немного отличающимся смещением для координат, затем объединяем красный канал первого сэмпла, зеленый канал второго и синий канал третьего вместе для окончательного результата.
Добавьте _ROffset, _GOffst, и _BOffset свойства наверху, затем объявите их в подшейдере.
И функция frag выглядит примерно так
Мы делали такие вещи раньше! Без труда! Сохраните и взгляните снова в Unity. Теперь у вас будет куча векторных полей в редакторе материалов.
Поиграйте со значениями, и вы получите вот такие крутые результаты!
Вы обнаружите, что лучшие результаты дают небольшие значения, потому что 1 означает полный размер текстуры. Кстати, такого рода эффект разделения цвета часто называют «хроматической аберрацией», потому что он очень похож на реальную.
Ура, шейдер готов! Вы можете удалить свой тестовый объект, если хотите. Теперь выясним, как получить эту вещь на камеру.
В жизни случается и блитирование
Обычно шейдер получает основную текстуру от материала, но камеры не имеют средств для визуализации мешей или прикрепленных к ним материалов, поэтому нам нужен сценарий для передачи изображения с камеры шейдеру, в качестве текстуры.
В ColorSplit.cs, избавьтесь от всех верхних записей, кроме using UnityEngine; и добавьте эти атрибуты в верхней части объявления класса
[ImageEffectAllowedInSceneView, ExecuteInEditMode]
Атрибут ImageEffectAllowedInSceneView означает, что как только эффект изображения заработает, он будет применен одновременно в режиме игры и при просмотре сцены. Для таких эффектов, как разделение цвета, очень удобно, когда способ размещения источников света и других объектов может зависеть от того, как все будет выглядеть на конечном изображении. И обычно надо исключать эффекты, которые сильно искажают экран. Я предполагаю, что вы будете ответственно и со вкусом использовать ColorSplit! Не заставляйте меня отзывать ваши привилегии ImageEffectAllowedInSceneView!
ExecuteInEditMode приводит к тому, что скрипт выполняется в режиме редактирования. Эффекты изображения могут работать без этого, но мы будем вызывать некоторые функции, которые не будут работать без него.
Теперь мы объявим некоторые переменные.
В классах, которые наследуются от MonoBehaviour в Unity (как этот), публичные переменные по умолчанию отображаются как поля в инспекторе, а закрытые переменные — нет. Вы можете добавить атрибуты к отдельным переменным, чтобы переопределить это, но не сегодня. Закрытые переменные будут настроены в коде. Мы будем использовать их как домики, чтобы припрятать кое-какие постоянно нужные вещи.
Затем добавьте этот метод
OnPreCull будет вызываться до того, как камера начнет что-либо рисовать. Мы используем эту функцию, чтобы получить то, что нам нужно, если у нас его еще нет. Проще говоря, мы говорим: если у нас нет камеры, возьмите камеру. Если у нас нет шейдера, возьмите шейдер. Если у нас есть шейдер, и у нас нет материала, создайте новый материал с прикрепленным к нему шейдером.
Затем добавьте OnDisable ()
Это СУПЕР важно. Если вы создаете эффекты изображения без этого, вы рискуете однажды сломать программу. Объясняю: мы говорим Unity создать новый материал для данного эффекта изображения, но из-за некоторых сложных правил, стоящих за всеми этими «дырами», которые мы резервируем, нам также нужно убедиться, что мы очистим материал, когда закончим. Иначе мы бы в конечном итоге зарезервировали все доступное пространство под дыры только для старых материалов, которые нам больше не нужны, и вызвали сбой из-за недостатка памяти.
(Если вы приступаете к созданию сложных многопроходных эффектов, использующих несколько текстур рендеринга, не удивляйтесь, если увидите такое же много раз 😩) Это не особо большой риск для того, к чему мы сейчас стремимся, но лучше обезопасить себя и не потерять работу из-за аварии!
Мы немного говорили о директивах прекомпилятора в C # в прошлый раз, но напомню, что мы не можем иметь код Unity Editor вне сборки Editor, не вызывая проблем со сборкой, если только он не находится внутри одного из блоков #if UNITY_EDITOR. И причина, по которой нам надо использовать код редактора, заключается в том, что способ очистки пространства, зарезервированного для материала, зависит от того, в игре мы или нет. Если мы в игре, нам надо использовать «Destroy», а если нет — «DestroyImmediate».
Вы можете заметить, что если вы запустите игру и вызовете появление врагов, то когда вы остановите игру, появившиеся враги будут очищены. Но если вы скопируете врага в Иерархии, когда игра не запущена, он останется (до тех пор, пока вы сохраняете сцену!). Аналогично, если вы используете Destroy во время игры, чтобы избавиться от монстров, которых убил ваш герой, реальные игровые объекты монстров все еще будут существовать, когда игра завершена. DestroyImmediate удаляет вещи из файлов. Поскольку материал, который мы создаем для этого эффекта изображения, является временным, если игра не запущена, мы хотим уничтожить его по-настоящему.
Я знаю, что это звучало довольно сложно, но вы поблагодарите меня позже / пожалеете, что не выслушали после крэша Unity пятый раз за день.
В любом случае, приступим к волшебству!
Добавьте функцию OnRenderImage в ваш скрипт. Вы заметите, что получаете две вещи: источник и назначение RenderTexture. RenderTexture — это попросту текстура, которую Unity создает в коде во время работы. Здесь источник source — это то, что на самом деле увидела камера, а назначение destination — то, что камера собирается передать, как если бы это было то, что она увидела.
if (cam == null || material == null) Graphics.Blit(source, destination);
Прежде всего, если у нас нет камеры или нет материала, мы просто блитируем источник к месту назначения. Блитируем? Что еще за блитирование? Это жаргон графического программирования, он значит взять изображение, которое у нас есть, и переместить его из одного уютного местечка в другое. Есть сложные технические причины, почему это делается так, но нам сейчас все равно. Вы можете думать об этом как о штампе для картинок в играх.
Особенно приятно, что Graphics.Blit можно использовать и просто чтобы штамповать изображение из одного места в другое, и для блитирования с материалом. Поэтому вы фактически штампуете окончательный цвет из шейдера. Я полагаю, что в данной аналогии это похоже на то, как раскрашивать оттиск перед печатью, но я не знаю, я не ремесленник.
Итак, если мы блитируем источник к месту назначения, мы просто говорим, что у нас будет то, что на самом деле увидела бы камера, и камера говорит, что так оно и есть. Ухты.
И если у нас ЕСТЬ камера, и ЕСТЬ материал, мы можем установить свойства шейдеров на основе того, что помещаем в инспектор и блитируем, но с материалом, к которому тоже прикреплен наш шейдер.
material.SetVector(“_ROffset”,
redOffset);
material.SetVector(“_GOffset”, greenOffset);
material.SetVector(“_BOffset”, blueOffset);
Graphics.Blit(source, destination, material);
Как только вы все это получите, сохранитесь и посмотрите в Unity. Изменяйте значения в инспекторе и посмотрите, что получите.
Оно работает! Теперь все, что осталось сделать, это расположить камеру где-нибудь в хорошем месте, чтобы вы могли сделать хороший скриншот!
Компонент эффекта изображения, который мы создали сегодня, прикреплен как ColorSplit.cs, а шейдер — как ColorSplit.shader. Если у вас есть какие-либо вопросы или вы хотите поделиться тем, что придумали, дайте автору знать в Twitter или в Discord. И если этот урок помог вам, вы могли бы стать покровителем автора в Patreon.
Это руководство лицензировано на условиях международной лицензии Creative Commons Attribution-NonCommercial-ShareAlike4.0. Код, используемый в этом руководстве, распространяется под международной лицензией CreativeCommonsAttribution 4.0.
Источник: https://www.patreon.com/posts/31597594