Намалювання безлічі плиток з OpenGL, сучасний спосіб


35

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

Мені потрібно намалювати біля екрану 250-750 плиток 48х48 до екрана кожного кадру, а також, можливо, приблизно 50 спрайтів. Плитка змінюється лише при завантаженні нового рівня, а спрайти змінюються весь час. Деякі з плиток складаються з чотирьох штук 24х24, а більшість (але не всі) спрайтів мають такий же розмір, як і плитки. Багато плиток та спрайтів використовують альфа-змішування.

Зараз я все це роблю в негайному режимі, що я знаю, що це погана ідея. Тим не менше, коли хтось із членів нашої команди намагається запустити його, він отримує дуже погану частоту кадрів (~ 20-30 кадрів в секунду), і це набагато гірше, коли більше плиток, особливо коли багато таких плиток є такі, які нарізати шматочками. Це все змушує мене думати, що проблема полягає в кількості телефонних дзвінків, які здійснюються.

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

ПЛИТКИ:

  1. Коли рівень завантажений, намалюйте всі плитки один раз у буфер кадру, прикріплений до великої текстури, і просто намалюйте на кожному кадрі великий прямокутник з такою текстурою.
  2. Покладіть всі плитки в статичний вершинний буфер, коли рівень завантажений, і намалюйте їх таким чином. Я не знаю, чи є спосіб намалювати предмети з різними текстурами за допомогою одного виклику glDrawElements, чи це навіть щось, що я хотів би зробити. Можливо, просто покладіть всі плитки у велику гігантську текстуру та використовуйте смішні координати текстури у VBO?

СПРИТИ:

  1. Намалюйте кожен спрайт окремим викликом до glDrawElements. Це, мабуть, передбачає багато переключення текстур, що, як мені кажуть, погано. Чи може тут бути корисними масиви текстур?
  2. Використовуйте динамічний VBO якось. Питання текстури як номер 2 вище.
  3. Точкові спрати? Це, мабуть, нерозумно.

Чи є якась із цих ідей розумною? Чи є хороша реалізація десь я могла б переглянути?


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

Спробуйте використовувати текстурний атлас, щоб вам не довелося перемикати текстури, але зберігаючи все інше те саме. Тепер, як у них частота кадрів?
користувач253751

Відповіді:


25

Найшвидший спосіб візуалізації плиток - упаковка даних вершин у статичний VBO з індексами (як вказує glDrawElements). Запис його в інше зображення абсолютно непотрібний і вимагатиме лише набагато більше пам’яті. Переключення текстури ДУЖЕ затратно, тому, ймовірно, ви захочете упакувати всі плитки в так званий Texture Atlas і надати кожному трикутнику в VBO правильні координати текстури. Виходячи з цього, не повинно бути проблем візуалізувати 1000, навіть 100000 плиток, залежно від обладнання.

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

Ви можете подивитися невеликий прототип, який я створив на C ++ за допомогою OpenGL: Particulate

Я маю на увазі приблизно 10000 точкових спрайтів, мабуть, із середнім кадром в секунду 400 на звичайній машині (Quad Core @ 2,66 ГГц). Це CPU з обмеженим процесором, це означає, що відеокарта може відображати ще більше. Зауважте, що я тут не використовую Text Atlases, оскільки у мене є лише одна текстура для частинок. Частинки відображаються за допомогою GL_POINTS, і тоді шейдери обчислюють фактичний розмір квадратиків, але я думаю, що також існує Quad Renderer.

О, і так, якщо у вас немає квадрата і не використовуєте шейдери для картографування текстур, GL_POINTS є досить нерозумним. ;)


Спрати змінюють свої позиції та текстуру, яку вони використовують, і більшість з них робить це кожен кадр. Також спрайти і створюються і руйнуються дуже часто. Це речі, з якими може впоратися потік VBO?
Нік

2
Потік малювання в основному означає: "Надіслати ці дані на відеокарту та відкинути їх після малювання". Тож вам доведеться знову надсилати дані кожен кадр, а це означає, що не має значення, скільки спрайтів ви надаєте, яке положення вони мають, які координати текстури або який колір. Але, звичайно, відправлення всіх даних і дайте можливість GPU обробляти це НАЙМО швидше, ніж безпосередній режим, звичайно.
Марко

Це все має сенс. Чи варто використовувати для цього індексний буфер? Єдині вершини, які повторюватимуться - це два кути від кожного прямокутника, правда? (Я розумію, що індекси різниця між glDrawElements і glDrawArrays це право.?)
Nic

1
Без індексів ви не можете використовувати GL_TRIANGLES, що, як правило, погано, оскільки цей метод малювання той, з найкращими гарантіями. Також реалізація GL_QUADS застаріла в OpenGL 3.0 (джерело: stackoverflow.com/questions/6644099/… ). Трикутники - це рідна сітка будь-якої відеокарти. Отже, ви "використовуєте" 2 * 6 байт більше, щоб зберегти 2 вершини виконання шейдера та vertex_size * 2 байт. Отже, ви, як правило, можете сказати, що ЗАВЖДИ краще.
Марко

2
Посилання на Частинки мертве ... Не могли б ви надати нову?
SWdV

4

Навіть при цьому кількість викликів відтворення ви не повинні бачити такого роду падіння продуктивності - безпосередній режим може бути повільним , але це не так, що повільно (для довідки, навіть дорогий старий Quake може керувати кількома тисячами викликів негайного режиму на кадр без падіння вниз так погано).

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


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

Як щодо змін у державі тоді? Ви групуєте непрозорі плитки за державою?
Максим Мінімус

Така можливість. Це, безумовно, заслуговує більшої уваги з мого боку.
Нік

2

Гаразд, оскільки моя остання відповідь ніби не вийшла з рук, це нова, яка може бути кориснішою.


Про 2D-продуктивність

Спочатку кілька загальних порад: 2D не вимогливий до поточного обладнання, навіть багато в чому неоптимізований код буде працювати. Це не означає, що ви повинні проміжний режим, хоча, принаймні, переконайтеся, що ви не змінюєте стани, коли непотрібні (наприклад, не пов'язуйте нову текстуру з glBindTexture, коли та сама текстура вже пов'язана; якщо перевірка на процесорі є тоннами швидше, ніж glBindTexture-call) і не використовувати щось настільки неправильне і дурне, як glVertex (навіть glDrawArrays буде набагато швидшим, і це не складніше у використанні, хоча це не дуже "сучасне"). З цих двох дуже простих правил час кадру повинен бути не менше 10 мс (100 кадрів в секунду). Тепер, щоб отримати ще більшу швидкість, наступним логічним кроком є ​​групування, наприклад, поєднання стільки зворотних дзвінків в один, для цього слід розглянути можливість реалізації текстурних атласів, Таким чином, ви можете мінімізувати кількість прив'язки текстури і тим самим збільшити кількість прямокутників, які ви можете намалювати за допомогою одного дзвінка до великої кількості. Якщо вам зараз не до 2 мс (500 кадрів в секунду), ви щось робите не так :)


Карти плитки

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

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


Спрайт

Для спрайтів потрібна велика гнучкість, що робить її дуже важкою для оптимізації, окрім обговорених у розділі "Про 2D-продуктивність". І якщо ви не хочете одночасно десять тисяч спрайтів на екрані, це, мабуть, не варте зусиль.


1
І навіть якщо у вас десять тисяч спрайтів, сучасне обладнання повинно працювати на пристойній швидкості :)
Marco

@ API-Beast почекати що? як ви обчислюєте УФ текстури у шейдері фрагмента? Якщо вам належить надіслати ультрафіолетові вилучення на фрагмент шейдера?
HgMerk

0

Якщо все інше не вдалося ...

Налаштування методу малювання відкидним флопом. Оновіть кожен інший спрайт за раз. Хоча навіть за допомогою VisualBasic6 та простих розрядних методів, ви можете активно малювати тисячі спрайтів на кадр. Можливо, вам варто поглянути на ці методи, оскільки ваш прямий метод просто малювання спрайтів здається невдалим. (Звучить, як ви використовуєте "метод візуалізації", але намагаєтесь використовувати його як "ігровий метод". Візуалізація - це ясність, а не швидкість.)

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

Використовуйте буфер для статичного фону незайманого. Це ніколи не відображається само собою, якщо тільки на екрані немає спрайтів. Це постійно використовується для "повернення", де був намальований спрайт, щоб відтягнути спрайт у наступному дзвінку. Вам також потрібен буфер, щоб "намалювати", що не є екраном. Ви малюєте там, після того, як все намальовано, один раз гортаєте його на екран. Це повинно бути один екранний дзвінок на всі ваші справки. (На відміну від малювання кожного спрайта на екрані, по одному або спроби зробити це все одночасно, що призведе до того, що ваше альфа-змішування не вдасться.) Запис у пам'ять швидко і не потребує часу на екрані, щоб "намалювати ". Кожен дзвінок дзвінка чекатиме зворотного сигналу, перш ніж він спробує зробити повторний. (Не v-синхронізація, фактична апаратна галочка, яка набагато повільніше, ніж час очікування RAM.)

Я думаю, це є причиною того, що ви бачите цю проблему лише на одному комп’ютері. Або це відновлюється до візуалізації програмного забезпечення ALPHA-BLEND, яке не підтримує всі карти. Ви перевіряєте, чи підтримується ця функція апаратно, перш ніж спробувати її використовувати? Чи є у вас резервний запас (режим не альфа-змішування), якщо у них його немає? Очевидно, у вас немає коду, які обмеження (кількість речей змішане), як я припускаю, це призведе до погіршення вмісту вашої гри. (На відміну від того, якби це були лише ефекти з частинками, які є всіма альфа-змішаними, і, отже, програмісти обмежують їх, оскільки вони сильно оподатковують більшість систем, навіть при апаратній підтримці.)

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


-1

Створіть аркуш спрайт для предметів і набір плиток для місцевості, як і в іншій 2D грі, не потрібно перемикати текстури.

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

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


-1: Моменталізація - це гірша ідея, ніж чисте шадерське рішення містера Беаста. Моменталізація найкраще спрацьовує за продуктивністю, коли візуалізуються об'єкти помірної складності (~ 100 трикутників або близько того). Кожна плитка трикутника, яка потребує текстурних координат, не є проблемою. Ви просто створите сітку з купою сипучих квадратиків, які утворюють мапу плитки.
Нікол Болас

1
@NicolBolas гаразд, я залишу відповідь заради навчання
дрета

1
Для наочності, Нікол Болас, яка ваша пропозиція щодо того, як з усім цим боротися? Потік Марко малює річ? Чи десь я бачу реалізацію цього?
Нік

@ Nic: Потік до буферних об'єктів не є особливо складним кодом. Але насправді, якщо ви говорите лише про 50 колючок, це нічого . Коефіцієнти хороші, що саме ваш малюнок на місцевості був причиною проблеми продуктивності, тому перехід на статичні буфери для цього, ймовірно, буде досить хорошим.
Нікол Болас

Насправді, якби екземпляр спрацював так, як ми можемо подумати, це було б найкращим рішенням, але, оскільки це не так, випікання всіх екземплярів в одну статичну vbo - це шлях.
Jari Komppa
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.