Tính số khung hình trên giây trong trò chơi


110

Thuật toán tốt để tính số khung hình trên giây trong trò chơi là gì? Tôi muốn hiển thị nó dưới dạng số ở góc màn hình. Nếu tôi chỉ xem mất bao lâu để hiển thị khung hình cuối cùng, con số thay đổi quá nhanh.

Điểm thưởng nếu câu trả lời của bạn cập nhật từng khung hình và không hội tụ khác nhau khi tốc độ khung hình tăng so với giảm.

Câu trả lời:


100

Bạn cần một điểm trung bình mịn, cách đơn giản nhất là lấy câu trả lời hiện tại (thời điểm vẽ khung hình cuối cùng) và kết hợp nó với câu trả lời trước đó.

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

Bằng cách điều chỉnh tỷ lệ 0,9 / 0,1, bạn có thể thay đổi 'hằng số thời gian' - tức là số phản ứng nhanh như thế nào với các thay đổi. Một phần lớn hơn có lợi cho câu trả lời cũ cho một thay đổi chậm hơn mượt mà hơn, một phần lớn cho câu trả lời mới cho một giá trị thay đổi nhanh hơn. Rõ ràng là hai yếu tố phải thêm vào một!


14
Sau đó, để chống đánh lừa và gọn gàng, bạn có thể muốn một cái gì đó như float weightRatio = 0,1; và thời gian = thời gian * (1.0 - weightRatio) + last_frame * weightRatio
Korona

2
Về nguyên tắc thì nghe có vẻ hay và đơn giản, nhưng trên thực tế, cách làm này hầu như không đáng chú ý. Không tốt.
Petrucio

1
@Petrucio nếu độ mịn quá thấp, chỉ cần tăng hằng số thời gian (weightRatio = 0,05, 0,02, 0,01 ...)
John Dvorak

8
@Petrucio: last_framekhông có nghĩa là (hoặc ít nhất là không có nghĩa là) thời lượng của khung trước đó; nó có nghĩa là giá trị timemà bạn đã tính toán cho khung hình cuối cùng. Bằng cách này, tất cả các khung hình trước đó sẽ được bao gồm, với các khung hình gần đây nhất có trọng số cao nhất.
j_random_hacker

1
Tên biến "last_frame" gây hiểu lầm. "current_frame" sẽ mang tính mô tả nhiều hơn. Cũng cần biết rằng biến "thời gian" trong ví dụ này phải là biến toàn cục (tức là giữ giá trị của nó trên tất cả các khung) và lý tưởng là một số dấu phẩy động. Nó chứa giá trị trung bình / tổng hợp và được cập nhật trên mỗi khung hình. Tỷ lệ này càng cao, thì càng mất nhiều thời gian để giải quyết biến "thời gian" đối với một giá trị (hoặc làm trôi biến nó khỏi nó).
jox

52

Đây là những gì tôi đã sử dụng trong nhiều trò chơi.

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

Tôi thích cách tiếp cận này rất nhiều. Bất kỳ lý do cụ thể nào khiến bạn đặt MAXSAMPLES thành 100?
Zolomon

1
MAXSAMPLES ở đây là số lượng giá trị được tính trung bình để đưa ra giá trị cho khung hình / giây.
Cory Gross

8
Nó đơn giản của đường trung bình (SMA)
KindDragon

Hoàn hảo, cảm ơn! Tôi đã điều chỉnh nó trong trò chơi của mình để dấu phẩy func bị vô hiệu và một chức năng khác trả về FPS, sau đó tôi có thể chạy chức năng chính mỗi lần đánh dấu, ngay cả khi mã kết xuất không hiển thị FPS.
TheJosh

2
Vui lòng sử dụng modulo chứ không phải if. tickindex = (tickindex + 1) % MAXSAMPLES;
Felix K.

25

Vâng, chắc chắn

frames / sec = 1 / (sec / frame)

Tuy nhiên, như bạn đã chỉ ra, có rất nhiều sự thay đổi về thời gian cần thiết để hiển thị một khung hình duy nhất và từ góc độ giao diện người dùng, việc cập nhật giá trị khung hình / giây ở tốc độ khung hình là không thể sử dụng được (trừ khi con số rất ổn định).

Những gì bạn muốn có thể là một đường trung bình động hoặc một số loại bộ đếm binning / reset.

Ví dụ: bạn có thể duy trì cấu trúc dữ liệu hàng đợi giữ thời gian hiển thị cho mỗi khung trong số 30, 60, 100 hoặc khung hình cuối cùng của bạn (thậm chí bạn có thể thiết kế nó để giới hạn có thể điều chỉnh được tại thời điểm chạy). Để xác định xấp xỉ fps tốt, bạn có thể xác định fps trung bình từ tất cả thời gian hiển thị trong hàng đợi:

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

Khi bạn hoàn thành việc kết xuất một khung hình mới, bạn sắp xếp thời gian kết xuất mới và xếp lại thời gian kết xuất cũ. Ngoài ra, bạn chỉ có thể xếp hàng lại khi tổng số lần hiển thị vượt quá một số giá trị đặt trước (ví dụ: 1 giây). Bạn có thể duy trì "giá trị khung hình / giây cuối cùng" và dấu thời gian cập nhật lần cuối để bạn có thể kích hoạt thời điểm cập nhật số liệu khung hình / giây, nếu bạn muốn. Mặc dù với đường trung bình nếu bạn có định dạng nhất quán, việc in "trung bình tức thời" fps trên mỗi khung hình có lẽ sẽ ổn.

Một phương pháp khác là có một bộ đếm đặt lại. Duy trì dấu thời gian chính xác (mili giây), bộ đếm khung hình và giá trị khung hình / giây. Khi bạn hoàn thành kết xuất khung, hãy tăng bộ đếm. Khi bộ đếm đạt đến giới hạn đặt trước (ví dụ: 100 khung hình) hoặc khi thời gian kể từ khi dấu thời gian vượt qua một số giá trị đặt trước (ví dụ: 1 giây), hãy tính fps:

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

Sau đó đặt lại bộ đếm về 0 và đặt dấu thời gian thành thời điểm hiện tại.


12

Tăng bộ đếm mỗi khi bạn kết xuất màn hình và xóa bộ đếm đó trong một khoảng thời gian mà bạn muốn đo tốc độ khung hình.

I E. Cứ sau 3 giây, nhận bộ đếm / 3 và sau đó xóa bộ đếm.


+1 Mặc dù điều này sẽ chỉ cung cấp cho bạn một giá trị mới trong các khoảng thời gian, nhưng điều này rất dễ hiểu và không yêu cầu mảng cũng như không phải đoán giá trị và chính xác về mặt khoa học.
opatut

10

Có ít nhất hai cách để làm điều đó:


Đầu tiên là một trong những người khác đã đề cập ở đây trước tôi. Tôi nghĩ đó là cách đơn giản nhất và được ưa thích. Bạn chỉ cần theo dõi

  • cn: bộ đếm số lượng khung hình bạn đã hiển thị
  • time_start: thời gian kể từ khi bạn bắt đầu đếm
  • time_now: thời gian hiện tại

Tính fps trong trường hợp này đơn giản như đánh giá công thức này:

  • FPS = cn / (time_now - time_start).

Sau đó, có một cách tuyệt vời của uber mà bạn có thể muốn sử dụng vào một ngày nào đó:

Giả sử bạn có khung 'i' để xem xét. Tôi sẽ sử dụng ký hiệu này: f [0], f [1], ..., f [i-1] để mô tả mất bao lâu để hiển thị khung hình 0, khung hình 1, ..., khung hình (i-1 ) tương ứng.

Example where i = 3

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

Sau đó, định nghĩa toán học của fps sau khi tôi khung sẽ là

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

Và công thức tương tự nhưng chỉ xét đến khung i-1.

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

Bây giờ, mẹo ở đây là sửa đổi bên phải của công thức (1) theo cách nó sẽ chứa bên phải của công thức (2) và thay thế nó cho bên trái của nó.

Như vậy (bạn sẽ thấy nó rõ ràng hơn nếu bạn viết nó trên giấy):

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)

Vì vậy, theo công thức này (mặc dù kỹ năng tính toán của tôi hơi bị lỗi), để tính fps mới, bạn cần biết fps từ khung hình trước đó, khoảng thời gian cần thiết để hiển thị khung hình cuối cùng và số khung hình mà bạn kết xuất.


1
+1 cho phương pháp thứ hai. Tôi tưởng tượng nó sẽ là tốt cho tính toán chính xác uber: 3
zeboidlund

5

Điều này có thể là quá mức cần thiết đối với hầu hết mọi người, đó là lý do tại sao tôi đã không đăng nó khi tôi triển khai nó. Nhưng nó rất mạnh mẽ và linh hoạt.

Nó lưu trữ Hàng đợi với thời gian khung hình cuối cùng, vì vậy nó có thể tính toán chính xác giá trị FPS trung bình tốt hơn nhiều so với việc chỉ xem xét khung hình cuối cùng.

Nó cũng cho phép bạn bỏ qua một khung hình, nếu bạn đang làm điều gì đó mà bạn biết là sẽ làm sai lệch thời gian của khung hình đó một cách giả tạo.

Nó cũng cho phép bạn thay đổi số lượng khung hình cần lưu trữ trong Hàng đợi khi nó chạy, vì vậy bạn có thể kiểm tra nhanh đâu là giá trị tốt nhất cho mình.

// 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

Câu trả lời hay ở đây. Cách bạn triển khai nó phụ thuộc vào những gì bạn cần. Tôi thích mức trung bình chạy "time = time * 0.9 + last_frame * 0.1" của anh chàng ở trên.

tuy nhiên, cá nhân tôi thích đặt trọng số trung bình của mình nhiều hơn vào dữ liệu mới hơn bởi vì trong một trò chơi, SPIKES là thứ khó bị bóp méo nhất và do đó tôi quan tâm nhất. Vì vậy, tôi sẽ sử dụng một cái gì đó giống như một phân tách .7 \ .3 sẽ làm cho tăng đột biến hiển thị nhanh hơn nhiều (mặc dù hiệu ứng của nó cũng sẽ giảm ra ngoài màn hình nhanh hơn .. xem bên dưới)

Nếu bạn tập trung vào thời gian RENDERING, thì phần chia .9.1 hoạt động khá hiệu quả b / c nó có xu hướng trơn tru hơn. Mức độ đột biến về trò chơi / AI / vật lý là mối quan tâm nhiều hơn vì ĐÓ thường sẽ là thứ khiến trò chơi của bạn trông bị giật (thường tệ hơn tốc độ khung hình thấp giả sử chúng ta không giảm xuống dưới 20 khung hình / giây)

Vì vậy, những gì tôi sẽ làm là thêm một cái gì đó như thế này:

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

(điền 3.0f với bất kỳ độ lớn nào bạn thấy là mức tăng đột biến không thể chấp nhận được) Điều này sẽ cho phép bạn tìm thấy và do đó giải quyết các vấn đề FPS ở cuối khung hình mà chúng xảy ra.


Tôi thích time = time * 0.9 + last_frame * 0.1tính toán trung bình giúp màn hình thay đổi mượt mà.
Fabien Quatravaux

2

Một hệ thống tốt hơn nhiều so với việc sử dụng một loạt lớn tốc độ khung hình cũ là chỉ cần thực hiện một số việc như sau:

new_fps = old_fps * 0.99 + new_fps * 0.01

Phương pháp này sử dụng ít bộ nhớ hơn, yêu cầu ít mã hơn và đặt tầm quan trọng vào các tốc độ khung hình gần đây hơn so với các tốc độ khung hình cũ trong khi vẫn làm mượt các tác động của việc thay đổi tốc độ khung hình đột ngột.


1

Bạn có thể giữ một bộ đếm, tăng nó sau khi mỗi khung hình được hiển thị, sau đó đặt lại bộ đếm khi bạn đang ở một giây mới (lưu trữ giá trị trước đó làm số khung hình của giây cuối cùng được hiển thị)


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

Đây là một ví dụ hoàn chỉnh, sử dụng Python (nhưng dễ dàng điều chỉnh cho bất kỳ ngôn ngữ nào). Nó sử dụng phương trình làm mịn trong câu trả lời của Martin, vì vậy hầu như không tốn bộ nhớ và tôi đã chọn các giá trị phù hợp với mình (vui lòng thử với các hằng số để thích ứng với trường hợp sử dụng của bạn).

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

Đặt bộ đếm thành không. Mỗi lần bạn vẽ một khung sẽ tăng bộ đếm. Sau mỗi giây in bộ đếm. Lót, rửa sạch, lặp lại. Nếu bạn muốn có thêm tín dụng, hãy giữ một bộ đếm đang chạy và chia cho tổng số giây để có giá trị trung bình.


0

Trong mã giả (c ++ like), hai mã này là những gì tôi đã sử dụng trong các ứng dụng xử lý hình ảnh công nghiệp phải xử lý hình ảnh từ một tập hợp các camera được kích hoạt bên ngoài. Các biến thể về "tốc độ khung hình" có một nguồn khác (sản xuất chậm hơn hoặc nhanh hơn trên dây đai) nhưng vấn đề là giống nhau. (Tôi giả sử rằng bạn có một lệnh gọi timer.peek () đơn giản cung cấp cho bạn một cái gì đó giống như nr của msec (nsec?) Kể từ khi ứng dụng bắt đầu hoặc cuộc gọi cuối cùng)

Giải pháp 1: nhanh nhưng không cập nhật mọi khung hình

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

Giải pháp 2: Cập nhật mọi khung hình, yêu cầu thêm bộ nhớ và CPU

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

Làm thế nào tôi làm điều đó!

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

Nói cách khác, đồng hồ tích tắc theo dõi tích tắc. Nếu đó là lần đầu tiên, nó sẽ lấy thời gian hiện tại và đưa nó vào 'tickstart'. Sau dấu tích đầu tiên, nó làm cho biến 'fps' bằng bao nhiêu tích tắc của đồng hồ đánh dấu chia cho thời gian trừ đi thời gian của dấu tích đầu tiên.

Fps là một số nguyên, do đó "(int)".


1
Không muốn giới thiệu cho bất cứ ai. Chia tổng số tích tắc cho tổng số giây làm cho FPS tiếp cận một cái gì đó giống như một giới hạn toán học, nơi nó về cơ bản cố định trên 2-3 giá trị sau một thời gian dài và hiển thị kết quả không chính xác.
Kartik Chugh

0

Đây là cách tôi làm điều đó (trong 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

Trong Typecript, tôi sử dụng thuật toán này để tính toán tốc độ khung hình và thời gian trung bình:

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

sử dụng:

statsUpdate();

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

Mẹo: Nếu các mẫu là 1, kết quả là thời gian thực và tốc độ khung hình.


0

Điều này dựa trên câu trả lời của KPexEA và đưa ra Đường trung bình trượt đơn giản. Đã gọn gàng và chuyển đổi sang TypeScript để sao chép và dán dễ dàng:

Sự định nghĩa biến:

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

Chức năng:

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

Cách sử dụng (có thể khác nhau trong ứng dụng của bạn):

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

-1

lưu trữ thời gian bắt đầu và tăng bộ đếm khung hình của bạn một lần cho mỗi vòng lặp? cứ sau vài giây, bạn chỉ có thể in số lượng khung hình / (Bây giờ - thời gian bắt đầu) và sau đó khởi động lại chúng.

chỉnh sửa: oops. hai ninja

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.