Як розробити візуальні ефекти для 2D-гри за допомогою шейдерів. Підходи та приклади

Усім вітаннячко, мене звати Віталій Янчук. Я Unity-розробник і Tech Artist у компанії SUITSME, що входить до екосистеми бізнесів Genesis. Обʼєднавши геймінг і фешн, ми створили інтерактивну платформу для всіх, кого цікавить мода. У застосунку SUITSME кожен може поекспериментувати з одягом та аксесуарами від відомих брендів і стилізувати свого аватара для участі в різноманітних івентах. Усе це побудоване на основі 2D-графіки.

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

Ця стаття буде корисною для VFX-художників-початківців, розробників усіх рівнів та інших спеціалістів, які цікавляться комп’ютерною графікою.

Почнемо з основ

Одного дня я поставив собі завдання освіжити візуальний стиль продукту і створити кілька ефектів для тканин, які додали б шарму статичним речам.

Базовий ефект для тканини має такий вигляд:


Ще кілька варіацій цього ефекту в готових образах:


Базові моменти

Перед тим як потрапити на екран, всі дані й текстури проходять певний шлях: спочатку на процесорі комп’ютера (CPU), а потім на графічному процесорі (GPU). Цей шлях називається графічним конвеєром, або ж Graphics Pipeline.


У найбільш загальному вигляді вхідні дані (геометрія, текстури, колір) спочатку обробляються на рівні застосунку в CPU, за інструкціями, написаними C++ чи будь-якою іншою мовою. Потім ці дані потрапляють до графічного процесора. Там спочатку обробляються вершини усіх геометричних фігур, а потім ця геометрія попіксельно зафарбовується (проходить растеризацію). Растеризація та обробка геометрії відбуваються так, як це скаже зробити шейдерний код — код для GPU (зазвичай написаний на HLSL або Cg).

GPU не дає змоги ефективно проводити складні логічні операції, проте здатний робити масивні паралельні розрахунки.

Для кожної стадії на GPU є свій шейдер:

  • вертексний (Vertex Shader) — для обробки геометрії, масиву вершин (вертексів) фігур;
  • фрагментний (Fragment Shader) — для обробки окремих пікселів і текстур.

Сьогодні розглянемо лише Fragment Shader. Саме на цій стадії прорахована вся геометрія, і маніпуляція з графікою відбувається лише на рівні текстур і кольорів.

Розуміння текстурних координат. Контроль форми та розташування

Кожен піксель на текстурі має свої координати в UV-просторі (аналогічно до XY-координат) від 0 до 1 з кожної з координат в UnityEngine, починаючи з нижнього лівого кута.


Додавання векторної константи до UV відповідає за зміщення текстури. Якщо додати до U-координати UV-простору величину, що рівна 0.5, то текстура зміститься на половину своєї довжини вправо. Прив’язавши це до таймера, отримуємо латеральний рух текстури (далі буде приклад).


Множення UV відповідає за масштабування текстури. Якщо помножити V на 2, текстура звузиться вдвічі по вертикалі й у той самий квадрат вміститься вже дві такі текстури.


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

Для закріплення покажу один цікавий ефект, що імітує рефракцію від гарячого повітря. Його можна використати й для зображення об’єктів під хвилями води.

Він складається з двох шарів:

  • зсув текстури шуму (додаємо векторну константу до UV-координат текстури шуму за таймером, у нас це вектор U=1 V=0 (1, 0), помножений на час одного кадру. Це означає, що текстура зміститься вправо на один період за одну секунду ігрового часу);
  • додавання отриманого результату до UV-координат іншої текстури.

Це має такий вигляд:


Використана текстура не зовсім проста. Це процедурно згенерований градієнтний шум, тому при анімації чи зміщенні він не повторює себе й водночас не має швів.

Якщо ж текстуру шуму розтягнути й трішки зменшити інтенсивність, отримаємо більш м’який ефект, що схожий на розвіювання тканини.


Для розтягнення застосовуємо множення UV-координат.

Ефект для тканини майже готовий, але треба подбати про деталі. Тканина — складний матеріал, і при утворенні на ній хвиль змінюється її освітленість.

Розуміння кольорових координат. Контроль кольору

Крім контролю координат текстури, фрагментні шейдери допомагають контролювати колір кожного пікселю, що видно на екрані.

Майже кожен монітор комп’ютера показує колір як комбінацію червоного, зеленого та синього різної яскравості — RGB-формат. Тому й на графічному процесорі кожен піксель описується комбінацією трьох кольорів з яскравістю від 0 до 1.

Згадуємо, як працює додавання цих кольорів один до одного:


Якщо ви працювали до цього у Photoshop, то, ймовірно, знаєте різні режими змішування шарів, або ж Blend modes.

Режимів змішування багато, але всі вони містять хоча б одну з чотирьох базових операцій:

  • додавання кольорів (режим змішування Add або Linear Dodge) ARGB+BRGB — корисне для засвітлення без змін контрасту;
  • віднімання кольорів (режим змішування Subtract) ARGB-BRGB — корисне для затемнення з контрастом;
  • множення кольорів (режим змішування Multiply) ARGB*BRGB — корисне для затемнення без змін контрасту та маскування зображень;
  • ділення кольорів (режим змішування Divide) ARGB/BRGB — корисне для засвітлення з контрастом.

Кілька прикладів однієї і тієї ж маски в різних режимах:


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

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

Ось що відбудеться, якщо застосувати анімовану маску до текстури із зірочками в режимі Multiply:


Маска є текстурою, тож її також можна анімувати, деформувати й накладати на неї додаткові шари масок.

Якщо маніпулювати лише кольором і застосовувати маски для кольорів, зображення набуває магічного вигляду:


Повернемося до нашого незавершеного ефекту розвіювання тканини:

  • засвітимо ділянки на вершині хвилі;
  • затемнимо ділянки внизу хвилі.

Ці дві дії допоможуть спрощено передати світло і тінь. Для більш коректного зображення варто змінювати освітленість не від висоти хвилі, а від кута нахилу поверхні хвилі. А якщо мова про кут нахилу, то це — математична похідна. Тож якщо ми продиференціюємо текстуру за координатою U (ddx), то отримаємо більш фізично коректну картину зміни освітленості поверхні.


Згори — маска для засвітлення, знизу — маска для затемнення.

Це лише маленька частина того, що можна зробити. Важливо не перестаратись і намагатись робити ефект так, щоб він не виходив з контексту, відповідав стилю і був доцільним. Рекомендую ще прочитати про 12 принципів анімації.

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

Кожен шейдерний ефект у 2D можна розглянути як текстуру, що є результатом поєднання інших текстур за певною логікою. Тому можна нескінченно накладати ці текстури одна на одну, створюючи ще більше унікальних поєднань.

Для 3D-графіки працюють інші техніки, не менш цікаві. Наступного разу буду радий розповісти про це.

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

👍ПодобаєтьсяСподобалось11
До обраногоВ обраному1
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

Мало що зрозумів, але цікаво :) Дякую!

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