Оптимізація гравітаційних розрахунків


23

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

Таке враження, що я маю змогу покращити продуктивність. У будь-який момент часу, ймовірно, 99% об'єктів у системі матимуть лише незначний вплив на об’єкт. Я, звичайно, не можу сортувати об'єкти за масою і розглядаю лише 10 найбільших об'єктів чи щось подібне, тому що сила змінюється на відстані більше, ніж з масою (рівняння по лініях force = mass1 * mass2 / distance^2). Я думаю, що хорошим наближенням було б розглянути найбільші об’єкти та найближчі об’єкти, ігноруючи сотні крихітних фрагментів скелі з іншого боку світу, які ніяк не можуть вплинути ні на що - але для того, щоб з’ясувати, які об’єкти є найближче мені доводиться повторювати всі об’єкти, і їх положення постійно змінюються, тож це не так, як я можу це зробити один раз.

В даний час я роблю щось подібне:

private void UpdateBodies(List<GravitatingObject> bodies, GameTime gameTime)
{
    for (int i = 0; i < bodies.Count; i++)
    {
        bodies[i].Update(i);
    }
}

//...

public virtual void Update(int systemIndex)
{
    for (int i = systemIndex + 1; i < system.MassiveBodies.Count; i++)
    {
        GravitatingObject body = system.MassiveBodies[i];

        Vector2 force = Gravity.ForceUnderGravity(body, this);
        ForceOfGravity += force;
        body.ForceOfGravity += -force;
    }

    Vector2 acceleration = Motion.Acceleration(ForceOfGravity, Mass);
    ForceOfGravity = Vector2.Zero;

    Velocity += Motion.Velocity(acceleration, elapsedTime);
    Position += Motion.Position(Velocity, elapsedTime);
}

(зауважте, що я видалив багато коду - наприклад, тести зіткнення, я не повторюю об'єкти вдруге, щоб виявити зіткнення).

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

Функції Gravity.ForceUnderGravity(...)і Motion.Velocity(...)т. Д. Просто використовують трохи вбудованої XNA у векторну математику.

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

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

Будь-які думки про те, як я міг би покращити це? Якийсь евристичний я можу використовувати для гоління довжини петлі від сотень до декількох? Який код я можу виконувати рідше, ніж кожне оновлення? Чи повинен я просто багатопрочитати його, поки він не стане досить швидким, щоб забезпечити світ пристойних розмірів? Чи варто спробувати вивантажити обчислення швидкості на GPU? Якщо так, то як я це архітектор? Чи можу я зберігати статичні, спільні дані на GPU? Чи можу я створити HLSL функції на графічному процесорі та викликати їх довільно (за допомогою XNA) чи вони повинні бути частиною процесу малювання?


1
Лише зауваживши, що ви сказали, що "масивні предмети не перекручуються над сміттям як частина обчислення їх швидкості, але кожен шматок сміття повинен перебирати масивні частинки". У мене склалося враження, що ви припускаєте, що це більш ефективно. Однак ітерація 100 предметів сміття 10 разів кожна, все одно така ж, як повторення 10 масивних предметів у 100 разів. Можливо, було б непогано перебирати кожен предмет сміття в масивний цикл об'єктів, щоб ви цього не робили вдруге.
Річард Марскелл - Дракір

Наскільки точне моделювання вам потрібно? Вам справді потрібно все, що прискорюється один до одного? І чи справді вам потрібно використовувати справжній розрахунок гравітації? Або ви можете відійти від цих умов для того, що ви намагаєтеся досягти?
хаосТехнік

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

@chaosTechnician це не повинно бути дуже точним - адже якщо на нього припадає лише кілька найбільш домінуючих сил, то система була б більш стабільною, що ідеально. Але це з'ясування, яка з сил є найбільш домінуючою ефективно, з якою я маю проблеми. Крім того, розрахунок гравітації вже наближений, це просто G * m1 * m2 / r^2, де G просто налаштувати поведінку. (хоча я не можу їх просто пройти шляхом, оскільки користувач може порушити систему)
Carson Myers

Чому кожен шматок сміття повинен перебиратись над масивними частинками, якщо він безмасштабний? Зіткнення?
sam hocevar

Відповіді:


26

Це звучить як робота для сітки. Розділіть свій ігровий простір на сітку, і для кожної комірки сітки зберігайте список об'єктів, що знаходяться в ній. Коли об’єкти переміщуються через межу комірки, оновіть список, в якому вони перебувають. Під час оновлення об'єкта та пошуку інших взаємодіючих можна переглянути лише поточну комірку сітки та кілька сусідніх. Ви можете налаштувати розмір сітки для найкращої продуктивності (врівноважуючи витрати на оновлення комірок сітки - що вище, коли комірки сітки занадто малі - з витратами на пошук, що вище, коли комірки сітки занадто великий).

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

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

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


1
+1 для ідеї сітки. Я б також запропонував відстежити центр маси для сітки, а також зробити обчислення трохи більш чистими (якщо потрібно).
хаосТехнік

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

8
Це, по суті, алгоритм Барнса-Хата: en.wikipedia.org/wiki/Barnes –Hut_simulation
Рассел Борогов

Це навіть схоже на те, як гравітація повинна працювати у фізиці - згинання простору-часу.
Zan Lynx

Ідея мені подобається - але я, чесно кажучи, вважаю, що їй потрібно трохи доопрацювати - що станеться, якщо два об'єкти дуже близькі один до одного, але в окремих клітинках? Я міг бачити, як може допомогти ваш третій абзац - але чому б просто не зробити кругову оболонку на взаємодіючих об'єктах?
Джонатан Дікінсон

16

Алгоритм Барнса-Хата - це шлях із цим. Він використовується в моделях суперкомп'ютерів для вирішення вашої точної проблеми. Кодувати це не надто важко, а це дуже ефективно. Я фактично написав аплет Java не так давно, щоб вирішити цю проблему.

Зайдіть на сторінку http://mathandcode.com/programs/javagrav/ і натисніть "пуск" і "показати квадратик".

На вкладці параметрів ви бачите, що кількість частинок може пройти аж до 200 000. На моєму комп’ютері обчислення закінчується приблизно за 2 секунди (малюнок 200 000 точок займає близько 1 секунди, але обчислення працює на окремій потоці).

Ось як працює мій аплет:

  1. Створіть список випадкових частинок із випадковими масами, положеннями та початковими швидкостями.
  2. Побудуйте з цих частинок квадри. Кожен вузол квадрату містить центр маси кожного підвузла. В основному для кожного вузла у вас є три значення: massx, massy та mass. Кожен раз, коли ви додаєте частинку до даного вузла, ви збільшуєте massx та massy відповідно на particle.x * particle.mass та particle.y * particle.mass відповідно. Положення (massx / mass, massy / mass) закінчиться як центр маси вузла.
  3. Для кожної частинки обчисліть сили ( тут повністю описано ). Це робиться, починаючи з верхнього вузла і повторюючи через кожний підвузл квадрату до тих пір, поки даний субнод не буде достатньо малим. Після того, як ви припините повторюватись, ви можете обчислити відстань від частинки до центру маси вузла, а потім можна обчислити силу, використовуючи масу вузла та масу частинки.

Ваша гра повинна легко вміти обробляти тисячу взаємно приваблюючих предметів. Якщо кожен предмет "німий" (як частинки голих кісток у моєму аплеті), ви повинні мати можливість отримати від 8000 до 9000 частинок, а може і більше. І це припускаючи однониткові. За допомогою багатопотокових або паралельних обчислювальних програм ви можете отримати набагато більше частинок, ніж оновлення в режимі реального часу.

Дивіться також: http://www.youtube.com/watch?v=XAlzniN6L94 для великого відображення цього


Перша посилання мертва. Аплет розміщений в іншому місці?
Анко

1
Виправлено! Вибачте, забув заплатити за оренду за цей домен, і хтось придбав його автоматично: \ Також 3 хвилини - це досить хороший час відгуку на 1,3-річну посаду 8D

І я повинен додати: у мене немає вихідного коду. Якщо ви шукаєте якийсь вихідний код, перегляньте частину (написану с). Я впевнений, що є й інші.

3

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

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

Крім того, ви можете створити гравітаційне поле за один прохід, а потім оновити об'єкти в другому проході. Перший прохід, ви в основному заповнюєте сітку (або більш складну структуру просторових даних) гравітаційними впливами кожного об'єкта. Результат тепер - це гравітаційне поле, яке ви навіть можете відображати (виглядає досить круто), щоб побачити, яке прискорення буде застосовано до об'єкта в будь-якому даному місці. Потім ви перебираєте об'єкти і просто застосовуєте ефекти гравітаційного поля до цього об'єкта. Навіть крутіше, ви можете це зробити на графічному процесорі, перетворюючи об'єкти як круги / сфери на текстуру, потім читаючи текстуру (або використовуючи інший перехід-зворотний зв'язок на GPU) для зміни швидкостей об'єкта.


Поділ процесу на проходи - відмінна ідея, оскільки інтервал оновлення - це, наскільки я знаю, дуже мала частина секунди. Текстура поля гравітації - ДУЖЕ, але, можливо, трохи поза моєю досяжністю зараз.
Карсон Майєрс

1
Не забудьте помножити прикладені сили на кількість пропущених часових відрізків з моменту останнього оновлення.
Zan Lynx

@seanmiddlemitch: Не могли б ви детальніше розкрити текстуру поля гравітації? Вибачте, я не графік-програміст, але це звучить дуже цікаво; Я просто не розумію, як це має працювати. І / чи, можливо, у вас є посилання на опис методики?
Фелікс Домбек

@FelixDombek: Візуалізуйте об'єкти як кола, що представляють область впливу. Фрагментний шейдер записує вектор, що вказує на центр об'єкта і з відповідною величиною (виходячи з відстані від центру та маси об'єкта). Апаратне змішування може впоратися з підсумовуванням цих векторів у режимі аддиції. Результатом будуть не точні гравітаційні поля, але майже напевно будуть досить хорошими для потреб гри. Ще один підхід із використанням графічного процесора дивіться у цій методиці моделювання сили тяжіння на тілі на основі CUDA: http.developer.nvidia.com/GPUGems3/gpugems3_ch31.html
Шон Міддледіч

3

Я б рекомендував використовувати Quad Tree. Вони дозволяють швидко та ефективно шукати всі об’єкти у довільній прямокутній області. Ось стаття про wiki про них: http://en.wikipedia.org/wiki/Quadtree

І безсоромне посилання на мій власний проект XNA Quad Tree на SourceForge: http://sourceforge.net/projects/quadtree/

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


0

Лише невеликий шматочок (можливо, наївний). Я не займаюся ігровим програмуванням, але я відчуваю, що вашим основним вузьким місцем є розрахунок гравітації за рахунок гравітації. Замість ітерації над кожним об'єктом X, а потім пошуку гравітаційного ефекту від кожного об'єкта Y і додавання його, ви можете взяти кожну пару X, Y і знайти силу між ними. Це повинно скоротити кількість обчислень гравітації з O (n ^ 2). Тоді ви будете робити багато доповнень (O (n ^ 2)), але це зазвичай менш дорого.

Також в цей момент ви можете реалізувати такі правила, як "якщо гравітаційна сила буде меншою, ніж \ epsilon, оскільки цих тіл занадто мала, встановіть силу на нуль". Ця структура може бути вигідною і для інших цілей (включаючи виявлення зіткнення).


Це принципово те, що я роблю. Після того, як я отримаю всі пари, що включають X, я більше не повторюю X. Я знаходжу силу між X і Y, X і Z тощо, і застосую цю силу до обох об'єктів у парі. Після завершення циклу ForceOfGravityвектор - це сума всіх сил, яка потім перетворюється на швидкість і нове положення. Я не впевнений, що підрахунок сили тяжіння особливо дорогий, і перевірка того, чи не перевищує він пороговий показник спочатку, не заощадить помітну кількість часу, я не думаю
Карсон Майєрс

0

Продовжуючи відповідь семанмідледітча, я думав, що можу пролити трохи світла (іронії?) На ідею гравітаційного поля.

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

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

Але скільки з цих балів слід обчислити, перш ніж воно стане більш або неефективним, як раніше? Напевно, не багато, навіть 32х32 є суттєвим полем для перегляду кожного об'єкта. Тому розбийте весь процес на кілька проходів; кожна з різною роздільною здатністю (або точністю).

Тобто, перший прохід може обчислити силу тяжіння об'єктів, представлену в сітці 4х4, при цьому кожне значення комірки представляє 2D координату в просторі. Надаючи O (n * 4 * 4) сумарну складність.

Другий прохід може бути більш точним з гравітаційним полем роздільної здатності 64х64, причому кожне значення комірки представляє 2D координату в просторі. Однак, оскільки складність дуже велика, ви можете обмежити радіус дії оточуючих клітин (можливо, оновлюються лише навколишні клітини 5х5).

Додатковий третій прохід може бути використаний для розрахунків з високою точністю, можливо, з роздільною здатністю 1024x1024. Ніколи не запам'ятовуючи, чи фактично ви виконуєте окремі обчислення 1024x1024, але працюєте лише на ділянках цього поля (можливо, підрозділи 6x6).

Таким чином, ваша загальна складність для оновлення становить O (n * (4 * 4 + 5 * 5 + 6 * 6)).

Щоб потім обчислити зміни швидкості для кожного з ваших об'єктів, для кожного поля гравітації (4х4, 64х64, 1024х1024) ви просто зіставите положення маси точок на осередку сітки, застосуйте ці клітинки сітки загальний вектор гравітаційного потенціалу до нового вектора; повторити для кожного "шару" або "проходу"; потім додайте їх разом. Це повинно дати вам хороший вектор гравітаційної сили.

Тому загальна складність становить: O (n * (4 * 4 + 5 * 5 + 6 * 6) + n). Те, що насправді рахується (за складністю), - це кількість осередків, які ви оновлюєте під час обчислення гравітаційного потенціалу в проходах, а не загальної роздільної здатності гравітаційних полів.

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

Сподіваюся, це мало сенс.


0

Як щодо іншого підходу:

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

Тепер розділіть свій світ на сітку і поставте кожен об’єкт у список усіх комірок, на які він впливає.

Робіть ваші гравітаційні обчислення лише на об'єктах у списку, приєднаних до клітинки, в якій знаходиться об'єкт.

Оновити списки потрібно лише тоді, коли об’єкт переміщується в нову комірку сітки.

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

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