Як інтерполювати між двома ігровими станами?


24

Яка найкраща модель для створення системи, щоб усі позиції об'єктів були інтерпольовані між двома станами оновлення?

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

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

Напівфіксований або Повністю фіксований графік часу?

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


2
тики є логічними кліщами? Отже, ваше оновлення fps <рендерінг fps?
Качка комуніста

Я змінив термін. Але так логічні кліщі. І ні, я хочу повністю звільнити візуалізацію від оновлення, тому гра може відображатися на швидкості 120 Гц або 22,8 Гц, і оновлення все одно буде працювати з тією ж швидкістю, за умови, що користувач відповідає системним вимогам.
AttackingHobo

це може бути дуже складним, оскільки під час надання всіх позицій об'єкта має залишатися нерухомим (зміна їх під час процесу візуалізації може спричинити певну не визначену поведінку)
Ali1S232,

Інтерполяція обчислює стан за один раз між двома вже обчисленими кадрами оновлення. Чи не це питання щодо екстраполяції, обчислення стану за час після останнього кадру оновлення? Оскільки наступне оновлення ще не піддано перерахунку.
Майк Семер

Я думаю, що якщо у нього є лише одна нитка оновлення / візуалізації, не може статися повторного оновлення просто позиції рендерінгу. Ви просто відправляєте позиції до GPU, а потім повторно оновлюєтесь.
zacharmarz

Відповіді:


22

Ви хочете розділити показники оновлення (логічний галочок) та намалювати (візуалізувати) ставки.

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

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

1.

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

Для цього кожен об’єкт, який буде намальований, повинен мати пов'язані velocityта position. Щоб знайти позицію, що об'єкт буде знаходитись у наступному кадрі, ми просто додаємо velocity * draw_timestepдо поточного положення об'єкта, щоб знайти передбачуване положення наступного кадру.draw_timestep- кількість часу, що минув з моменту попереднього галочки візуалізації (aka попередній дзвінок дзвінка).

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

2.

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


На мою думку, ви, мабуть, хочете інтерполяції, як я це описав, оскільки це простіше здійснити з двох, і намалювати крихітну частку секунди (наприклад, 1/60 секунди) позаду вашого поточного оновленого стану, це добре.


Редагувати:

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

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

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

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

Об'єкт зберігає дві його копії previous_state. Я поміщу їх у масив і позначаю їх як previous_state[0]і previous_state[1]. Так само потрібні дві його копії current_state.

Щоб відслідковувати, яка копія подвійного буфера використовується, ми зберігаємо змінну state_index, яка доступна як для потоку оновлення, так і для малювання.

Нитка оновлення спочатку обчислює всі властивості об'єкта, використовуючи власні дані (будь-які структури даних, які ви хочете). Потім він копіює current_state[state_index]в previous_state[state_index]і копіює нові дані , що відносяться до малювання, positionі rotationв current_state[state_index]. Потім це роблять state_index = 1 - state_index, щоб перевернути поточно використану копію подвійного буфера.

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

У потоці візуалізації ви робите лінійну інтерполяцію щодо положення та обертання таким чином:

current_position = Lerp(previous_state[state_index].position, current_state[state_index].position, elapsed/update_tick_length)

Де elapsedкількість часу, що минула в потоці візуалізації, починаючи з останнього галочки оновлення, і update_tick_lengthскільки часу займає фіксований показник оновлення на один галочку (наприклад, при оновленнях 20 кадрів в секунду, update_tick_length = 0.05).

Якщо ви не знаєте, що таке Lerpфункція вище, ознайомтесь із статтею Вікіпедії на тему: Лінійна інтерполяція . Однак якщо ви не знаєте, що таке лерпінг, то, ймовірно, ви не готові реалізувати розв'язане оновлення / малювання з інтерпольованим малюнком.


1
+1 те ж саме потрібно зробити для орієнтацій / обертань та всіх інших станів, які змінюються з часом, тобто як матеріальні анімації в системах частинок тощо.
Maik Semder,

1
Добрий момент, Майку, я просто використав позицію як приклад. Вам потрібно зберігати "швидкість" будь-якої властивості, яку ви хочете екстраполювати (тобто швидкість зміни за цей час), якщо ви хочете використовувати екстраполяцію. Зрештою, я дійсно не можу придумати ситуацію, коли екстраполяція краща, ніж інтерполяція, я включив її лише тому, що запитувач запитував її. Я використовую інтерполяцію. За допомогою інтерполяції нам потрібно зберігати поточні та попередні результати оновлення будь-яких властивостей для інтерполяції, як ви сказали.
Ольховський

Це перестановка проблеми та різниця між інтерполяцією та екстраполяцією; це не відповідь.

1
У своєму прикладі я зберігав положення та обертання в стані. Ви також можете просто зберігати швидкість (або швидкість) у штаті. Тоді ви переходите між швидкістю точно так само ( Lerp(previous_speed, current_speed, elapsed/update_tick_length)). Це можна зробити з будь-яким номером, який ви хочете зберегти в державі. Лерпінг просто дає значення між двома значеннями з урахуванням коефіцієнта лерпу.
Ольховський

1
Для інтерполяції кутових рухів рекомендується використовувати slerp замість lerp. Найпростіше було б зберігати кватерніони обох штатів і рівновагу між ними. В іншому випадку ті ж правила застосовуються для кутової швидкості та кутового прискорення. Чи є у вас тест-кейс для скелетної анімації?
Майк Семдер

-2

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

Скажімо, у вас є мавпа в положенні x. Тепер у вас також є "addX", до якого ви додаєте позицію мавпи за кадром на основі клавіатури або якогось іншого елемента управління. Це буде працювати, якщо у вас є гарантована частота кадрів. Скажімо, ваш x - 100, а ваш addX - 10. Після 10 кадрів ваш x + = addX повинен накопичитися до 200.

Тепер замість addX, коли у вас є змінна частота кадрів, ви повинні думати з точки зору швидкості та прискорення. Я проведу вас через всю цю арифметику, але це дуже просто. Що ми хочемо знати, - як далеко ви хочете подорожувати за одну мілісекунду (1/1000 секунди)

Якщо ви знімаєте 30 FPS, то ваш velX повинен бути 1/3 секунди (10 кадрів з останнього прикладу при 30 FPS), і ви знаєте, що ви хочете подорожувати 100 'x' за цей час, тому встановіть свій velX на 100 відстань / 10 FPS або 10 відстань на кадр. У мілісекундах це працює на 1 відстань x за 3,3 мілісекунди або 0,3 'x' на мілісекунд.

Тепер, щоразу, коли ви оновлюєтесь, все, що вам потрібно зробити, - це з’ясувати час, що минув. Незалежно від того, чи пройшло 33 мс (1/30 секунди), ви просто помножите відстань 0,3 на кількість пройдених мілісекунд. Це означає, що вам потрібен таймер, який дає точність мс (мілісекунд), але більшість таймерів дає вам це. Просто зробіть щось подібне:

var beginTime = getTimeInMillisecond ()

... пізніше ...

var time = getTimeInMillisecond ()

var elapsedTime = час-beginTime

beginTime = час

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


1
Він не має змінної швидкості оновлення. Він має фіксовану швидкість оновлення. Якщо чесно, то я справді не знаю, який момент ви тут намагаєтеся зробити: /
Ольховський,

1
??? -1. Це вся суть, у мене є гарантована швидкість оновлення, але змінна швидкість візуалізації, і я хочу, щоб вона була гладкою, без заїкання.
AttackingHobo

Змінна швидкість оновлення не дуже добре працює з мережевими іграми, конкурентними іграми, системами перегляду чи будь-яким іншим, що залежить від детермінованості гри.
AttackingHobo

1
Фіксоване оновлення також дозволяє легко інтегрувати псевдо-тертя. Наприклад, якщо ви хочете помножити свою швидкість на 0,9 в кожному кадрі, як ви зрозумієте, на скільки помножити, якщо у вас швидкий або повільний кадр? Фіксоване оновлення іноді дуже бажано - практично всі фізичні симуляції використовують фіксовану швидкість оновлення.
Ольховський

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