Магія кастомних шейдерів: як я перестав їх боятися

Вітаю, спільното!

Одразу зазначу, що це не тутоіал, а міні девлог.

Це авторський текст. Він народився не з натхнення ззовні, а з того стану захоплення, який з’являється під час роботи. Бо розробка ігор — це постійні відкриття, майже як у справжній науці.

Не впевнений, що мене вистачить на цілу серію, але вирішив почати з першої статті про шейдери. Раніше я свідомо оминав тему власних шейдерів: по-перше, здавалось, що це щось надто складне; по-друге, я був переконаний, що стандартні рішення покривають 99% графічних задач. Лише зараз стало зрозуміло, скільки можливостей я втрачав. Колись я навіть купував готові шейдери — наприклад, для ефекту привида за $10. Сьогодні це виглядає майже комічно, бо подібне робиться за кілька хвилин.

Скріншот з прикладом роботи купленого шейдераСкріншот з прикладом роботи купленого шейдера

Світ шейдерів відкрився для мене відносно нещодавно — і швидко став улюбленою частиною розробки. Якщо пояснювати просто, Unity Shader Graph — це щось середнє між спрощеним і ускладненим програмуванням. Деякі речі справді легше зробити кодом, але можливості, які дає Shader Graph, важко переоцінити.

Щоб не говорити абстрактно, покажу маленький приклад із поточної роботи. Зараз я роблю idle-гру, і для ключової механіки мені знадобилось коло з переривчастим контуром. Два місяці тому я вирішив це без шейдерів — просто намалював кілька варіантів спрайта під різні масштаби.

У грі відбувався банальний sprite swap: підставлявся той варіант, який був найближчим до потрібного масштабу. Просте рішення, але з типовими проблемами — розмиття, розтягування, обмеження. Учора я вирішив, що це вже виглядає несерйозно, і зробив власний шейдер, який малює таке коло в будь-якому масштабі — pixel perfect.

У студентські роки геометрія давалась мені легко, а от тригонометрія — значно гірше. Але за роки в геймдеві вона стала інтуїтивно зрозумілою. Далі — коротко про сам принцип.

Починаємо (як в 99% випадках) з UV. Це двовимірна розгортка зі значеннями від 0 до 1 — координатна система, яка каже шейдеру, де саме знаходиться піксель на текстурі. Розиваємо UV на 2 осі (X та Y) та отримуємо базові вертикальний та горизонтальний градієнти.

Наше завдання — рівномірно розбити коло на сегменти по периметру. Логічний перший крок — тригонометрія: синуси, косинуси, радіани. Беремо X і Y з UV, переносимо центр координат у середину, нормалізуємо значення.

Центром координат для нашого кола має бути центр UV розгортки тому нормалізуємо наші значення з формату від 0 до 1 до формату [1 0 1]. насправді нам підійде і [-1 0 1] але знак мінус в нашому випадку на результат не впливає а от shader graph всі занчення з межами 0-1 фарбує в чорний або білий відповідно і для нас це мешн інформативно.

Ми підготували вхідні дані й можемо переходити до базової тригонометрії. Маючи координати X та Y кожного пікселя відносно центру спрайта, можна визначити його кут відносно цього центру в радіанах. Для цього використовуємо atan2 (досі до кінця не розумію, як воно працює, але воно повертає кут у радіанах, який за потреби можна перевести в градуси).

На виході отримуємо щось дуже схоже на наші попередні X та Y відносно центру — але тепер це вже не лінійна залежність, а радіальна (математики, виправте мене).

Далі, маючи ці значення в радіанах, просто застосовуємо косинус — і на цьому етапі отримуємо щось, що виглядає як старий прапор Японії.

80% роботи вже зроблено. Залишилось лише зібрати все докупи — нормалізуємо цей «градієнтний прапор» до двох кольорів.

Проганяємо це через Saturate, тому що, попри зовнішній вигляд, значення насправді лежать не в межах 0–1 до яких має все звестись, а приблизно в діапазоні від —100 до 100. Нам це не підходить і призведе до зайвих артефактів.

Саме коло також можна було б побудувати через синуси та косинуси. Я навіть почав це робити в перші дві хвилини — поки не згадав, що в Unity Shader Graph є готова нода для малювання кола. Тож просто беремо з неї периметр.

Далі залишається по суті скласти коло з нашим «прапором Японії» — тобто перемножити їх так, щоб білими залишилися лише зони перетину цих двох «текстур».

Усі ці маніпуляції були потрібні лише для отримання маски для альфа-каналу. Очевидно, я опустив кілька технічних деталей, де додається:

  • анімація обертання
  • різні дрібні, але важливі нормалізації
  • фарбування

але в межах цього прикладу це не критично.

Комусь це може здатися занадто складним для простої задачі — і що простіше було залишитися на варіанті з кількома спрайтами з фотошопу та їх свапом. Можливо. Але зараз я швидше зроблю шейдер, ніж витрачатиму час на механічну роботу й зайві асети.

Я б радив не оминати тему власних кастомних шейдерів. Це значно простіше, ніж здається, і відкриває доступ до справжньої магії. Особисто мені це дало друге дихання в інді-геймдеві.

У цьому ж проєкті я працював і з більш складними речами — наприклад, із системами вогню, рідин та газів з використанням compute shader-ів. Можливо, колись опишу й цей досвід.

Дякую за увагу. Радію, що знайшов час це написати. Сподіваюся, комусь було цікаво.

Підписуйтеся на Telegram-канал @gamedev_dou, щоб не пропустити найважливіші статті і новини про геймдев

👍ПодобаєтьсяСподобалось3
До обраногоВ обраному0
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

Теж нещодавно відкрив для себе шейдери. Сильно спрощують життя і відкривають багато можливостей. Тим більше зараз їх можна писати дуже легко за допомогою АІ

Я сам кайфую від використання Shader Graph-a. Коли познав дзен, то багато крутого візуалу можна робити через шейдери, причому майже безкоштовно по перфомансу.

Підписатись на коментарі