Оптимізація графіки на Unity. Як використовувати LOD, Draw Calls, Texture Mipmaps та інші техніки рушія

Вітаю усіх прихильників розробок ігор, в яких кожен фрейм — це віддзеркалення вашої особистої наснаги, клопіткої роботи та неабиякої фантазії! Мене звати Роман, і я якоюсь мірою напевно можу називати себе «Unity Developer». Сьогодні я б хотів разом з Вами вийти за межі «звичайного» та відправитись у віртуальні лабіринти оптимізації на рушії Unity.

І так, звісно я розумію, що оптимізація в Unity — це широка тема, і її реалізація може бути відносно унікальною для кожного проєкту. Однак деякі загальні поради можуть бути корисними для більшості випадків, особливо для новачків в цій справі. Саме тому я спробую описати різні техніки та методи своїми словами та з наочними прикладами («загуглити» технічний опис Ви завжди зможете самі). Також для правильної картини світу, що стосується оптимізації було б доречніше оперувати величинами — мілісекунд на кадр (ті самі 16,66), але воно не так наочно, тому будем використовувати одиниці виміру FSP.

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

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

  • оптимізація графіки;
  • оптимізація коду;
  • оптимізація фізики;
  • оптимізація інтерфейсу користувача;
  • оптимізація анімацій;
  • оптимізація освітлення;
  • оптимізація звуку.

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

  1. Система LOD;
  2. Draw Calls;
  3. Occlusion culling;
  4. Texture Mipmaps;
  5. Scenes Manager (Handlers);
  6. Vegetation Grass Render;
  7. Skinned Mesh Renderer.

Система LOD (Levels of Detail)

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

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

Рівнів деталізації може бути декілька, хоча в основному використовують до трьох-чотирьох. У Вас відразу виникне закономірне запитання — чому не можна зробити більше і організувати «плавний перехід» між ними, щоб користувачу взагалі не був помітний перехід. Тут вся річ у тому, що кожен рівень LOD це по суті окрема сітка (mesh) яку потрібно відредагувати або створити в програмі для тривимірної графіки (Blender, Maya...), а кожна сітка — це додатковий обсяг роботи, а також додаткове місце в пам’яті. Саме тому в основному використовують такий «розумний» компроміс. До того ж існує режим, який «згладжує» сам перехід — Cross Fade (на Speed Tree не звертайте увагу оскільки він працює в основному з рослинністю заснованої на шайдерах компанії Speed Tree, яка також належить Unity).


Отже, як працює сама система. Все не просто, а дуже просто! LOD 0 — це Ваша найбільш деталізована модель. LOD 3 (до прикладу) — це Ваша найменш деталізована модель, в якій відсутні будь-яка конкретні деталі, а сама сітка максимально проста. Culled — це стан в якому рендер (відображення) Вашої сітки відключається.


Отже, в яких випадках потрібно застосовувати LOD?

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


А в яких випадках НЕ потрібно застосовувати LOD?

Не потрібно забувати, що ніколи нічого не дається задарма. Система LOD також споживає ресурс вашого пристрою, але це нівелюється користю яку вона приносить. Що я маю на увазі? Якщо у Вас гра, до прикладу, в стилі LOW POLY і всі моделі знаходять в діапазоні кількості трикутників від 50 до 200 то при використанні LOD Ви не отримаєте будь-якої переваги, а можливо і навпаки — отримаєте ще гіршу оптимізацію. Звісно, як я вже казав раніше, все залежить від контексту застосування і це також потрібно тримати в голові.

Розширення, які можуть Вам допомогти в простішій реалізації системи LOD з Unity Asset Store

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

Amplify Impostors. Створює за Вас самий менш деталізований LOD (так звану реалізацію «рекламного щита»), яка максимально схожа на Ваш LOD 0.

Impostors — Runtime Optimization. Реалізація аналогічна попередньому зі своїми нюансами та трохи дешевша (+ є підтримка при купівлі розширення для України згідно з ініціативою Unity).

Poly Few | Mesh Simplifier and Auto LOD Generator. Досить універсальне розширення, яке робить багато роботи за Вас в середовищі редактора Unity

Рекомендую ознайомитись — в демонстраційних відео ассетів також чудово показаний процес роботи системи LOD та які «benefici» вона дає.

Оптимізація Draw Calls

Це процес виклику графічних об’єктів (моделей, елементів інтерфейсу, частинок, матеріалів) для відображення на екрані. Кожен Draw Call викликає «рендерер» для малювання об’єкта або групи об’єктів на екрані. Кількість Draw Calls може дуже впливати на продуктивність гри. Якщо у Вас є багато Draw Calls, це може стати фактором, що призводить до зниження кадрової частоти та продуктивності гри. Таким чином, оптимізація кількості Draw Calls є важливим аспектом при розробці ігор. В Unity існує вбудований «аналізатор» кількості Draw Calls, який називається Frame Debugger. За допомогою нього Ви в будь-який момент можете проаналізувати яким чином та з якою складність рендериться ваша моделька.

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

Для чого, як мінімум, потрібно вміти користуватись Frame Debugger, якщо ви і так знаєте Ваші модельки і з чого вони «зроблені». Все просто — уявіть, що ви влаштувались в якусь нову компанію і Вас кидають на проєкт який вже у виробництві певний час. Ви запускаєте його в редакторі і у Вас показує 8 FPS) На це звісно може бути мільйон причин, але завдяки Frame Debugger Ви за лічені хвилини зможете найти вузьке місце або місця що стосується рендерингу (якщо проблема в ньому).

Як зменшити кількість Draw Calls? Якщо дуже коротко — то Вам потрібно зменшити кількість деталей (суб-сіток) та кількість матеріалів, які використовуються для конкретної моделі. Unity з коробки надає два методи зробити це.

Static Batching (статичне об’єднання): об’єкти, які не змінюють своє положення, можуть бути статично об’єднані в один Draw Call під час попереднього обчислення. Важливо зауважити, що це можуть бути абсолютно різні об’єкти (до прикладу будівлі, ліхтарі, дорожні знаки тощо). Для його роботи Вам потрібно поставити галочку у верхньому правому куті у вкладці інспектора конкретної моделі:

GPU Instancing (екземпляри на GPU): це техніка, яка дозволяє використовувати один Draw Call для багатьох екземплярів одного і того ж об’єкта, що має однакові властивості. Для реалізації цієї техніки Ви повинні увімкнути у властивостях матеріалу GPU Instancing.

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

Однією з самих відносно простих і в той же момент самих дієвих технік є Mesh Combining (об’єднання сіток) — це оптимізує кількість викликів до графічного процесора (GPU). А тепер давайте на прикладі побачимо, який результат дає нам об’єднання сіток:


Покращення в пакетуванні практично у вісім раз. Звісно це тільки для наочного прикладу, оскільки використання однієї моделі на мільйони трикутників також не є чудовим (якщо це звісно не Nanite від UE5) рішенням — в ідеалі для даного прикладу всі тотеми треба було б розбити на партії до 65К трикутників на партію — але знову, все залежить від конкретної ситуації.

А як щодо матеріалів? Якщо одна модель має декілька матеріалів, то їх неможливо об’єднати як всі сітки в одну, скажете Ви і будете правы! З матеріалами справа базується трохи на іншому. У Unity, матеріал — це об’єкт, який визначає, як об’єкт буде відображатися візуально на сцені. Він визначає колір, текстури, властивості відбиття світла, прозорість та інші параметри візуалізації об’єкта.

В більшості випадків інформацію для матеріалу ми надаємо з текстур — Albedo(Base Color), Normal, Roughness і так далі. Ось тут ми і можемо застосувати оптимізацію створивши з різних текстур лише одну, яка має назву «Атлас Текстур». Завдяки цьому атласу і правильній розгортці (UV) нашої моделі ми можемо досягти всього одного Draw Call для нашого матеріалу.


Якість фінального результату в створені атласу в основному залежить від ваших «скілів» в справі компанування UV розгортки.

Розширення, які можуть Вам допомогти в більш простій реалізації об’єднання моделей та текстур з Unity Asset Store.

Mesh Baker. Можливість легко об’єднувати моделі та створювати атласи текстур.

GPU Instancer. Чудове та просте рішення для реалізації DrawMeshInstancedIndirect і Compute Shaders. Може значно збільшити Ваш FPS всього декількома клацаннями миші.

Nature Renderer 2022. Можливість легко об’єднувати моделі та створювати атласи текстур. Ще одне цікаве рішення зосереджене для покращення продуктивності саме для рослиності.

Occlusion culling

Навіщо рендерити те, чого ми не бачимо? Головний принцип роботи системи occlusion culling — це техніка оптимізації, яка дозволяє виключати з рендеринга ті об’єкти, які приховані іншими об’єктами в сцені, або які невидимі для гравця у даний момент.

Unity має вбудований механізм оклюзійного затемнення, який автоматично виконує частину роботи за Вас.


В Unity Learn досить детально розписано для початківців, як використовувати дану техніку — переходьте за цим посиланням та пробуйте.

Отже, в яких випадках потрібно застосовувати occlusion culling?

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

В яких випадках НЕ доречно застосовувати occlusion culling?

Якщо у вас великий відкритий світ то користі від occlusion culling може бути не так багато, як від закритих приміщень, особливо якщо Ви використовуєте в сцені не тільки одну камеру. Але знову ж — залежить від конкретних обставин — якщо у Вас великий відкритий світ, який завдяки Вашому максимально правильному левел дизайну «правильно» перекривається, до прикладу горами, Ви можете отримати користь від occlusion culling!

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

GPU Instancer. Згаданий вже розширення також з коробки реалізує дві техніки Frustum Culling та Occlusion Culling, якими Ви можете керувати.

Optimizers. Реалізує дві техніки Frustum Culling та Occlusion Culling, якими Ви теж можете керувати.

Texture Mipmaps

В контексті даного матеріалу також варто нагадати і про техніку «Texture Mipmaps». Якщо Ви мали справу з Unity (а раз Ви дочитали до цього місця, то напевно що так), то могли навіть не знати про неї, оскільки вся магія робиться за лаштунками. Texture Mipmaps — це версії текстур з меншою роздільною здатністю, які створюються для поліпшення продуктивності та якості зображення. Вони використовуються для того, щоб зменшити артефакти при зменшенні розміру текстур або коли об’єкт віддаляється від камери.


Чому Ви могли не звертати увагу на цю техніку увагу і чому я згадав її — тому що ця техніка дуже схожа на принцип роботи системи LOD описаний раніше. А не звертають на неї увагу, тому що генерація Mipmap відбується коли ви імпортуєте текстуру в середовище виконання Unity, де ця опція активована автоматично.

Scenes Manager (Handlers)

Ця техніка носить трохи специфічний характер, оскільки як ми знаємо (або в якійсь мірі (і не зовсім безпідставно) ходить про це легенда в інтернеті), що Unity не призначений для великих світів. Навіть не так — якщо Ви хочете будувати великий відкритий світ для цього Вам краще буде використовувати більш заточені для цього рушії — до прикладу Unreal Engine чи CryEngine. Але зараз про Unity...

Unity Вам, як користувачу, з порога не надає готове рішення для керування великим світом, як той же World Partition від UE. Він робить це трохи по іншому — він надає Вам бібліотеку UnityEngine.SceneManagement з якою ви, як користувач повинні робити те що Вам потрібно. Якщо порівнювати її з тим же World Partition від UE — то Unity надає Вам каркас від машини, яку Вам потрібно самому зібрати, в той час як World Partition — це вже готова машина, яка їздить (і до речі дуже непогано!).

Але ви можете спитати мене — яке відношення якась бібліотека може мати до оптимізації графіки. Давайте подивимось на ці два зображення, які зроблені з редактора Unity:


Як видно із зображень вище, до даної сцени ми застосували принцип, який по суті повністю інвертує техніку Mesh Combining описану вище — я б назвав його — «розділяй і керуй». Зміст, як можна зрозуміти полягає в тому, щоб «важкі» об’єкти (до прикладу террейни) виносити в так звані «суб-сцени» і в залежності від створеної Вами реалізації асинхронно завантажувати потрібні нам сцени, а ті які нам вже не потрібні — вивантажувати.

Сама реалізація може базуватись на різних «китах» — завантаження за допомогою тригерів, відстаней, спеціальних компонентів в циклах масивів чи за допомогою реалізацій в методі Update і т.д. До речі, відома всім нам функція DontDestroyOnLoad також застосовує принцип суб-сцени, яка знаходиться з нами на протязі роботи застосунку.

Отже, в яких випадках потрібно застосовувати керування сценами?

Як мною вже було продемонстровано — можна робити це для великих світів. Є навіть така популярна мобільна гра «Genshin impact», яка зроблена на Unity — там також великий світ на мобільній платформі і скоріш за все в ній активно застосовується техніка асинхронного завантаження сцен зі своєю реалізацією. Але її також дуже корисно використовувати для лінійних ігор, розставляючи тригери у зонах в яких потрібно «підгрузити» нову сцену.

В яких випадках НЕ доречно застосовувати керування сценами?

Якщо у Вас якась проста гра — «клікер» або якийсь «рогалик» чи це проста 2D гра — цією технікою Ви тільки ускладните собі життя.

Розширення, які можуть Вам допомогти в більш простій реалізації трансляції структурованих просторів (сцен) з магазину активів Unity Asset Store.

World Streamer 2. Дуже комплексне рішення для створення великих світів. Трохи спочатку важке в розумінні принципів роботи — тобто спочатку треба читати інструкцію, а потім робити (а не як зазвичай — воно зламалось — де ця дурна інструкція). Вирішує багато проблем, в основному з плаваючою комою.

SECTR COMPLETE 2019. Універсальне, зручне та просте у використані рішення для трансляції структурованих просторів у Unity.

Vegetation Grass Render

Якщо Ви уважно читали текст вище, то могли звернути увагу на те, що я ніде не згадував про відображення трави (Details). Вся справа в тому, що Unity дещо специфічним видом обробляє траву.

До трави в Unity не можна застосувати систему LOD без застосування плагінів від сторонніх розробників. Звісно це можна обійти специфічним типом і «малювати» траву, як умовні дерева, щоб можна було застосовувати LOD, але на практиці це дуже погана ідея.

Але до чого тут LOD, якщо в Unity вже виділили спеціальний розділ для трави і він працює? Так, він працює і максимально «пробує» оптимізувати траву за допомогою «пачкування».

Проблеми у Вас почнуть виникати тоді, коли Вам знадобиться густа трава на плюс-мінус пристойній відстані, як, до прикладу, на цьому скріншоті з Red Dead Redemption 2 (і так я знаю, RDR2 практично еталон, але все одно хочеться хоч трохи наблизиться до цього рівня).

Існуюча система просто не може це реалізувати — без всяких «але» — просто не може. На це є багато причин — одна з них в тому, що система террейна Unity стара як г.... мамонта.

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

З проблемою зрозуміло — тепер трохи по можливі рішення.

Пачкуйте" траву (Snoop Dogg би одобрив). Що мається на увазі? Пробуйте робити набір («ковдру») з трави по принципу Mesh Combining. Це спростить роботу Unity, оскільки йому не треба буде перебирати 1000 і одну траву в пачку і далі в GPU — завдяки цій техніці Ви зможете сильно полегшити йому життя тим самим збільшивши продуктивність.

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

Досить елегантне рішення запропонував розробник BOXOPHOBIC з його плагіном до його ж двигуна рослинності — The Vegetation Engine | Terrain Blanket Module. Взагалі рекомендую поглянути на продукти цього розробника — реально дуже якісні і зручні інструменти для роботи в Unity (і це не реклама — це факт!)

Використовуєте плагіни сторонніх розробників — детальний опис завжди наведений в їх плагінах. Від себе додам, що їх рішення завжди «переносить» будь-яку важку роботу з рослинність прямо на GPU, а як їх використовувати, і чи використовувати взагалі — залежить від Вас.

Skinned Mesh Renderer

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

Ми бачимо на ньому купу рухомих деталей (башту, колеса, дуло). Перше, що нам прийде в голову — якщо вони рухомі, то ними якось потрібно маніпулювати, а це в свою чергу означає що всі вони мають бути незалежні. І саме в цьому місці ми повернемось до пункту 2 нашого сьогоднішнього матеріалу — якщо всі рухомі деталі танка представляють з себе окремі моделі, то на кожну деталь буде приходитись свій Draw Call. Це нас повністю не влаштовує!

Вихід полягає в тому, щоб розглядати цю модель через призму компонента Skinned Mesh Renderer, де кожна рухома деталь танка — це окрема «кістка», якою ми спокійно можемо маніпулювати, чи то з коду, чи то з аніматора. Додатковим плюсом буде те, що при застосуванні LOD деталі умовного танка на кожному рівні деталізації будуть синхронізовані, оскільки маніпуляція відбувається за допомогою кісток, а не моделей.

Відразу скажу два недоліки цієї техніки — по-перше Вам потрібно буде переробити свої модельки (розмістити кістки, надати їм вагу). По-друге компонент Skinned Mesh Renderer «важчий» для продуктивності чим звичайний Mesh Renderer. Тому, якщо у Вас рухається одна або може лише дві деталі — краще все ж таки використовувати Mesh Renderer.


Але якщо розглядати з моделями танків — то результати просто чудові з 4220 пакетів до 494 — це дуже і дуже непогано). Коли і де використовувати цю техніку вже повністю залежить від Вас!

P.s. В даному матеріалі я не розглядав Post Processing (Volume), шайдери та VFX(і можливо ще щось, по типу того що буде в Unity 6 (бо його ще просто немає)), але якщо матеріал виявився для Вас хоч чимось корисним — в майбутньому є варіант що я його доповню. Тому тисніть «Подобається», додавайте до обраного та пишіть свої зауваження в коментарях.

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

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

Гарний матеріал — продовжуйте.
Думаю, було варто зазначити, що краще не пробувати робити ігри та візуалізації з реалістичною 3D графікою в Unity. Для цього є Unreal Engine (UE), бо навряд чи Unity зможе запропонувати щось подібне до Nanite (заміна LOD) та Lumen (заміна запіканню світла). У UE в цьому напрямку прогрес приголомшливий.
В той же час 2D на Unreal це також жах ;).
Меня здається, що підвищення цікавості починаючих було б варто згадати про:
— шейдери і сайт ShaderToy.com
— спецефекти і показати якусь демку із візуалізації музики, наприклад, youtube.com/watch?v=...​o0m1Y?si=2uesJTK5tvogruyb
— майданчик де можна подивитися роботи розробників різного рівня (для UE це ArtStation.com). Соррі, не в курсі щодо Unity.
— створення текстур та пакет Substance 3D
— створення моделей та пакет ZBrush
— створення сладної та пригломшивої анімації у пакеті Houdini та приголомшивий прогресс у Blender із появою Geometry Nodes

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