Обчислення кадрів в секунду в грі


110

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

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

Відповіді:


100

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

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

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


14
Тоді для дуростійкості та охайності ви, напевно, хочете чогось на кшталт поплавкової вагиRatio = 0,1; and time = time * (1.0 - weightRatio) + last_frame * weightRatio
korona

2
В принципі звучить добре і просто, але насправді згладжування такого підходу ледь помітно. Не добре.
Петрусіо

1
@Petrucio, якщо згладжування занадто низьке, просто закрутіть константу часу (weightRatio = 0,05, 0,02, 0,01 ...)
Джон Дворак

8
@Petrucio: last_frameне означає (або принаймні не повинен означати) тривалість попереднього кадру; це повинно означати значення, timeяке ви обчислили для останнього кадру. Таким чином, всі попередні кадри будуть включені, причому останні останні кадри зважуються найбільш сильно.
j_random_hacker

1
Назва змінної "last_frame" вводить в оману. "current_frame" буде більш описовим. Важливо також знати, що змінна "час" у прикладі повинна бути глобальною (тобто зберігати її значення у всіх кадрах) і в ідеалі число з плаваючою комою. Він містить середнє / сукупне значення та оновлюється на кожному кадрі. Чим вище коефіцієнт, тим більше часу знадобиться для врегулювання змінної "time" у напрямку до значення (або відхилення його від нього).
jox

52

Це те, що я використовував у багатьох іграх.

#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);
}

Мені дуже подобається такий підхід. Будь-яка конкретна причина, чому ви встановите MAXSAMPLES на 100?
Золомон

1
MAXSAMPLES - це кількість значень, які усереднюються, щоб знайти значення для fps.
Cory Gross

8
Це проста
ковзаюча

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

2
Будь ласка, використовуйте модуль, а не якщо. tickindex = (tickindex + 1) % MAXSAMPLES;
Фелікс К.

25

Ну, звичайно

frames / sec = 1 / (sec / frame)

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

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

Наприклад, ви можете підтримувати структуру даних черги, яка утримувала час візуалізації для кожного з останніх кадрів 30, 60, 100, або що-у вас є (ви навіть могли спроектувати його так, щоб ліміт був регульований під час виконання). Щоб визначити гідне наближення кадрів в секунду, ви можете визначити середній кадр в секунду за всі часи візуалізації в черзі:

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

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

Іншим методом може бути лічильник скидання. Підтримуйте точну (мілісекундну) часову позначку, лічильник кадрів та значення fps. Закінчивши надання кадру, збільшуйте лічильник. Коли лічильник досягає встановленої межі (наприклад, 100 кадрів) або коли час, коли часова марка минула якесь задане значення (наприклад, 1 сек), обчисліть fps:

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

Потім скиньте лічильник на 0 і встановіть часову позначку на поточний час.


12

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

Тобто Кожні 3 секунди отримуйте лічильник / 3, а потім очищайте лічильник.


+1 Хоча це дасть вам нове значення лише з інтервалом, це легко зрозуміти і не вимагає ані масивів, ані відгадування значень і є науково правильним.
opatut

10

Існують як мінімум два способи:


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

  • cn: лічильник кількості кадрів, які ви надали
  • time_start: час, коли ви почали рахувати
  • time_now: поточний час

Розрахунок кадрів в секунду в цьому випадку простий, як і оцінка цієї формули:

  • FPS = cn / (time_now - time_start).

Тоді є класний спосіб uber, яким ви хочете скористатися якийсь день:

Скажімо, у вас є "я" кадри для розгляду. Я буду використовувати це позначення: f [0], f [1], ..., f [i-1], щоб описати, скільки часу знадобилося, щоб візуалізувати кадр 0, кадр 1, ..., кадр (i-1 ) відповідно.

Example where i = 3

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

Тоді було б математичне визначення кадрів у кадрі після кадрів 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)

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


1
+1 для другого методу. Я думаю, було б добре для точного обчислення uber: 3
zeboidlund

5

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

Він зберігає чергу з останніми часами кадру, тому він може точно обчислити середнє значення 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

Тут хороші відповіді. Від того, як ви це реалізуєте, залежить те, що вам потрібно. Я більше віддаю перевагу середньому бігу "час = час * 0,9 + останній кадр * 0,1" хлопцем вище.

однак мені особисто подобається наближати свій середній показник до нових даних, тому що в грі саме СКЕЙСИ найскладніші для сквош, і, таким чином, найбільше цікавлять мене. Тож я б скористався чимось більш подібним до розбиття .7 \ .3, зробить шип відображатися набагато швидше (хоча ефект також випаде з екрана швидше. Дивіться нижче)

Якщо ваша увага приділяється часу відпуску, то розбиття .9.1 працює досить непогано, але вона, як правило, є більш гладкою. Хоча для геймплея / AI / фізики сплески викликають набагато більше занепокоєння, оскільки це, як правило, робить вашу гру виглядаючою (що часто гірше, ніж низька частота кадрів, якщо ми не зануримось нижче 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 в кінці кадру, який вони трапляються.


Мені подобається time = time * 0.9 + last_frame * 0.1середній розрахунок, завдяки якому дисплей плавно змінюється.
Fabien Quatravaux

2

Набагато краща система, ніж використання великого масиву старих кадрів - це просто зробити щось подібне:

new_fps = old_fps * 0.99 + new_fps * 0.01

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


1

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


1

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

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

import time

SMOOTHING_FACTOR = 0.99
MAX_FPS = 10000
avg_fps = -1
last_tick = time.time()

while True:
    # <Do your rendering work here...>

    current_tick = time.time()
    # Ensure we don't get crazy large frame rates, by capping to MAX_FPS
    current_fps = 1.0 / max(current_tick - last_tick, 1.0/MAX_FPS)
    last_tick = current_tick
    if avg_fps < 0:
        avg_fps = current_fps
    else:
        avg_fps = (avg_fps * SMOOTHING_FACTOR) + (current_fps * (1-SMOOTHING_FACTOR))
    print(avg_fps)

0

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


0

У (п ++ подібному) псевдокоді ці два - це те, що я використовував у промислових програмах обробки зображень, яким доводилося обробляти зображення з набору зовнішньо спрацьованих камер. Варіанти "частоти кадрів" мали інше джерело (повільніше або швидше виробництво на поясі), але проблема однакова. (Я припускаю, що у вас є простий виклик timer.peek (), який дає вам щось на зразок nr msec (nsec?) З моменту запуску програми або останнього дзвінка)

Рішення 1: швидко, але не оновлюється кожен кадр

do while (1)
{
    ProcessImage(frame)
    if (frame.framenumber%poll_interval==0)
    {
        new_time=timer.peek()
        framerate=poll_interval/(new_time - last_time)
        last_time=new_time
    }
}

Рішення 2: оновлений кожен кадр, потребує більше пам'яті та процесора

do while (1)
{
   ProcessImage(frame)
   new_time=timer.peek()
   delta=new_time - last_time
   last_time = new_time
   total_time += delta
   delta_history.push(delta)
   framerate= delta_history.length() / total_time
   while (delta_history.length() > avg_interval)
   {
      oldest_delta = delta_history.pop()
      total_time -= oldest_delta
   }
} 

0
qx.Class.define('FpsCounter', {
    extend: qx.core.Object

    ,properties: {
    }

    ,events: {
    }

    ,construct: function(){
        this.base(arguments);
        this.restart();
    }

    ,statics: {
    }

    ,members: {        
        restart: function(){
            this.__frames = [];
        }



        ,addFrame: function(){
            this.__frames.push(new Date());
        }



        ,getFps: function(averageFrames){
            debugger;
            if(!averageFrames){
                averageFrames = 2;
            }
            var time = 0;
            var l = this.__frames.length;
            var i = averageFrames;
            while(i > 0){
                if(l - i - 1 >= 0){
                    time += this.__frames[l - i] - this.__frames[l - i - 1];
                }
                i--;
            }
            var fps = averageFrames / time * 1000;
            return fps;
        }
    }

});

0

Як я це роблю!

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);
}

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

Fps - ціле число, отже, "(int)".


1
Не рекомендував би нікому. Розмежування загальної кількості тиків на загальну кількість секунд змушує FPS наближатись до чогось подібного до математичного обмеження, де він в основному встановлюється на 2-3 значення через тривалий час і показує неточні результати.
Kartik Chugh

0

Ось як я це роблю (на Java):

private static long ONE_SECOND = 1000000L * 1000L; //1 second is 1000ms which is 1000000ns

LinkedList<Long> frames = new LinkedList<>(); //List of frames within 1 second

public int calcFPS(){
    long time = System.nanoTime(); //Current time in nano seconds
    frames.add(time); //Add this frame to the list
    while(true){
        long f = frames.getFirst(); //Look at the first element in frames
        if(time - f > ONE_SECOND){ //If it was more than 1 second ago
            frames.remove(); //Remove it from the list of frames
        } else break;
        /*If it was within 1 second we know that all other frames in the list
         * are also within 1 second
        */
    }
    return frames.size(); //Return the size of the list
}

0

У Typescript я використовую цей алгоритм для обчислення середніх частот частот і частот:

let getTime = () => {
    return new Date().getTime();
} 

let frames: any[] = [];
let previousTime = getTime();
let framerate:number = 0;
let frametime:number = 0;

let updateStats = (samples:number=60) => {
    samples = Math.max(samples, 1) >> 0;

    if (frames.length === samples) {
        let currentTime: number = getTime() - previousTime;

        frametime = currentTime / samples;
        framerate = 1000 * samples / currentTime;

        previousTime = getTime();

        frames = [];
    }

    frames.push(1);
}

використання:

statsUpdate();

// Print
stats.innerHTML = Math.round(framerate) + ' FPS ' + frametime.toFixed(2) + ' ms';

Порада: Якщо зразків дорівнює 1, результатом є частота кадрів у режимі реального часу та час виконання.


0

Це ґрунтується на відповіді KPexEA і дає просте рухоме середнє. Прибрати та перетворити на TypeScript для легкої копіювання та вставки:

Змінна декларація:

fpsObject = {
  maxSamples: 100,
  tickIndex: 0,
  tickSum: 0,
  tickList: []
}

Функція:

calculateFps(currentFps: number): number {
  this.fpsObject.tickSum -= this.fpsObject.tickList[this.fpsObject.tickIndex] || 0
  this.fpsObject.tickSum += currentFps
  this.fpsObject.tickList[this.fpsObject.tickIndex] = currentFps
  if (++this.fpsObject.tickIndex === this.fpsObject.maxSamples) this.fpsObject.tickIndex = 0
  const smoothedFps = this.fpsObject.tickSum / this.fpsObject.maxSamples
  return Math.floor(smoothedFps)
}

Використання (може змінюватись у вашій програмі):

this.fps = this.calculateFps(this.ticker.FPS)

-1

зберігати час початку та збільшувати свій кадровий кадра один раз за цикл? кожні кілька секунд ви можете просто надрукувати кількість кадрів / (зараз - початковий час), а потім повторно активізувати їх.

редагувати: ой. подвійний ніндзя

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