Розробка

Створення інтерактивної трави в Unreal Engine


До недавнього часу трава в іграх зазвичай позначалася текстурою на землі, а не рендерингом окремих стебел. Але зі збільшенням потужності заліза з’явилася можливість рендери траву. Чудові приклади такого візуалізації можна побачити в іграх на зразок Horizon Zero Dawn і The Legend of Zelda: Breath of the Wild. У цих іграх гравець може бродити по трав’яним лугах, і, що більш важливо, трава реагує на дії гравця.

На щастя, створити таку систему не дуже складно. Насправді, стаття навчить вас саме цьому! У цьому туториале ви навчитеся наступного:

  • Створювати векторне поле з допомогою захоплення сцени (scene capture) і системи частинок
  • Згинати траву від гравця на підставі векторного поля

Примітка: у цьому туториале передбачається, що ви вже знаєте основи роботи з Unreal Engine. Якщо ви новачок в Unreal Engine, то вивчіть нашу серію туториалов з десяти частин Unreal Engine для початківців. Зокрема, зверніть увагу на туторіал по системах частинок, щоб знати, як використовувати Cascade в цьому туториале.
Примітка: цей туторіал відноситься до серії з трьох туториалов по роботі з render targets:

  • Частина 1: малювання за допомогою Render Targets
  • Частина 2: деформований сніг
  • Частина 3: інтерактивна трава

 

Приступаємо до роботи

Почнемо з завантаження матеріалів для цього туториала (їх можна завантажити звідси). Розпакуйте їх, перейдіть в InteractiveGrassStarter і відкрийте InteractiveGrass.uproject. Ви побачите невелике поле трави, яке буде темою цього туториала. Також я створив віджет для відображення render target захоплення сцени.

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

Для початку давайте розглянемо інший спосіб створення інтерактивної трави. Найбільш поширений спосіб полягає в передачі координат гравця матеріалу трави і у використанні сферичної маски для коливання трави в певному радіусі від гравця.

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

Що таке векторне поле?

Векторне поле — це просто текстура, кожен піксель якого відповідає напрямку. Якщо ви раніше працювали з картками потоків, то вони аналогічні. Але замість переміщення UV ми будемо з допомогою контакту World Position Offset переміщати вершини. На відміну від рішення зі сферичною маскою, для отримання направлення згинання матеріалів досить тільки один раз семплувати векторне поле.

Давайте дізнаємося, як можна зберігати напрямки в текстуру. Подивіться на цю сітку:


Припустимо, червона точка — це об’єкт, який ми хочемо перемістити. Якщо ми перемістимо його у правий нижній кут, то який вектор буде позначати це переміщення? Якщо ви відповіли (1, 1), то ви маєте рацію! Як ви напевно знаєте, можна також представити вектори у вигляді квітів, і таким чином зберегти їх текстуру. Давайте вставимо цей вектор color picker Unreal і подивимося, який колір він поверне.


Як бачите, напрямок (1, 1) повертає жовтий колір. Це означає, що якщо ми захочемо зігнути траву за напрямом позитивних осей XY, то нам доведеться використовувати цей колір текстури. Давайте тепер подивимося на кольори всіх векторів.


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

Але з іншими трьома квадрантами виникає проблема. У нас є градієнт тільки по одній осі, або немає градієнта взагалі. Це означає, що кілька векторів будуть мати один колір. Наприклад, ми ніяк не зможемо розрізнити вектори (-1, 1) і (0, 1).

Ці три квадранти не мають унікальних кольорів для кожного вектора тому, що ми можемо уявити кольору тільки з допомогою значень від 0 до 1. проте в цих трьох квадрантах використовуються від’ємні значення, що знаходяться поза межами цього інтервалу.

Рішення полягає в перерозподілі векторів таким чином, щоб всі вони вмістилися в інтервалі від 0 до 1. Це можна зробити, помноживши вектор на 0.5 і додавши 0.5. Ось візуалізація того, що ми отримаємо:


Тепер у кожного вектора є унікальний колір. Коли нам потрібно використовувати його для обчислень, ми просто перерозподіляємо його назад в інтервал від -1 до 1. Ось кілька кольорів, і відповідні їм напрями після перерозподілу:

  • (0, 0): негативні X і Y
  • (0.5, 0.5): немає руху
  • (0, 1): негативний X і позитивний Y
  • (1, 0): позитивний X і негативний Y

Тепер давайте дізнаємося, як створити векторне поле в Unreal.

Створення векторного поля

На відміну від створення слідів на снігу, ми не будемо виконувати захоплення форми об’єктів. Замість цього ми будемо малювати на render target з допомогою пензлів». Це будуть просто зображення вибраного векторного поля. Я буду називати їх кистями напрямків.

Замість малювання на render target з допомогою блюпринтів, ми можемо використовувати частинки. Частинки відображатимуть кисть напрямів і випускати з гравця. Для створення векторного поля нам достатньо використовувати захоплення сцени (scene capture) і виконувати захоплення тільки частинок. Перевага цього методу в тому, що створювати сліди дуже просто. Крім того, він дозволяє з легкістю управляти такими властивостями, як тривалість збереження слідів та їх розмір. Крім того, частинки створюють тимчасово зберігаються сліди, тому що вони існують після відходу з галузі захоплення і повернення до неї.

Нижче наведено декілька прикладів кистей напрямків, які ми можемо використовувати, а також їх вплив на траву. Зауважте, що у наведеному нижче прикладі невидимі частинки.


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

Створення матеріалу для напрямів

Існує два способи створення кисті напрямків:

  1. Математичний: напрямки та форма задаються всередині матеріалу. Перевага його в тому, що він не вимагає стороннього ПЗ і зручний для простих форм.
  2. Перетворення карти нормалей: створення карти нормалей потрібних напрямків і форми. Для перетворення карти у відповідний векторне поле нам достатньо видалити синій канал. Перевага цього методу в тому, що можна дуже просто створювати складні форми. Нижче представлений приклад кисті, яку складно буде створити математично.


Для цього туторіалу ми створимо кисть математично. Перейдіть в папку Materials та відкрийте M_Direction. Зауважте, що для цього матеріалу обрана модель затінення (shading model) Unlit. Це важливо, тому що дозволяє захоплення сцени захоплювати частки без впливає на них освітлення.

Щоб не ускладнювати, ми створимо матеріал, який змусить траву відходити від центру частинки. Для цього створимо наступну схему:


Тепер нам потрібно виконати перерозподіл. Для цього додайте виділені ноди і з’єднайте всі наступним чином:


Тепер давайте додамо кисті круглу форму. Для цього додамо виділені ноди:


RadialGradientExponential управляє розміром і різкістю окружності кола. Множення його на Particle Color дозволяє управляти непрозорістю частинок із системи частинок. Докладніше я розповім про це в наступному розділі.

Ось, як виглядає кисть:


Натисніть Apply і закрийте матеріал. Тепер, коли ми створили матеріал, настав час приступити до системи частинок слідів.

Створення системи частинок слідів

Перейдіть в папку ParticleSystems і відкрийте PS_GrassTrail. Для економії часу я вже створив всі необхідні модулі.


Ось, як кожен модуль впливає на сліди на траві:

  • Spawn: частота створення впливає на плавність слідів. Якщо сліди виглядають переривчастими, то варто збільшити частоту створення. У цьому туторіалу ми залишимо значення за замовчуванням (20).
  • Lifetime: час існування сліду до повернення трави до вихідного стану
  • Initial Size: розмір сліду
  • Color Over Life: оскільки ми використовуємо в матеріалі Particle Color, тут можна управляти непрозорістю. Також можна змінювати альфа-криву для управління зникненням сліду. Наприклад, можна вибрати лінійну пропажу, easing in і/або easing out. У цьому туториалі ми залишимо налаштування за замовчуванням, тобто лінійна пропажа.
  • Axis Lock: використовується для того, щоб частинки були спрямовані в бік захоплення сцени
  • Initial Rotation: використовується для того, щоб частинки були орієнтовані за правильним осях (детальніше про це нижче)

Спочатку нам потрібно задати матеріал. Виберіть модуль Required і задайте для Material значення M_Direction. Також визначте для Sort Mode значення PSORTMODE Age Newest First.


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

Далі йде тривалість існування сліду. Виберіть модуль Lifetime і задайте Constant значення 5. Завдяки цьому слід буде пропадати протягом п’яти секунд.


Тепер перейдемо до розміру сліду. Виберіть модуль Initial Size і задайте для Constant значення (150, 150, 0). Завдяки цьому кожна частинка буде покривати область 150×150.


Тепер нам потрібно зробити так, щоб дивилися в напрямку захоплення сцени. Оскільки захоплення сцени виконуватися з виду зверху, то частинки повинні дивитися у напрямку позитивної осі Z. Для цього виберіть модуль Axis Lock і задайте для Axis Lock Flags значення Z.


Нарешті, нам потрібно задати поворот частинок. На поточний момент кольору кисті не вирівняні з напрямком, який вони представляють. Так вийшло тому, що за замовчуванням система часток застосовується з поворотом на 90 градусів. Щоб виправити це, виберіть модуль Initial Rotation і задайте для Constant значення -0.25. Це поверне частинки на 90 градусів проти годинникової стрілки.


І це все, що нам потрібно для системи частинок, тому давайте закриємо її.

Далі нам потрібно прикріпити систему частинок до того, що повинне створювати сліди. В нашому випадку потрібно прикріпити її до персонажа гравця.

Прикріплення системи частинок

Перейдіть в CharactersMannequin і відкрийте BP_Mannequin. Далі створіть компонент Partice System і назвіть його GrassParticles.


Далі нам потрібно задати систему частинок. Перейдіть в панель Details та задайте для ParticlesTemplate значення PS_GrassTrail.


Було б дивно, якби гравець міг бачити слід в грі, тому варто приховати його. Для цього включимо RenderingOwner No See.


Оскільки система частинок прикріплена до гравця (owner), то гравець не побачить її, але вона буде видима всьому іншому.

Натисніть Compile, а потім натисніть Play. Зауважте, що частинки не з’являються для камери гравця, але відображаються на render target.


Поки захоплення сцени налаштований на захоплення всього. Очевидно, що це нам не підходить, тому що на траву впливають тільки частинки. У наступному розділі ми дізнаємося, як виконувати захоплення тільки частинок.

Захоплення частинок

Якщо ми будемо захоплювати частки зараз, то буде виконуватися непотрібне нам згинання в областях без часток. Так відбувається тому, що фоновим кольором render target є чорний. Згинання відбувається тому, що чорний позначає рух у напрямку до негативним осях XY (після перерозподілу). Щоб порожні області не містили руху, нам потрібно зробити так, щоб фоновим кольором render target був (0.5, 0.5, 0). Простіше всього це зробити, створивши величезну площину і прикріпивши його до гравця.

Спочатку створимо матеріал для фону. Поверніться в Browser і Content відкрийте MaterialsM_Background. Потім з’єднайте константу (0.5, 0.5, 0) з Emissive Color.


Примітка: як і у випадку з матеріалом часток, будь-який матеріал, який ми будемо захоплювати, повинен мати модель затінювання unlit.
Натисніть Apply і закрийте матеріал. Поверніться до BP_Mannequin, а потім створіть новий компонент Plane. Назвіть його Background.


Далі встановіть наступні властивості:

  • Location: (0, 0, -5000). Ми розміщуємо площину так низько, щоб вона не перекривала ніякі частки.
  • Scale: (100, 100, 1). Так ми отмасштабируем до розміру, достатнього для покриття всієї області захоплення.
  • Material: M_Background


Як і у випадку з частками, було б дивно, якби гравець бачив під собою величезну жовту площину. Щоб приховати її, включіть RenderingOwner No See.


Тепер, коли ми налаштували фон, настав час для захоплення частинок. Ми можемо зробити це, додавши систему частинок до списку show-only list захоплення сцени. Це список компонентів, які буде захоплювати захоплення сцени.

Використання Show-List Only

Перш ніж ми отримаємо можливість додавати в show-only list, нам необхідний спосіб отримання усіх впливаючих на траву акторів. Один із способів їх отримання — використання тегів. Теги — це прості рядки, які можна присвоювати акторам і компонентів. Потім можна використовувати нод Get All Actors With Tag для отримання всіх акторів з відповідним тегом.

Оскільки актор гравця повинен впливати на траву, йому потрібен тег. Для додавання тега натисніть на кнопку Class Defaults. Потім створіть в ActorTags новий тег і назвіть його GrassAffector.


Оскільки в список show-only list можна передавати тільки компоненти, нам потрібно додати теги і впливає на траву компонентів. Виберіть компонент GrassParticles і додайте новий тег, розташований в розділі Tags. Назвіть його теж GrassAffector (необов’язково використовувати саме цей тег). Повторіть те ж саме для компонента Background.


Тепер нам потрібно додати впливають на траву компоненти show-only list захоплення сцени. Натисніть Compile і закрийте BP_Mannequin. Потім відкрийте BlueprintsBP_Capture. Перейдіть до Event BeginPlay і додайте виділені ноди. Переконайтеся також, що приєднані позначені контакти.


Ця схему буде обходити в циклі всіх акторів з тегом GrassAffector. Після чого вона буде перевіряти, чи є у актора компоненти з таким тегом, і додавати їх в show-list only.

Далі нам потрібно сказати захоплення сцени, щоб він використовував тільки show-list only. Виберіть компонент SceneCapture і перейдіть в розділ Scene Capture. Задайте для Primitive Render Mode значення Use ShowOnly List.


Натисніть Compile і закрийте блюпринт. Якщо натиснути на Play, то ви побачите, що render target тепер виконує захоплення тільки частинок і площини фону.


У наступному розділі ми дістанемося до того, чого очікували. Настав час навчити траву згинатися!

Згинаємо траву

Спочатку нам потрібно спроектувати render target на траву. Перейдіть в папку Materials та відкрийте M_Grass. Потім створіть показані нижче ноди. Задайте в якості текстури RT_Capture.


Оскільки ми перерозподілили кольору в інтервал від 0 до 1, то перед використанням їх потрібно перерозподілити назад в інтервал від -1 до 1. Для цього додамо виділені ноди:


Тепер, коли у нас є напрямок згинання, нам потрібен якийсь спосіб повернути траву в цьому напрямку. На щастя, для цього існує мод під назвою RotateAboutAxis. Давайте створимо його.


Давайте почнемо з контакту NormalizedRotationAxis. Як зрозуміло з назви, це вісь, навколо якої буде повертатися вершина. Для обчислення нам лише потрібно векторний добуток напрямки згинання на (0, 0, -1). Для цього нам потрібно додати виділені ноди:


Також нам потрібно вказати RotationAngle, тобто величину повороту вершини відносно точки обертання. За замовчуванням значення повинно знаходитися в інтервалі від 0 до 1, де 0 — 0 градусів, а 1 — це 360 градусів. Для отримання кута повороту ми можемо використовувати довжину напрямки згинання, помножену на максимальний поворот.


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

Обчислення PivotPoint — трохи більш складне завдання, тому що один меш трави містить кілька стебел. Це означає, що ми не можемо використовувати щось на зразок нода Object Position, тому що він буде повертати одну точку для всіх стебел трави.

В ідеалі слід використовувати сторонній 3D-редактор для збереження точок обертання усередині UV-каналів. Але для цього туториала ми просто апроксимуємо точку обертання. Це можна зробити, пересунувшись з вершини вниз на певний зсув.


Для цього додамо виділені вузли:


У цьому туториале трава висотою приблизно 80 одиниць, тому я ставлю для PivotOffset це значення.

Далі нам потрібно виконати дві маски. Перша маска гарантує, що корінь стебла не буде рухатися. Друга маска гарантує, що на траву за межами області захоплення не буде діяти векторне поле.

Маскування

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


Для маскування коренів ми просто помножимо результат RotateAboutAxis на нод Vertex Color.


Для маскування трави за межами області захоплення ми помножимо попередній результат на виділений нсд:


Натисніть Apply і закрийте матеріал. Натисніть Play і побігайте по траві, щоб залишити на ній сліди!

Куди рухатися далі?

 

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

Related Articles

Залишити відповідь

Ваша e-mail адреса не оприлюднюватиметься. Обов’язкові поля позначені *

Close