Расчет кадров в секунду в игре - PullRequest
102 голосов
/ 18 сентября 2008

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

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

Ответы [ 17 ]

93 голосов
/ 18 сентября 2008

Вам нужно сглаженное среднее, самый простой способ - взять текущий ответ (время рисования последнего кадра) и объединить его с предыдущим ответом.

// eg.
float smoothing = 0.9; // larger=more smoothing
measurement = (measurement * smoothing) + (current * (1.0-smoothing))

Регулируя соотношение 0,9 / 0,1, вы можете изменить «постоянную времени» - то есть как быстро число реагирует на изменения. Большая доля в пользу старого ответа дает более медленное плавное изменение, большая доля в пользу нового ответа дает более быстрое изменение значения. Очевидно, что два фактора должны добавить к одному!

44 голосов
/ 18 сентября 2008

Это то, что я использовал во многих играх.

#define MAXSAMPLES 100
int tickindex=0;
int ticksum=0;
int ticklist[MAXSAMPLES];

/* need to zero out the ticklist array before starting */
/* average will ramp up until the buffer is full */
/* returns average ticks per frame over the MAXSAMPLES last frames */

double CalcAverageTick(int newtick)
{
    ticksum-=ticklist[tickindex];  /* subtract value falling off */
    ticksum+=newtick;              /* add new value */
    ticklist[tickindex]=newtick;   /* save new value so it can be subtracted later */
    if(++tickindex==MAXSAMPLES)    /* inc buffer index */
        tickindex=0;

    /* return average */
    return((double)ticksum/MAXSAMPLES);
}
22 голосов
/ 18 сентября 2008

ну конечно же

frames / sec = 1 / (sec / frame)

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

То, что вам нужно, это, вероятно, скользящее среднее или какой-то счетчик биннинга / сброса.

Например, вы могли бы поддерживать структуру данных очереди, которая содержала бы время рендеринга для каждого из последних 30, 60, 100 или того, что у вас есть (вы могли бы даже спроектировать его так, чтобы предел был настраиваем при время). Чтобы определить приличное fps приближение, вы можете определить среднее fps из всех времен рендеринга в очереди:

fps = # of rendering times in queue / total rendering time

Когда вы заканчиваете рендеринг нового кадра, вы ставите в очередь новое время рендеринга и удаляете старое время рендеринга. С другой стороны, вы можете удалить из очереди только в том случае, если сумма времени рендеринга превысила какое-либо предварительно установленное значение (например, 1 сек). Вы можете сохранить «последнее значение fps» и последнюю обновленную временную метку, чтобы при желании вы могли запускать время обновления значения fps. Хотя при использовании скользящего среднего, если у вас постоянное форматирование, печать «мгновенного среднего» fps для каждого кадра, вероятно, будет в порядке.

Другим способом было бы иметь счетчик сброса. Поддерживайте точную (миллисекундную) метку времени, счетчик кадров и значение fps. Когда вы закончите рендеринг кадра, увеличьте счетчик. Когда счетчик достигает заданного предела (например, 100 кадров) или когда время, прошедшее с отметки времени, прошло какое-то предварительно установленное значение (например, 1 секунда), вычислите fps:

fps = # frames / (current time - start time)

Затем сбросьте счетчик до 0 и установите метку времени на текущее время.

11 голосов
/ 18 сентября 2008

Увеличивайте счетчик каждый раз при рендеринге экрана и очищайте его в течение некоторого интервала времени, в течение которого вы хотите измерить частоту кадров.

Т.е.. Каждые 3 секунды получайте счетчик / 3, а затем очищайте счетчик.

9 голосов
/ 17 октября 2011

Есть как минимум два способа сделать это:


Первый - это тот, о котором другие упоминали здесь до меня. Я думаю, что это самый простой и предпочтительный способ. Вы просто следить за

  • cn: счетчик количества отображаемых кадров
  • time_start: время с начала подсчета
  • time_now: текущее время

Вычисление fps в этом случае так же просто, как вычисление этой формулы:

  • FPS = cn / (time_now - time_start).

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

Допустим, у вас есть кадры 'i' для рассмотрения. Я буду использовать эту запись: f [0], f [1], ..., f [i-1], чтобы описать, сколько времени потребовалось для рендеринга кадра 0, кадра 1, ..., кадра (i-1 ) соответственно.

Example where i = 3

|f[0]      |f[1]         |f[2]   |
+----------+-------------+-------+------> time

Тогда математическое определение fps после i кадров будет

(1) fps[i]   = i     / (f[0] + ... + f[i-1])

И та же формула, но только с учетом кадров i-1.

(2) fps[i-1] = (i-1) / (f[0] + ... + f[i-2]) 

Теперь хитрость заключается в том, чтобы изменить правую часть формулы (1) таким образом, чтобы она содержала правую часть формулы (2), и заменить ее левой стороной.

Примерно так (вы должны видеть это более четко, если напишите на бумаге):

fps[i] = i / (f[0] + ... + f[i-1])
       = i / ((f[0] + ... + f[i-2]) + f[i-1])
       = (i/(i-1)) / ((f[0] + ... + f[i-2])/(i-1) + f[i-1]/(i-1))
       = (i/(i-1)) / (1/fps[i-1] + f[i-1]/(i-1))
       = ...
       = (i*fps[i-1]) / (f[i-1] * fps[i-1] + i - 1)

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

5 голосов
/ 23 мая 2013

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

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

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

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

// Number of past frames to use for FPS smooth calculation - because 
// Unity's smoothedDeltaTime, well - it kinda sucks
private int frameTimesSize = 60;
// A Queue is the perfect data structure for the smoothed FPS task;
// new values in, old values out
private Queue<float> frameTimes;
// Not really needed, but used for faster updating then processing 
// the entire queue every frame
private float __frameTimesSum = 0;
// Flag to ignore the next frame when performing a heavy one-time operation 
// (like changing resolution)
private bool _fpsIgnoreNextFrame = false;

//=============================================================================
// Call this after doing a heavy operation that will screw up with FPS calculation
void FPSIgnoreNextFrame() {
    this._fpsIgnoreNextFrame = true;
}

//=============================================================================
// Smoothed FPS counter updating
void Update()
{
    if (this._fpsIgnoreNextFrame) {
        this._fpsIgnoreNextFrame = false;
        return;
    }

    // While looping here allows the frameTimesSize member to be changed dinamically
    while (this.frameTimes.Count >= this.frameTimesSize) {
        this.__frameTimesSum -= this.frameTimes.Dequeue();
    }
    while (this.frameTimes.Count < this.frameTimesSize) {
        this.__frameTimesSum += Time.deltaTime;
        this.frameTimes.Enqueue(Time.deltaTime);
    }
}

//=============================================================================
// Public function to get smoothed FPS values
public int GetSmoothedFPS() {
    return (int)(this.frameTimesSize / this.__frameTimesSum * Time.timeScale);
}
2 голосов
/ 14 ноября 2008

Хорошие ответы здесь. То, как вы это реализуете, зависит от того, для чего вам это нужно. Я предпочитаю скользящее среднее одно "время = время * 0,9 + последний_фрейм * 0,1" парнем выше.

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

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

Итак, я бы также добавил что-то вроде этого:

#define ONE_OVER_FPS (1.0f/60.0f)
static float g_SpikeGuardBreakpoint = 3.0f * ONE_OVER_FPS;
if(time > g_SpikeGuardBreakpoint)
    DoInternalBreakpoint()

(заполните 3.0f любой величиной, которую вы считаете неприемлемым всплеском) Это позволит вам найти и, таким образом, решить FPS-проблемы, в конце кадра они происходят.

2 голосов
/ 17 ноября 2015

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

new_fps = old_fps * 0.99 + new_fps * 0.01

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

1 голос
/ 24 января 2017

JavaScript:

// Set the end and start times
var start = (new Date).getTime(), end, FPS;
  /* ...
   * the loop/block your want to watch
   * ...
   */
end = (new Date).getTime();
// since the times are by millisecond, use 1000 (1000ms = 1s)
// then multiply the result by (MaxFPS / 1000)
// FPS = (1000 - (end - start)) * (MaxFPS / 1000)
FPS = Math.round((1000 - (end - start)) * (60 / 1000));
1 голос
/ 17 февраля 2013

Как мне это сделать!

boolean run = false;

int ticks = 0;

long tickstart;

int fps;

public void loop()
{
if(this.ticks==0)
{
this.tickstart = System.currentTimeMillis();
}
this.ticks++;
this.fps = (int)this.ticks / (System.currentTimeMillis()-this.tickstart);
}

На словах тиковые часы отслеживают тики. Если это первый раз, он берет текущее время и помещает его в 'tickstart'. После первого тика переменная 'fps' становится равной количеству тиков часов тика, деленных на время минус время первого тика.

Fps - целое число, следовательно, "(int)".

...