Частота кадров и время кадра

Плавность анимации измеряют не «красивостью», а числом кадров в секунду и временем на один кадр.

FPS (frames per second) — сколько готовых кадров система успевает показать за секунду; frame time — сколько миллисекунд ушло на один кадр.

Зачем это знать

Графика реального времени — это гонка со временем. Монитор обновляется, например, 60 раз в секунду, и у вас есть жёсткий бюджет на кадр. Если не уложиться, кадр «опоздает», и пользователь увидит рывок. Оптимизация шейдеров и сцены — это, по сути, борьба за миллисекунды.

Связь FPS и времени кадра

Время одного кадра — это просто единица, делённая на FPS. При 60 кадрах в секунду на кадр приходится около 16.7 мс. Это и есть ваш бюджет: всё — обновление логики, расчёт вершин, растеризация, фрагментные шейдеры, постобработка — должно уместиться сюда.

for fps in [30, 60, 120, 144]:
    ms = 1000.0 / fps
    print(f"{fps} FPS  ->  {ms:.2f} мс на кадр")

Вывод:

30 FPS  ->  33.33 мс на кадр
60 FPS  ->  16.67 мс на кадр
120 FPS  ->  8.33 мс на кадр
144 FPS  ->  6.94 мс на кадр

Почему средний FPS обманывает

FPS — усреднённая величина. Если 59 кадров отрисовались мгновенно, а один завис на 100 мс, средний FPS будет высоким, но пользователь почувствует именно тот один рывок. Поэтому профессионалы смотрят на распределение времени кадров, особенно на «1% low» — худшие кадры.

frame_times = [16.0, 16.5, 16.2, 50.0, 16.1, 16.3, 16.4, 16.0]  # мс
n = len(frame_times)
avg = sum(frame_times) / n
avg_fps = 1000.0 / avg
worst = max(frame_times)
print("Средний frame time:", round(avg, 2), "мс")
print("Средний FPS:", round(avg_fps, 1))
print("Худший кадр:", worst, "мс ->", round(1000.0 / worst, 1), "FPS в этот момент")

Вывод:

Средний frame time: 20.44 мс
Средний FPS: 48.9
Худший кадр: 50.0 мс -> 20.0 FPS в этот момент

Средние 49 FPS выглядят терпимо, но просадка до 20 FPS на один кадр — это видимый рывок (stutter).

Как работает под капотом

Кадр проходит конвейер: процессор готовит команды отрисовки (draw calls) и шлёт их видеокарте, та исполняет вершинные и фрагментные шейдеры, заполняет фреймбуфер. Узким местом может быть и CPU (слишком много команд), и GPU (тяжёлые шейдеры, много пикселей). Если включён VSync, готовый раньше времени кадр ждёт следующего обновления монитора — поэтому при 60 Гц мониторе FPS «залипает» на 60, 30 или 20, а не плавно меняется.

Частые ошибки

  • Гнаться за высоким средним FPS, игнорируя редкие, но заметные просадки.
  • Двигать объекты на фиксированную величину за кадр без учёта delta time — тогда на быстром компьютере всё летает, а на медленном ползёт.
  • Путать частоту обновления монитора (Гц) и FPS приложения: монитор 60 Гц не покажет больше 60 разных кадров в секунду.

Итоги

  • Время кадра = 1000 / FPS; при 60 FPS бюджет ~16.7 мс.
  • Средний FPS скрывает рывки — смотрите на худшие кадры (frame time).
  • Движение привязывайте к delta time, чтобы скорость не зависела от железа.
  • VSync устраняет разрывы, но «квантует» FPS под частоту монитора.
Проверьте себя
1. Сколько миллисекунд на кадр даёт частота 60 FPS?
A33.3 мс
B16.7 мс
C8.3 мс
D60 мс
2. Почему один тяжёлый кадр среди лёгких портит ощущение плавности, хотя средний FPS высокий?
AFPS не зависит от времени кадра
BПользователь чувствует именно этот рывок, а среднее его сглаживает
CМонитор удаляет такие кадры
DЭто невозможно
3. Зачем привязывать движение к delta time?
AЧтобы тратить меньше памяти
BЧтобы скорость объектов не зависела от FPS железа
CЧтобы отключить VSync
DЧтобы увеличить разрешение