Какой смысл обновлять независимый рендеринг в игровом цикле?

74

Есть десятки статей, книг и дискуссий по игровым циклам. Тем не менее, я довольно часто сталкиваюсь с чем-то вроде этого:

while(running)
{
    processInput();
    while(isTimeForUpdate)
    {
        update();
    }
    render();
}

Что в основном беспокоит меня об этом подходе, так это «независимый от обновления» рендеринг, например, рендеринг кадра, когда вообще нет изменений. Поэтому мой вопрос: почему этому подходу часто учат?

Сэм
источник
6
Лично я нашел gameprogrammingpatterns.com/game-loop.html полезным объяснением
Нильс
12
Не все изменения в рендеринге отражаются на состоянии игры. И я подозреваю, что вы неправильно понимаете смысл этого фрагмента кода - он позволяет обновлять несколько раз за рендер, а не рендерить несколько раз за обновление.
Луаан
13
Обратите внимание, что код читает while (isTimeForUpdate), а не if (isTimeForUpdate). Основная цель не в том, render()когда не было update(), но update()многократно между render()s. Независимо от того, обе ситуации имеют действительное использование. Первый будет действителен, если состояние может измениться вне вашей updateфункции, например, изменить то, что отображается на основе неявного состояния, такого как текущее время. Последнее действительно, потому что оно дает вашему физическому движку возможность выполнять множество небольших точных обновлений, что, например, снижает вероятность «коробления» через препятствия.
Тьерри
Более логичным вопросом будет «в чем смысл игрового цикла рендеринга, зависящего от обновления »
user1306322
1
Как часто у вас есть обновление, которое ничего не делает? Даже не обновляете фоновую анимацию или экранные часы?
pjc50

Ответы:

113

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

1. Проблема: устройства работают на разных скоростях

Вы когда-нибудь пытались сыграть в старую игру для DOS на современном ПК, и она работает неиграбельно быстро - просто размытие?

У многих старых игр был очень наивный цикл обновления - они собирали входные данные, обновляли состояние игры и рендерили так быстро, как позволяло оборудование, без учета того, сколько времени прошло. Это означает, что как только меняется оборудование, меняется и игровой процесс.

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

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

Верным решением здесь является блокировка скорости, с которой мы делаем обновления состояния геймплея. Таким образом, мы можем гарантировать, что результаты всегда будут одинаковыми.

2. Так почему бы просто не заблокировать частоту кадров (например, с помощью VSync) и по-прежнему запускать обновления и рендеринг состояния игрового процесса в режиме lockstep?

Это может работать, но не всегда приемлемо для аудитории. Было долгое время, когда работа на скорости 30 кадров в секунду считалась золотым стандартом для игр. Теперь игроки обычно ожидают минимальную полосу 60 кадров в секунду, особенно в многопользовательских экшен-играх, а некоторые старые игры теперь выглядят заметно изменчиво, поскольку наши ожидания изменились. Также есть вокальная группа игроков на ПК, которые вообще возражают против блокировок кадров. Они платят много за свое новейшее оборудование и хотят использовать эту вычислительную мощь для максимально плавного и высокоточного рендеринга, на который он способен.

В VR, в частности, частота кадров является королем, и стандарт продолжает набирать обороты. В начале недавнего возрождения виртуальной реальности игры часто работали со скоростью около 60 кадров в секунду. Теперь 90 более стандартен, и аппаратное обеспечение, подобное PSVR, начинает поддерживать 120. Это может продолжаться и дальше. Таким образом, если игра VR ограничивает частоту кадров тем, что выполнимо и принято сегодня, она может остаться позади, так как аппаратное обеспечение и ожидания будут развиваться дальше.

(Как правило, будьте осторожны, когда говорят, что «игроки не могут воспринимать что-либо быстрее, чем XXX», поскольку это обычно основано на определенном типе «восприятия», например, на распознавании кадра в последовательности. Восприятие непрерывности движения обычно гораздо более чувствительный.)

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

Это может быть особенно проблематично, если проблемы с производительностью обнаруживаются на поздних стадиях разработки, когда все ваши существующие системы собраны и настроены с учетом частоты кадров при рендеринге, которую вы не всегда можете решить. Разъединение скорости обновления и рендеринга дает больше гибкости при работе с изменчивостью производительности.

3. Не имеет ли обновление с фиксированным временным шагом те же проблемы, что и (2)?

Я думаю, что это основа первоначального вопроса: если мы отделяем наши обновления и иногда визуализируем два кадра без обновлений между состояниями игры, то разве это не то же самое, что рендеринг в режиме lockstep при более низкой частоте кадров, поскольку видимых изменений на экран?

На самом деле есть несколько разных способов, которыми игры могут эффективно использовать развязку этих обновлений:

а) частота обновления может быть выше, чем частота кадров

Как отмечает Tyjkenn в другом ответе, физика, в частности, часто имеет более высокую частоту, чем рендеринг, что помогает минимизировать ошибки интеграции и дает более точные столкновения. Таким образом, вместо 0 или 1 обновлений между визуализированными кадрами у вас может быть 5, 10 или 50.

Теперь игрок, выполняющий рендеринг со скоростью 120 кадров в секунду, может получать 2 обновления за кадр, в то время как игрок с более низкой спецификацией аппаратного рендеринга со скоростью 30 кадров в секунду получает 8 обновлений за кадр, и обе их игры работают с одинаковой скоростью «тики в секунду в реальном времени». Более качественное оборудование делает его более гладким, но не меняет радикально, как работает геймплей.

Здесь есть риск, что, если частота обновления не соответствует частоте кадров, вы можете получить «частоту ударов» между ними . Например. у большинства фреймов у нас достаточно времени для 4 обновлений состояния игры и небольшого остатка, затем время от времени у нас достаточно накопленных ресурсов, чтобы сделать 5 обновлений в фрейме, делая небольшой скачок или заикание в движении. Это можно решить с помощью ...

б) интерполяция (или экстраполяция) игрового состояния между обновлениями

Здесь мы часто будем позволять игровому состоянию жить в одном фиксированном временном шаге в будущем и хранить достаточно информации из двух самых последних состояний, чтобы мы могли отобразить произвольную точку между ними. Затем, когда мы готовы показать новый кадр на экране, мы смешиваемся с соответствующим моментом только для отображения (т. Е. Здесь мы не изменяем основное игровое состояние)

Если все сделано правильно, это делает движение более плавным и даже помогает скрыть некоторые колебания частоты кадров, пока мы не опустимся слишком низко.

c) Добавление плавности к изменениям не игрового состояния

Даже без интерполяции состояния геймплея мы все же можем получить некоторые плавные выигрыши.

Чисто визуальные изменения, такие как анимация персонажей, системы частиц или VFX, и элементы пользовательского интерфейса, такие как HUD, часто обновляются отдельно от фиксированного временного шага состояния игрового процесса. Это означает, что если мы помечаем наше игровое состояние несколько раз за кадр, мы не платим их стоимость с каждым тиком - только на последнем проходе рендеринга. Вместо этого мы масштабируем скорость воспроизведения этих эффектов, чтобы соответствовать длине кадра, чтобы они воспроизводились настолько плавно, насколько позволяет частота кадров рендеринга, не влияя на скорость или честность игры, как обсуждалось в (1).

Движение камеры может сделать это тоже - особенно в VR, мы иногда показываем один и тот же кадр более одного раза, но перепроектируем его, чтобы учесть движение головы игрока между ними , чтобы мы могли улучшить воспринимаемую задержку и комфорт, даже если мы можем не рендерит все так быстро. Некоторые системы потоковой передачи игр (где игра запускается на сервере, а игрок запускает только тонкий клиент) также используют эту версию.

4. Почему бы просто не использовать этот стиль (с) для всего? Если это работает для анимации и пользовательского интерфейса, разве мы не можем просто масштабировать наши обновления состояния игрового процесса в соответствии с текущей частотой кадров?

Да * это возможно, но нет, это не просто.

Этот ответ уже немного длинный, поэтому я не буду вдаваться в подробности, просто краткое резюме:

  • Умножение на deltaTimeработает, чтобы приспособиться к обновлениям переменной длины для линейного изменения (например, движение с постоянной скоростью, обратный отсчет таймера или прогресс по временной шкале анимации)

  • К сожалению, многие аспекты игр являются нелинейными . Даже такая простая вещь, как гравитация, требует более сложных методов интеграции или подшагов с более высоким разрешением, чтобы избежать расхождения результатов при различных частотах кадров. Ввод и управление плеером само по себе является огромным источником нелинейности.

  • В частности, результаты дискретного обнаружения и разрешения коллизий зависят от частоты обновления, что приводит к ошибкам туннелирования и дрожания, если кадры становятся слишком длинными. Таким образом, переменная частота кадров заставляет нас использовать более сложные / дорогие методы непрерывного обнаружения столкновений для большей части нашего контента или терпеть изменчивость нашей физики. Даже при непрерывном обнаружении столкновений возникают проблемы, когда объекты движутся по дугам, что требует более коротких временных шагов ...

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

Стандартизация частоты обновления позволяет нам гарантировать более согласованное поведение в различных условиях , часто с более простым кодом.

Сохранение этой частоты обновлений в отрыве от рендеринга дает нам гибкость в управлении плавностью и производительностью опыта без изменения логики игрового процесса .

Даже тогда мы никогда не получим действительно «идеальную» независимость от частоты кадров, но, как и многие другие подходы в играх, она дает нам управляемый метод, позволяющий набрать «достаточно хороший» для потребностей данной игры. Вот почему это обычно преподносится как полезная отправная точка.

Д.М.Григорий
источник
2
В случаях, когда все будут использовать одну и ту же частоту кадров, синхронизация всего может минимизировать задержку между моментом, когда выполняется выборка контроллера, и тем, как реагирует состояние игры. Для многих игр на некоторых старых машинах наихудшее время будет менее 17 мс (элементы управления считываются в начале вертикального пробела, затем применяются изменения состояния игры, и следующий кадр отображается при движении луча вниз по экрану) , Разделение вещей часто приводит к значительному увеличению времени наихудшего случая.
суперкат
3
Хотя более сложные конвейеры обновления упрощают непреднамеренное введение задержки, это не является необходимым следствием несвязанного подхода при правильной реализации. На самом деле, мы можем даже уменьшить время ожидания. Давайте возьмем игру, которая рендерит со скоростью 60 кадров в секунду. В режиме блокировки read-update-renderнаша задержка в худшем случае составляет 17 мс (без учета графического конвейера и задержки отображения). При (read-update)x(n>1)-renderразомкнутом цикле с той же частотой кадров задержка в худшем случае может быть только одинаковой или лучшей, потому что мы проверяем и действуем на входе так часто или чаще. :)
DMGregory
3
Что касается интересного примечания о старых играх, не учитывающих фактическое время, у оригинальной аркады Space Invaders был сбой, вызванный силой рендеринга, когда игрок уничтожал вражеские корабли, рендеринг и, следовательно, обновления игры ускорялись, приводя к случайному , в культовой кривой сложности игры.
Оскуро
1
@DMGregory: если все сделано асинхронно, то изменение элемента управления будет возможно сразу после того, как игровой цикл опрашивает элементы управления, цикл игровых событий после текущего завершается сразу после того, как цикл рендеринга захватывает игровое состояние, и чтобы цикл рендеринга после текущего завершился сразу после того, как система вывода видео захватит текущий буфер кадра, так что время наихудшего случая заканчивается тем, что оно просто не учитывает два времени цикла игры плюс два времени рендеринга плюс два периода кадра. Правильная синхронизация может сократить это вдвое.
суперкат
1
@ Оскуро: Это был не глюк. Скорость цикла обновления остается постоянной независимо от того, сколько захватчиков находится на экране, но игра не отображает всех захватчиков в каждом цикле обновления.
суперкат
8

Другие ответы хороши и говорят о том, почему игровой цикл существует и должен быть отделен от цикла рендеринга. Тем не менее, что касается конкретного примера «Зачем визуализировать кадр, если не было никаких изменений?» Это действительно сводится к аппаратному обеспечению и сложности.

Видеокарты - это конечные автоматы, и они действительно способны делать одно и то же снова и снова. Если вы визуализируете только то, что изменилось, это на самом деле дороже, а не меньше. В большинстве сценариев нет ничего статичного: если вы немного переместитесь влево в FPS-игре, вы изменили пиксельные данные 98% материала на экране, вы также можете визуализировать весь кадр.

Но в основном сложность. Отслеживание всего, что изменилось во время обновления, намного дороже, потому что вы должны либо все переделывать, либо отслеживать старый результат какого-либо алгоритма, сравнивать его с новым результатом и отображать этот пиксель только в том случае, если изменение отличается. Это зависит от системы.

Конструкция аппаратного обеспечения и т. Д. В значительной степени оптимизирована для существующих соглашений, и конечный автомат был хорошей моделью для начала.

ковыляет
источник
5
Здесь необходимо провести различие между рендерингом (всего) только тогда, когда что-то изменилось (о чем вопрос), и рендерингом только тех частей , которые изменились (что описывает ваш ответ).
DMGregory
Это правда, и я попытался провести различие между первым и вторым абзацами. Редко, когда кадр вообще не меняется, поэтому я подумал, что важно придерживаться этого мнения вместе с вашим исчерпывающим ответом.
Удачи
В дополнение к этому я хотел бы отметить, что нет никаких причин, чтобы не визуализировать. Вы знаете, что у вас всегда есть время для ваших вызовов рендеринга в каждом кадре (вам лучше знать, что у вас всегда есть время для ваших вызовов рендеринга в каждом кадре!), Поэтому делать «ненужный» рендеринг очень мало вреда - тем более что этот случай будет по сути, никогда не подходит на практике.
Стивен Стадницки
@StevenStadnicki Это правда, что не стоит тратить много времени на рендеринг всего в каждом кадре (так как вам нужно иметь время в бюджете, чтобы делать это в любом случае, когда много состояний изменяется одновременно). Но для портативных устройств, таких как ноутбуки, планшеты, телефоны или портативные игровые системы, целесообразно избегать избыточного рендеринга, чтобы эффективно использовать батарею игрока . Это в основном относится к играм с интенсивным пользовательским интерфейсом, где большие части экрана могут оставаться неизменными для кадров между действиями игрока, и не всегда будет практичным для реализации в зависимости от архитектуры игры.
DMGregory
6

Рендеринг обычно самый медленный процесс в игровом цикле. Люди не могут легко заметить разницу в частоте кадров быстрее 60, поэтому зачастую менее важно тратить время на рендеринг быстрее, чем это. Тем не менее, существуют и другие процессы, которые выиграли бы от более высокой скорости. Физика одна. Слишком большое изменение в одной петле может привести к тому, что объекты будут пролетать мимо стен. Могут быть способы обойти простые ошибки столкновения на больших приращениях, но для многих сложных физических взаимодействий вы просто не получите ту же точность. Однако, если физический цикл выполняется чаще, вероятность глюков меньше, поскольку объекты можно перемещать с меньшими приращениями без визуализации каждый раз. Больше ресурсов уходит на чувствительный физический движок и меньше тратится на рисование большего количества кадров, которые пользователь не видит.

Это особенно важно в играх с большим количеством графики. Если для каждого игрового цикла был один рендер, и у игрока не было самой мощной машины, в игре могут быть точки, в которых число кадров в секунду падает до 30 или 40. Хотя это все равно будет не совсем ужасная частота кадров, игра начала бы работать довольно медленно, если бы мы старались, чтобы каждое физическое изменение было достаточно небольшим, чтобы избежать сбоев. Игрок будет раздражен тем, что его персонаж ходит только вдвое медленнее. Однако, если скорость рендеринга не зависит от остальной части цикла, игрок сможет оставаться на фиксированной скорости ходьбы, несмотря на снижение частоты кадров.

tyjkenn
источник
1
Или, например, измерить время между тиками и посчитать, как далеко персонаж должен зайти за это время? Дни фиксированной анимации спрайтов давно прошли!
Грэм
2
Люди не могут воспринимать вещи быстрее, чем 60 кадров в секунду без временного алиасинга, но частота кадров, необходимая для достижения плавного движения в отсутствие размытия, может быть намного выше. Помимо определенной скорости, вращающееся колесо должно выглядеть как размытие, но если программное обеспечение не применяет размытие при движении и колесо поворачивается более чем на половину спицы на кадр, колесо может выглядеть плохо, даже если частота кадров составляла 1000 кадров в секунду.
суперкат
1
@ Грэхем, тогда ты столкнешься с проблемой из моего первого абзаца, где такие вещи, как физика, ведут себя плохо с большим изменением за такт. Если частота кадров падает достаточно низко, компенсация с большими изменениями может привести к тому, что игрок пробежит прямо через стену, полностью пропустив свой хит-бокс.
Тыкенн
1
Человек обычно не может воспринимать скорость более 60 кадров в секунду, поэтому бессмысленно тратить ресурсы на рендеринг быстрее. Я не согласен с этим утверждением. VR HMD рендерится на частоте 90 Гц, и это идет вверх. Поверьте мне, когда я говорю вам, что вы МОЖНО ощутить разницу между 90 Гц и 60 Гц на гарнитуре. Кроме того, в последнее время я видел столько игр, которые связаны с CPU, так и с GPU. Сказать «рендеринг обычно самый медленный процесс» не обязательно верно.
3Dave
@DavidLively, так что "бессмысленно", возможно, было слишком преувеличением. Я имел в виду, что рендеринг, как правило, является узким местом, и большинство игр выглядят хорошо при 60 кадрах в секунду. Конечно, есть некоторые эффекты, которые важны в некоторых играх, таких как VR, которые вы можете получить только с более высокой частотой кадров, но они кажутся скорее исключением, чем нормой. Если бы я запускал игру на более медленной машине, я бы предпочел работать с физикой, а не с едва заметной быстрой частотой кадров.
Тыкенн
4

Конструкция, подобная той, что в вашем вопросе, может иметь смысл, если подсистема рендеринга имеет некоторое представление о «прошедшем времени с момента последнего рендеринга» .

Рассмотрим, например, подход, при котором положение объекта в игровом мире представляется через фиксированные (x,y,z)координаты с подходом, который дополнительно сохраняет текущий вектор движения (dx,dy,dz). Теперь вы можете написать свой игровой цикл так, чтобы изменение позиции происходило в updateметоде, но вы также можете спроектировать его так, чтобы изменение движения происходило во время update. С последним подходом, даже если ваше игровое состояние на самом деле не изменится до следующего update,render-функция, которая вызывается на более высокой частоте, может уже нарисовать объект в слегка обновленном положении. Хотя это технически приводит к несоответствию между тем, что вы видите, и тем, что представлено внутри, разница достаточно мала, чтобы не иметь значения для большинства практических аспектов, но позволяет анимации выглядеть намного более плавно.

Прогнозирование «будущего» состояния вашей игры (несмотря на риск ошибочности) может быть хорошей идеей, если принять во внимание, например, задержки сетевого ввода.

Томас
источник
4

В дополнение к другим ответам ...

Проверка на изменение состояния требует значительной обработки. Если для проверки изменений требуется одинаковое (или больше!) Время обработки, по сравнению с фактическим выполнением обработки, вы действительно не улучшили ситуацию. В случае рендеринга изображения, как говорит @Waddles, видеокарта действительно хороша в том, чтобы снова и снова делать одну и ту же глупую вещь, и проверять каждый кусок данных на предмет изменений дороже, чем просто передавать их через на видеокарту для обработки. Кроме того, если рендеринг является игровым процессом, то вряд ли экран не изменился бы в последнем тике.

Вы также предполагаете, что рендеринг занимает значительное процессорное время. Это очень сильно зависит от вашего процессора и видеокарты. В течение многих лет основное внимание уделялось постепенной передаче более сложной работы по рендерингу на видеокарту и уменьшению входных данных, необходимых для процессора. В идеале render()вызов процессора должен просто установить передачу DMA и все. Получение данных на графическую карту затем делегируется контроллеру памяти, а создание изображения делегируется на графическую карту. Они могут делать это в свое время, в то время как процессор параллельнопродолжает физику, игровой движок и все остальное, что процессор делает лучше. Очевидно, что реальность намного сложнее, но возможность перекладывать работу на другие части системы также является существенным фактором.

Грэхем
источник