Туториал по созданию эффекта сел-шейдинга в Unity
Поэтапный рассказ с примерами кода.
Эффект сел-шейдинга используется для стилизации графики под мультфильм — благодаря этому 3D-объекты выглядят как 2D-спрайты. Эта техника рендеринга стала популярной после нескольких крайне успешных игр, к примеру, Jet Set Radio и The Legend of Zelda: The Wind Waker.
Независимый разработчик Эрик Ройстен Росс в 2018 году в собственном блоге опубликовал туториал, в котором подробно рассказал, как реализовать эффект сел-шейдинга в Unity. Мы выбрали из текста главное.
Основной референс в этом туториале — The Legend of Zelda: Breath of the Wild.
Сначала скачайте стартовый проект и откройте его в Unity. Этот файл содержит простой шейдер — по умолчанию текстура окрашена в синий цвет.
Направленный источник света
Если дело касается шейдеров, которые взаимодействуют с освещением, то обычно используются Surface Shaders — они позволяют автоматизировать взаимодействие объекта со светом и глобальным освещением. Но в нашем случае шейдер будет взаимодействовать только с направленным источником светом, поэтому в Surface Shaders нет необходимости.
Первым делом нужно сделать так, чтобы шейдер получал данные об освещении. Добавьте следующий код в верхней части Pass — сразу после фигурной скобки.
Первая строка запрашивает данные освещения для передачи в шейдер, а вторая фильтрует всё кроме направленного источника света.
Чтобы рассчитать освещение, нужно использовать общую модель затенения Блинна-Фонга, а также настроить дополнительные фильтры, чтобы придать шейдеру мультяшный вид. Теперь нужно рассчитать количества света, которое падает на поверхность от направленного источника — оно пропорционально нормали поверхности относительно направления света.
Шейдер должен учитывать данные нормалей, поэтому потребуется следующий код.
Нормали в appdata заполняются автоматически, в то время как значения в v2f должны быть введены вручную в vertex shader. Также нужно преобразовать нормаль из object space в world space, поскольку направление света связано именно с world space, Добавьте следующую строку в vertex shader.
Теперь нормаль можно сопоставить с направлением света при помощи скалярного произведения.
Скалярное произведение сравнивает два вектора и выдаёт простое число, основываясь на том, под каким углом они находятся по отношению друг к другу. Если векторы параллельны, то число равно 1, если перпендикулярны, то 0. Когда угол между векторами больше 90, скалярное произведение будет отрицательным. Добавьте в шейдер следующий код.
Теперь сфера выглядит более реалистично. Чтобы придать ей мультяшности, нужно разделить освещение на две зоны: светлую и тёмную.
Рассеянный свет
Следующий шаг — добавление рассеянного света, который отражается от поверхности объектов и рассеивается в атмосфере. В нашем случае этот свет будет воздействовать на все поверхности одинаково.
Чтобы можно было менять интенсивность или цвет направленного света, нужно добавить ещё одну часть кода.
Пора смягчить границу между светом и тенью. Для этого используется функция smoothstep: у неё есть три значения — нижняя граница, верхняя граница и значение между ними. Важно понимать, что функция smoothstep не линейная: в значениях от 0 до 0,5 она «усиливается», а в значениях от 0,5 до 1 — «ослабляется».
Отражения
Следующий шаг — добавление отражений: они зависят от угла, под которым на него смотрят. Поэтому нужно рассчитать world view direction в vertex shader.
Теперь мы переходим к реализации модели отражений Блинна-Фонга. Этот расчёт учитывает два свойства поверхности: цвет отражения и то, насколько хорошо поверхность отражает свет.
Сила отражения в модели Блинна-Фонга определяется как скалярное произведение между нормалью поверхности и half-вектором — вектором между углом обзора и источником света. Чтобы получить half-вектор, нужно суммировать эти два вектора, а затем нормализовать результат.
Функция pow контролирует размер отражения. Также нужно умножить NdotH на lightIntensity, чтобы гарантировать, что отражения появляются только в том случае, если поверхность освещена.
Затем нужно ещё раз использовать smoothstep для усиления отражения, а также умножить конечный результат на _SpecularColor.
Контровое освещение
Контровое освещение обычно используется для имитации отражённого света или источника света, находящегося за предметом. Это особенно важно для сел-шейдинга, так как помогает объектам не сливаться с фоном.
Само контровое освещение можно определить как поверхность, которая обращена в сторону от камеры. Чтобы вычислить это освещение, нужно взять скалярное произведение нормали и направления камеры, а затем инвертировать его.
Чтобы усилить эффект мультяшности, нужно установить пороговое значение с помощью smoothstep.
Так как этот вариант контрового света наблюдается вокруг всего объекта, возникает ощущение, что это обводка. Теперь нужно сделать так, чтобы контровой свет был только на освещённой части объекта.
С помощью функции pow можно масштабировать контровой свет.
Тени
Финальный шаг — добавление возможности отбрасывать тени. Поставьте следующий фрагмент перед Pass.
Намного сложнее реализовать возможность, при которой тени будут пад��ть на сам объект. Для этого нужно сделать так, чтобы шейдер «знал», когда объект попадает в тень — нужно перенести координаты текстуры из vertex shader в fragment shader.
Для реализации этого, нужно добавить Autolight.cginc — файл, содержащий несколько макросов, которые используются для определения теней. SHADOW_COORDS (2) генерирует значение для четырёх измерений с меняющейся точностью и присваивает его семантике TEXCOORD по заданному индексу (в нашем случае 2).
TRANSFER_SHADOW преобразует пространство вершины в пространство теневой карты, а затем сохраняет его в SHADOW_COORD.
Но перед этим нужно убедиться, что шейдер адекватно реагирует на разные условия освещения. Unity поможет справиться с этим — есть несколько вариантов решения этой задачи.
Это показывает Unity, что нужно скомпилировать все варианты, необходимые для рендеринга. Теперь можно выбрать значение на карте теней и применить его к расчёту освещения.
В результате получается эффект сел-шейдинга, имитирующий 2D-стиль рисования.