Введение
Часто, начинающие художники по эффектам используют стандартные шейдеры для частиц, которые в случае мобильной разработки довольно ограничены по функционалу, и не всегда позволяют делать то что хочется, а порой даже вынуждают поступать неэффективно, например спавнить лишние частицы для создания объёма или рандомного дополнительного движения. Используя представленный в этой статье набор эффектов, можно добиться интересных вещей, некоторых из которых нельзя добиться частицами, при этом в ряде случаев даже сэкономить на производительности.
Всё описанное в данной статье относится к разработке эффектов на юнити, однако очень легко адаптируется и для других игровых движков, так как почти все эти шейдерные функции везде ведут себя одинаково. Для использования информации из этой статьи нужно иметь базовое представление о шейдерах и как минимум опыт работы с нодовыми редакторами.
Формат
Все эффекты представлены в двух вариантах:
- В виде дерева нодов (для тех кто не понимает код) :<
- В виде кода (для тех кто понимает) :>
В некоторых случаях эффект может быть представлен только в виде кода.
Также у каждого эффекта есть степень нагрузки по производительности:
- Безопасно (можно использовать в обычных частицах, и спамить почти столько же сколько и со стандартным мобильным шейдером)
- С осторожностью (стоит избегать множества крупных частиц, рекомендуется спавнить поштучно, либо хорошо контролировать их количество)
- Опасно (в мобильной разработке лучше лишний раз не использовать)
Важно понимать, что хотя эффекты и могут быть помечены как безопасные, большое их сочетание друг с другом может запросто вывести ваш шейдер в категорию используемого с осторожностью, а бездумный спам и вовсе в область опасного)
Во всех случаях я рассматриваю ситуацию, когда мы используем для эффектов чёрно-белую текстуру, которую в дальнейшем собираемся красить либо из материала, либо из системы частиц. Поэтому на этапах до подмешивания цвета я работаю с одним каналом текстуры, чаще всего это R канал.
Шейдерные эффекты
Gradient Mapping
Gradient Mapping - позволяет не заливать текстуру просто одним цветом, а использовать несколько и управлять их распределением из материала или кастом даты. В примере я разобрал два цвета из материала, однако чтобы не плодить миллион одинаковых цветных материалов, мы можем взять один "внешний" цвет из вертекс колора (системы частиц), а "внутренний" просто оставить белым, и тогда у нас самые яркие пиксели будут уходить в белизну, делая текстуру частиц живее.
float4 tex = tex2D(_MainTex, i.uv0.xy);
tex.rgb = lerp(_ColorOut.rgb, _ColorIn.rgb, saturate((tex.r - _GradientPosition) * _GradientHardiness)) * _EmissivePower * tex.r;
Производительность: Безопасно
Выше рассмотрен вариант градиент маппинга, поддерживающего разные цвета, но если мы решаем в ярких пикселях использовать только белый, то интерполяция становится не обязательной и эффект можно оптимизировать, приведя к виду:
Выше рассмотрен вариант градиент маппинга, поддерживающего разные цвета, но если мы решаем в ярких пикселях использовать только белый, то интерполяция становится не обязательной и эффект можно оптимизировать, приведя к виду:
float4 tex = tex2D(_MainTex, i.uv0.xy);
tex.rgb = _ColorOut.rgb * tex.r + saturate((tex.r - _GradientPosition) * _GradientHardiness));
Alpha Errosion
Alpha Errosion - это более интересный вариант фейда изображения, в котором изображение фейдится не равномерно, а от прозрачных пикслелей к непрозрачным. Это достигается за счёт того, что мы не умножаем альфа канал на число, а вместо этого вычитаем это число из него. Важно не забыть saturate, так как значения будут меньше нуля. Здесь две гифки для сравнения обычного фейда по альфе и фейда по альфа эррозии.
float4 tex = tex2D(_MainTex, i.uv0.xy);
float opacity = saturate(_ClassicAlpha * tex.a - _AlphaErrosion);
Производительность: Безопасно
Panner
Panner - это простое движение текстуры по мешу. Сочетая хорошие затайленные текстуры с разными геометрическими объектами порой можно заменить большой объём эффекта одной или двумя частицами, в которых всё движение заложено в текстуре меша. Например как на гифке волны огня и пыли сделаны всего 4-6 партиклами с паннер шейдером.
float2 uvpanner = i.uv0.xy + _Time.y * float2(_SpeedX, _SpeedY);
float4 tex = tex2D(_MainTex, uvpanner);
Производительность: Безопасно
В случае реализации через код, расчёты UV можно вынести в вертексный шейдер, сделав эффект почти что бесплатным.
В случае реализации через код, расчёты UV можно вынести в вертексный шейдер, сделав эффект почти что бесплатным.
Fresnel
Fresnel - это наложение эффекта в зависимости от угла под которым камера смотрит на меш. Его можно использовать чтобы выделить либо наоборот увести в прозрачность грани скруглённых геометрических объектов.
Из вертексного шейдера передаём нормаль и вектор взгляда в пиксельный шейдер.
o.normalDir = UnityObjectToWorldNormal(v.normal);
float4 world = mul(unity_ObjectToWorld, v.vertex);
o.view = normalize(_WorldSpaceCameraPos.xyz - world.xyz);
--
Это код в пиксельном шейдере для варианта с подсветкой. В случае с прозрачностью нам нужно будет умножить на френель альфа канал.
float4 tex = tex2D(_MainTex, i.uv0.xy);
float fresnel = dot(i.normalDir, i.view);
fresnel = saturate((fresnel - _FresnelRadius) * _FresnelHardiness);
tex.r = tex.r + (1.0 - fresnel);
Производительность: Безопасно
В случае реализации через код, можно вынести расчёт Френеля в вертексный шейдер, сделав его практически бесплатным, однако в этом случае качество эффекта будет зависеть от количества полигонов модели.
В случае реализации через код, можно вынести расчёт Френеля в вертексный шейдер, сделав его практически бесплатным, однако в этом случае качество эффекта будет зависеть от количества полигонов модели.
Polar Coordinates
Polar Coordinates - это сворачивание текстуры в полярные (круговые) координаты, которые позволяют делать движение текстур из центра наружу и по окружности. Также с небольшими дополнениями позволяет сжимать текстуру к центру, либо растягивать её. Также позволяет скручивать текстуру в воронку. Тут стоит понимать, что подобный визуал можно получить и паннером, наложенным на меш-диск, и в этом случае эффект будет заметно легче. Полярные координаты стоит использовать только если хочется эффект со специфичной маской, или настройками сжатия и скручивания воронки.
float2 remapuv = i.uv0.xy * 2.0 - 1.0;
float l = length(remapuv);
float a = _ScrewPower * l + (atan2(remapuv.x, remapuv.y) / 6.28318530718 + 0.5);
a = a + _Time.y * _SpeedRotation;
l = pow(l, _Compression);
l = l + _Time.y * _SpeedExtrusion;
remapuv = float2(l, a);
float4 tex = tex2D(_MainTex, remapuv);
Производительность: С осторожностью
Этот эффект состоит не только из арифметики, но и из таких штук как арктангенс и длина, которыми спамить по экрану уже не рекомендуется. Вынести в вертексный шейдер тут также ничего уже не выйдет. Однако мы можем провести некоторые оптимизации, которые хоть и накладывают ограничения, но позволяют нам вывести уровень до условно безопасного) Например заменить операцию арктангенса на текстуру высокого разрешения с отключенными мип мапами, а из расчёта длины убрать квадратный корень (нам важна картинка а не точность), степень заменить интерполяцией lerp(x, x*x*x, value).
Этот эффект состоит не только из арифметики, но и из таких штук как арктангенс и длина, которыми спамить по экрану уже не рекомендуется. Вынести в вертексный шейдер тут также ничего уже не выйдет. Однако мы можем провести некоторые оптимизации, которые хоть и накладывают ограничения, но позволяют нам вывести уровень до условно безопасного) Например заменить операцию арктангенса на текстуру высокого разрешения с отключенными мип мапами, а из расчёта длины убрать квадратный корень (нам важна картинка а не точность), степень заменить интерполяцией lerp(x, x*x*x, value).
Liquify
Liquify - эффект расслоения текстуры, который хорошо подходит для визуализации жидкостных и энергетических эффектов. Случайно обнаружил этот эффект во время экспериментов) Расслоение возникает при значениях Liquify Power больше 1, таким образом можно сделать интересное взаимодействие нескольких текстур, как на зелёном эффекте на гифке.
float4 tex = tex2D(_MainTex, i.uv0.xy);
float liq = tex.r * 2.0 * _LiquifyPower - 1.0;
liq = saturate(1.0 - abs(liq));
Производительность: Безопасно
Desaturate
World Coordinates
World Coordinates - маппинг текстуры по мировым координатам. На гифке показан процедурный кружок, который используется в качестве маски для текстуры в мировых координатах. Тут к сожалению нормально показать реализацию можно только кодом, в нодовом редакторе есть только в качестве готовой ноды. Также реализация этого эффекта может отличаться в других движках.
Расчитываем мировые координаты в вертексном шейдере и передаём в пиксельный.
float4 world = mul(unity_ObjectToWorld, v.vertex);
o.uv0.xy = world.xz;
--
Читаем текстуру в пиксельном шейдере.
float4 tex = tex2D(_MainTex, i.uv0.xy);
Производительность: Безопасно
Целиком рассчитывается в вертексном шейдере.
Целиком рассчитывается в вертексном шейдере.
Screen Coordinates
Screen Coordinates - маппинг текстуры в плоскости экрана. Если вы видели в играх эффекты где открывается портал или дыра в космос и у неё фон из звёзд движется вместе с экраном, то это скорее всего именно эта тема) Тут к сожалению нормально показать реализацию можно только кодом, в нодовом редакторе есть только в качестве готовой ноды. Также реализация этого эффекта может отличаться в других движках.
В вертексном шейдере передаём экранные координаты в пиксельный через float4 screenpos
o.pos = UnityObjectToClipPos(v.vertex);
o.screenpos = ComputeScreenPos(o.pos);
COMPUTE_EYEDEPTH(o.screenpos.z);
--
Читаем в пиксельном.
float2 screenuv = i.screenpos.xy / i.screenpos.w;
float4 tex = tex2D(_MainTex, screenuv);
Производительность: Безопасно
Строку float2 screenuv = i.screenpos.xy / i.screenpos.w; можно перенести в вертексный шейдер, однако в этом случае возможно неточное наложение текстуры и даже разные артефакты. Но при некоторых ракурсах камеры их незаметно и если камера проекта позволяет, этим можно смело пренебречь в пользу дополнительной оптимизации.
Строку float2 screenuv = i.screenpos.xy / i.screenpos.w; можно перенести в вертексный шейдер, однако в этом случае возможно неточное наложение текстуры и даже разные артефакты. Но при некоторых ракурсах камеры их незаметно и если камера проекта позволяет, этим можно смело пренебречь в пользу дополнительной оптимизации.
Flow Map
Refraction
Refraction - это эффект искажения изображения сцены. Чаще всего им пользуются для визуализации искажений от горячего потока воздуха от пламени, либо искажения света проходящего рядом с чёрной дырой. Достигается за счёт того, что в шейдере считывается изображение экрана за мешем и затем читается как текстура в экранных координатах. Искажения вносятся в эти координаты по принципу флоу мапы. Здесь для демонстрации использован GrabPass. Насколько мне известно реализация этого эффекта будет разной в зависимости от выбора Render Pipeline, или от выбора игрового движка. Если немного поразмышлять над этим эффектом, то можно догадаться, что принцип в основе может быть использован не только для искажений, но и например для частиц, которые обесцвечивают всё за собой, или применяют другие разные эффекты)
В блоке SubShader вызываем GrabPass, так как текстура не указана, то результат будет сохранён в _GrabTexture.
GrabPass{ }
--
В вертексном шейдере передаём экранные координаты в пиксельный через float4 screenpos
o.pos = UnityObjectToClipPos(v.vertex);
o.screenpos = ComputeScreenPos(o.pos);
COMPUTE_EYEDEPTH(o.screenpos.z);
--
Читаем в пиксельном.
float2 screenuv = i.screenpos.xy / i.screenpos.w;
float4 refractionmap = tex2D(_RefractionMap, i.uv0.xy);
refractionmap.rg = sceneUVs + refractionmap.r * _RefractionPower;
float3 result = tex2D(_GrabTexture, refractionmap.rg).rgb;
Производительность: Опасно
В мобильной разработке используется редко из-за проблем с производительностью. Плюс ко всему, эти проблемы усугубляются плохой совместимостью с батчингом.
В мобильной разработке используется редко из-за проблем с производительностью. Плюс ко всему, эти проблемы усугубляются плохой совместимостью с батчингом.
Демонстрация
Сами по себе эти эффекты интересны, но действительно крутые вещи получаются при их сочетании. Например читатель может себе попробовать представить сочетание двух текстур с полярными координатами которые смешиваются через эффект Liquify и затем фейдятся альфа эррозией) Таким образом можно сделать очень красивую живую взрывную волну, или лужу жидкости)
В качестве примера эффект, в котором используется подложка с нойзом свёрнутым полярными координатами, на который наложена умножением маска с узором. Вокруг движутся трейлы с паннером и вытянутой маской текстурой)
Полезные ссылки
Справочник по стандартным шейдерным функциям.
Учебник по шейдерам и процедурной генерации в них.
Галерея в которой выкладывают разные шейдеры, тут можно подсматривать разные эффекты и хитрости с оптимизацией.
Учебник по шейдерам и процедурной генерации в них.
Галерея в которой выкладывают разные шейдеры, тут можно подсматривать разные эффекты и хитрости с оптимизацией.
Эта статья написана не программистом для не программистов, поэтому тут могут присутствовать некорректные термины и неточности, с этим вопросом буду рад, если немного помогут)
Ред. Empyreal
Ред. Roy Mustang
Можешь почекать его модельки