Оптимізація GameThread в Unreal Engine 4 ч. 1

Посилання на частину 2

Батл рояль, зараз досить популярний жанр, але в той же час вельми складний в реалізації. 100 гравців, великий відкритий світ і все це підносить проблеми в розробці такої гри.

Significance Manager

Основна ідея рішення в тому, що б кожного гравця розділити в свій Significance Bucket за важливістю, заснованої на дистанції, розмірах, видимості та іншої інформації. У кожного такого сегмента є максимальний розмір і пріоритет. Significance Manager може проводити розрахунок для LOD ігрового світу. Наприклад, пріоритет гравця який не потрапляє в кадр в рази нижче, ніж у гравця який знаходиться поруч в зоні видимості.

Крім того, Significance Manager можна використовувати в якості базової системи для управління іншими Scalability системами. Наприклад, «Fortnite» - багатоплатформенна гра, реалізація якої під різні платформи дуже розрізняється. Ми можемо встановити різні параметри Bucket для різних платформ і навіть різних моделей. Наприклад, для консолей MaxSize кожного з наших сегментів становить 5, 10, 10, 75, для мобільного телефону - 1, 5, 15, 79 тощо відповідно. Він також підтримує налаштування параметрів для певних моделей мобільних телефонів.

Анімації

У "Fortnite" "персонаж складається з 5 основних частин: Базового Скелета, голови, тіла, рюкзака і зброї. "Базовий Скелет" "це порожній скелет без будь-якої інформації про зовнішність. Кожна частина анімації має свій власний AnimBP і всі вони виконують свої процеси після CopyPose з «Базового Скелета», наприклад, симуляція фізики. Це рішення дуже гнучке і зручне, але несе великі труднощі з точки зору ефективності обробки. Так як у нас 100 гравців, що означає що близько 500 SkeletalMesh, які необхідно постійно обробляти в GameThread. Ми звичайно можемо застосувати LOD до міша, не сильно, але він зменшить кількість кісток, але ці розрахунки кісток проводяться в WorkerThread.

Є кілька альтернатив даному рішенню, наприклад Mesh merge, який використовувався в "Unreal Tournament" ", але воно несе свої проблеми. По-перше, збільшується кількість використовуваної пам'яті, так як кожен гравець має свою унікальну сітку. По-друге, цей підхід втрачає будь-яку гнучкість. Адже кожна частина може мати свою власну унікальну логіку і якщо матеріали об'єднати, унікальність анімації може зникнути.

Звичайно, існують й інші рішення даної проблеми. Наприклад, підвіски, які не потребують анімації, можна просто прикріпити до Sockets. Є ще варіант Master-Slave, але цей метод не має масштабованості. Наприклад, Master Skeleton без хвоста або плаща і незалежна анімація або фізична симуляція хвоста або плаща неможливі.

Незалежно від того, який метод буде обрано, LOD для кісток і скелета є невід'ємною частиною оптимізації.

(Детальніше можна ознайомитися з методами в офіціальній окументації)

Процес оновлення анімації в Unreal Engine розділений на 3 етапи. Спершу виконується операція оновлення в GameThread. Оновлення використовується для розрахунку деяких значень, таких як вага наприклад. Наступним кроком йде важкий процес з обробки Eval, такі як анімація, декомпресія, змішування (blend) тощо. І завершує цей процес запуск Notify і відправку даних в RenderThread.

Цитата Wang Mi

В одному кадрі дуже багато персонажів, які виконують прорахунок скелетних анімацій. Всі ми знаємо що, зараз мобільні пристрої багатоядерні. Щоб краще використовувати їх потужності нам необхідно краще обробляти запити BPVM (Blueprint virtual machine) і поділ на потоки. На основі двох вищевказаних способів оптимізації, ми не використовуємо Event Graph, а робимо ігрову логіку як частину AnimInstanceProxy, тим самим движок зможе автоматично визначати, чи зможе Event Graph оновляться в інших потоках. Якщо ви використовуєте Fast Path, ми можемо помістити оновлення скелета в WorkingThread. Наприклад є 50 персонажів. На початку будь-якого оновлення персонажа, обчислення розділяються на інші потоки, а основний потік продовжує свою роботу.

Якщо в AnimBP, є хоч одна нода (not Fast Path), якою необхідно пройти BPVM, то вся система не зможе бути відправлена в обробку в асинхронному потоці. Це пов'язано з тим, що безпека потоків не може бути гарантована для довільних звернень вузла ВР до інтерфейсу і самого BPVM.

Якщо ж Event Graph реалізований власним Proxy у власному спадкованому класі AnimInstance, і всі його властивості в AnimInstance також реалізовані у власному спадкованому класі, то весь процес оновлення не потрібно робити за допомогою BPVM, тому він також буде автоматично поміщений в AnyThread для обробки.

Перенесення Event Graph to C++

Blueprints чудова технологія, але у неї є свої проблеми і недоліки. Один з таких AnimBP - Event graph. Якщо він містить багато логічних обчислень і сам по собі складний, витрати ресурсів можуть бути особливо великими. Перенісши обчислення з ВР в С++, може дозволити заощадити багато процесорного часу.

Fast Path

Деякі математичні обчислення часто використовуються в AnimGraph, оскільки виконання у ВР часто супроводжуються зайвими викликами віртуальної машини. Деякі розрахунки можна перенести в AlphaCurveScaleBiasClamp в ноді, тим самим перенісши обчислення в С++, що саме по собі знижує витрати на виклики BPVM. Відмінною рисою Fast Path є невеликий значок блискавки, і необхідно намагатися перетворювати ці ноди в Fast Path якомога більше в процесі розробки.

Вимкнути оновлення анімації якомога частіше

У Skeletal Mesh є одне дуже важливе налаштування, MeshComponentUpdceFlag, яке можна встановити в OnlyTickPoseWhenRendered, тим самим, коли персонаж не буде потрапляти на екран, будь-яка обробка анімації будуть пропускатися (наприклад, якщо у вас звуки кроків прив'язані до anim notify, за вашою спиною) Це одна з тих речей, які можуть бути оброблені за допомогою Significance. Коли гравці занадто далекі від вас, відпадає необхідність обробляти анімацію зброї, в деяких випадках навіть гравця.

Більшість падаючих об'єктів імітують фізику і є Skeletal Meshes. Одна з проблем розрахунку для таких мішей це динамічний шлях. Статичні об'єкти в UE, безпосередньо сортуватимуться і групуватимуться при додаванні на сцену, що значно скорочує споживання ресурсів при вимальовуванні сцени. Динамічні об'єкти отримуються на етапі initViews кожного кадру на початку рендера. Цей процес дуже відрізняється від статики, він не потрапляє в статичну таблицю вимальовки і тим самим знижує ефективність процесу. Такі скелетні об'єкти, дані яких не потребують зміни в кожному кадрі, додаються до StaticRenderPath, тим самим прискорюючи рендеринг

URO (Update Rate Optimization)

У нас, немає необхідності робити розрахунки кісток кожен кадр для кожного гравця. Ви можете налаштувати різну частоту оновлення на основі різної важливості даних (Significance Manager), і навіть забрати у деяких персонажів інтерполяцію. Адже якщо персонаж займає дуже маленьку частину екрану, його можна вважати невидимим.

Додатково з налаштуваннями Skeletal LOD, ми можемо встановити Skeletal Control Code для управління анімації в AnimGraph, щоб не розраховувати на певних LOD, важких процесів, таких як ІК, фізика тощо.

RigidBody

Використання RigidBody, як заміна AnimDynamics, приносить у проект незаперечне поліпшення як продуктивності так і гнучкості налаштувань.

Calculation of Bounds

Не можна просто взяти і застосувати Use Fixed Bounds. Аніматори створюють класні анімації, і після застосування Fixed починають відбуватися дивні речі (наприклад, персонажі пропадають). Bounds Розраховуються кожен кадр використовуючи Collison Shapes. Ми можемо використовувати Bound батька, для того що б не розраховувати його 5 разів (рюкзак, парашут, зброя отримують Bound батька). (Докладніше як відбувається розрахунок Bound ви можете дізнатися з USkinnedMeshComponent::CalcMeshBound).

Інші способи оптимізації анімацій

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

Плагін для обміну анімацією. Принцип полягає в оновленні Eval кісток якомога частіше. Інші анімації використовуються копіюванням і додавання суперпозиції, що як і раніше оптимізується процесором.

Niagara simulates a large group, підходить для моделювання сцени масового побоїща величезної кількості людей і відноситься до GPU-симульованих анімацій.

Підсумок

У цій частині ми розглянули Significance Manager і оптимізацію через анімації. Продовження статті можна прочитати за посиланням.