Поговорим о шейдерах, их проектировании и Unity
Всем привет. Меня зовут Дядиченко Григорий, я CTO Foxsys, а ещё я люблю рендер. Хочется поговорить сегодня о том, как проектируются шейдеры, как идёт процесс их разработки и на что обращается внимание. В первую очередь я буду рассказывать про свой опыт и техники, которые я использую при проектировке и/или постановке задач на написание шейдеров. Если вам интересна данная тема — добро пожаловать под кат!
Я решил не дописывать серию статей «Shader — это не магия» потому что во-первых, мне не понравилась структура, которую я выбрал для серии. Да и во-вторых там материала на целую книгу, в которую нужно добавлять больше информации на тему того, как работают гпу, в чём особенности мобильных gpu и т.п. Но тем не менее после пары статей из серии было много вопросов: «А как придумываются эти ваши шейдеры?» То решено было вынести в отдельную тему. Об этом сегодня и поговорим.
Разработчик шейдеров != Технический художник
Важно понимать тот факт, что рассматриваю я именно проектировку оптимальных шейдеров. Придумывают как будет выглядеть конкретный взрыв, волна, поток энергии — технические художники или VFX художники. Это экспертиза на стыке дизайна и математики, так как для построения красивого потока частиц нужно на достаточном уровне знать хотя бы диффуры. Можно делать эффекты и чисто дизайном без углубления, но конечно это не позволяет ничего сделать реалистично, а не на глаз. Делается это в разном софте, иногда прямо в Unity в шейдер графе, либо в другой нодовой системе и более специализированном софте для этого типа Houdini.
В ряде вариантов продакшена этого в целом достаточно. Допустим на десктопных платформах, когда у тебя в доступе целая карточка от Nvidia многие вещи вообще не требуют некоего трюкачества и глубокого понимания в рендере и в оптимизации. По сути разработка оптимальных шейдеров с пониманием платформы — это экспертиза рендер разработчика. Точнее одна из экспертиз, так как не одними шейдерами мир полон. Но в целом любой Unity разработчик по хорошему должен и так знать теорию о графическом конвейере, о том как работает отрисовка графики на экране и т.п. И уметь писать хотя бы базовые шейдеры.
Воруй как художник
Итак, я не дизайнер и рисую только первый шаг совы. Как делать эффекты то? В общем то ответ простой — искать референсы. Их можно искать в природе, либо уже разработанные кем-то визуальные концепты, анимации и прочее.
Для разминки я бы в целом предложил довольно простое упражнение. Подобное есть у художников, только тут оно немного сложнее. Делать базовые материалы. Фарфор, акрил, стекло, воск, грунт, кафель, пластик, металлы и так далее. Материалов с разными физическими и оптическими свойствами существует очень много. Но не в стиле подкрасил и натянул на Standard шейдер текстурки с https://textures.com/ и готово. А размышляя о том, как это сделать в рамках ограничений.
Допустим поставить себе ограничение, что шейдер обязательно должен быть однопроходным. Можно сказать, что в данном случае ряд эффектов невозможным, но это не совсем правда. Из перечисленного выше ряд эффектов становится невозможным если делать «честно», по математической модели материала или физического явления. А тут нужно думать «на что это похоже» и «где я могу выиграть в производительности».
В целом если вы хотите уметь создавать эффекты от и до, то лучше обладать некоторой арт экспертизой, чтобы делать что-то совсем новое и уникальное. Но в целом, на мой взгляд, основная задача разработчика реализовывать безумные требования художников/геймдизайнеров/дизайнеров/бизнеса.
Любому хорошему повару нужны заготовки
Многие сложные эффекты — это комбинация довольно простых. По сути что такое тот же самый пластик? Определённая степень отражения, эффект Френеля + цвет в базовом случае. При тонком пластике можно ещё добавить подповерхностное рассеивание, чтобы он был просвечиваемым. Тот же самый фарфор или восковая свеча им обладает почти всегда.
Поэтому по любой материал, эффект или явление можно разобрать на набор составляющих его компонент и разработать оптимальное решение, которое похоже на данный компонент в рамках вашей задачи. Просто скажем сингл пасс шейдер не единственное интересное ограничение, может у вас 2.5D игра и в этом случае многие эффекты делаются проще. Или вам не нужно супер реалистичное подповерхностное рассеивание, то тогда вам подойдёт просто похожий на него эффект, который ± ничего не стоит. Например в данной статье был неплохой вариант https://habr.com/ru/post/337370/ Или вы знаете размещение источников света в сцене. В каждом конкретном случае упрощающих задачу ограничений может быть очень много вплоть до знания особенностей геометрии.
Возьмём для ещё одно примера эффект качающихся деревьев. Для простоты дерево — это ёлка с ветками, одно из требований к дереву — вертикальный ровный ствол. В ТЗ для 3д моделлеров стоит всего одно дополнительное требование, сделайте пивот 3д модели внизу. Тогда исходя из этого знания можно сделать функцию колебаний вертексов дерева в зависимости от высоты и это будет выглядеть достаточно правдоподобно.
Ещё можно заметить что у деревьев (просто посмотрев видео с ветром) ветки качаются не только по горизонтали, но и по вертикали — следовательно можно так же написать уравнение окружности и задать колебания синусоидой в зависимости от удалённости от центра ствола. Нужно только задать требование, чтобы пивот был по центру ствола.
Идём на шаг глубже. Ветки в данном решении совершают синхронные колебания — это выглядит странно. Вместо синусоиды задаём уравнение плоской волны с фазой колебания зависящей от угла между вершиной, центром ствола, и вектором в плоскости XZ. Чтобы сделать дискретное распределение добавляем округление угла через то же самое интовое деление.
Либо второй более сложный вариант. В цвета вершин нашего дерева записываем вектора колебания, которые задают ось колебания, а допустим в альфа канал — фазу колебаний. А длинна вектора отвечает за амплитуду и частоту колебаний. Чтобы вектор (0,0,0) не колебался и чёрным цветом получается у нас будут закрашены вершины ствола. И получаем более сложно сочинённые колебания конкретных веток в конкретном дереве.
Это лишь пара простых примеров. Можно ещё задать параметр ветра в виде вектора и описать его упрощённую логику или полную. Но суть размышлений ± такова во многих случаях. Думаем на что это похоже из того, что можно сделать простыми средствами.
Но что же по поводу конкретных заготовок. Которые нужны любому «повару» отвечающему за разработку эффектов. Их очень много я перечислю лишь часть.
Шум https://github.com/keijiro/NoiseShader
Дисторшн https://catlikecoding.com/unity/tutorials/flow/texture-distortion/
Эффект Френеля https://gist.github.com/garzaa/226016958263fd57632d9e7c2ac589a5
Отражения
Градиенты https://github.com/Nox7atra/Unity-Figma-Gradients
Периодические функции https://habr.com/ru/post/435828/
Каустика https://www.alanzucconi.com/2019/09/13/believable-caustics-reflections/
Преломление (она же рефракция) https://lettier.github.io/3d-game-shaders-for-beginners/screen-space-refraction.html
И такой список можно продолжать очень долго. В чём отличие от идеологии нодовых редакторов шейдеров? Все ссылки приведены для примера, где можно посмотреть сами эффекты, но это далеко не единственная их реализация, да и подобных эффектов. Того же самого Френеля можно писать по разному, с помощью градиентов можно имитировать свет.
Нодовые редакторы — это прекрасный инструмент и им круто пользоваться. Мне он не нравится чисто за счёт скорости разработки. Так как визуальный интерфейс — это медленнее, чем любое хоть сколько-нибудь сложное математическое выражение написать кодом. Но с нодовыми редакторами надо быть аккуратным, и чтобы не появлялся минимум страшных спагетти шейдеров нужно создавать много кастомных нод с обобщёнными эффектами и математическими формулами. Иначе в визуале «умножение едет через умножение, умножением погоняя», где каждое умножение — это нода, будет очень легко запутаться. То что он не выдаёт неоптимальные шейдеры — это не так важно, так как тут простое правило «работает — не трогай». Нет смысла экономить на спичках и нано секундах, оптимизировать надо то, что тормозит и в чём есть проблема.
Реальные математические модели
Поговорили о трюках, а теперь давайте поговорим о хардкоре. Есть второй более сложный способ разрабатывать эффекты. Использовать готовые математические модели. И сложный он за счёт всем давно известной тайны «математика не нужна» и большая часть разработчиков в принципе не знают высшую математику. Но не все. Если вы знаете математику на высоком уровне, то подобные статьи ваш лучший друг. И для неё даже есть реализация на Unity.
И поверьте мне на слово, реализовано прям вот совсем не всё, очень далеко. Даже из техник 2010 года, которые довольно крутые. Но для порога входа в данную область нужно глубоко знать высшую математику. Особенно линейную алгебру, принципы решения дифференциальных уравнений численными методами, что такое СЛАУ и так далее. Ну и математический анализ и теорию функции комплексного переменного для определённых симуляций. В данной области разработки графических эффектов учебники и саенс директ ваши лучшие друзья.
Главное помнить, что это уже выход на уровень «гуру», и это далеко не обязательная вещь к изучению. Но с этим знанием и наличием бюджетов со временем можно творить сущую магию. Например в ААА проектах, потому что там и время и бюджет бывает что есть. Особенно на десктопе. Хотя на мобилках я тоже заводил пару интересных математических моделей в коммерческих проектах, но они к сожалению под NDA.
Инструменты для работы
На самом деле при разработке шейдеров и понимании что ты делаешь, есть ряд полезных инструментов, которые могут упростить жизнь:
https://textures.com/ — сток текстур, который можно использовать для теста гипотез и экспериментов.
https://www.desmos.com/calculator? lang=ru — простой визуальный калькулятор. Его я в основном использую для подбора периодической функции и её коэффициентов, чтобы сделать её более правдоподобным. Или же какую-нить математику взрывной волны.
https://www.wolframalpha.com/ — уже штука посерьёзнее для расчётов. Математику надо знать, чтобы понимать, что ты хочешь сделать. Уметь решать, чтобы запомнить, как оно работает. Но в реальном продакшене нет необходимости всё подряд решать руками
https://www.blender.org/ — замоделить я не смогу даже конус, так же как и натянуть сову на глобус. Но в целом в нём удобно задавать нужные вертексные параметры для конкретных эффектов, чтобы не ждать пока это сделают 3д моделлеры.
Спасибо за внимание! Надеюсь данная статья была вам интересна и полезна, а у меня может быть всё-таки дойдут руки выстроить нормальную структуру материалов по рендеру, чтобы уже написать детально о том, как шейдеры то сами по себе пишутся.