게임에서 초당 프레임 계산


110

게임에서 초당 프레임을 계산하는 데 좋은 알고리즘은 무엇입니까? 화면 모서리에 숫자로 표시하고 싶습니다. 마지막 프레임을 렌더링하는 데 걸린 시간 만 보면 숫자가 너무 빠르게 변경됩니다.

답변이 각 프레임을 업데이트하고 프레임 속도가 증가 할 때와 감소 할 때 다르게 수렴하지 않으면 보너스 포인트를 얻을 수 있습니다.

답변:


100

평활화 된 평균이 필요합니다. 가장 쉬운 방법은 현재 답변 (마지막 프레임을 그리는 데 걸리는 시간)을 가져와 이전 답변과 결합하는 것입니다.

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

0.9 / 0.1 비율을 조정하여 '시정 수'를 변경할 수 있습니다. 즉, 숫자가 변화에 얼마나 빠르게 반응 하는지를 나타냅니다. 이전 답변을 선호하는 분수가 클수록 변경이 더 느리게 이루어지고 새 답변을 선호하는 분수가 클수록 값이 더 빠르게 변경됩니다. 분명히 두 가지 요소가 하나에 더해져야합니다!


14
그런 다음 바보 방지 및 깔끔함을 위해 아마도 float weightRatio = 0.1과 같은 것을 원할 것입니다. 및 시간 = 시간 * (1.0-weightRatio) + last_frame * weightRatio
korona

2
원칙적으로 훌륭하고 간단 해 보이지만 실제로는이 접근 방식의 평활화가 거의 눈에 띄지 않습니다. 좋지 않다.
Petrucio

1
@Petrucio 스무딩이 너무 낮 으면 시간 상수를 높이십시오 (weightRatio = 0.05, 0.02, 0.01 ...)
John Dvorak

8
@Petrucio : 이전 프레임의 지속 시간을 의미 last_frame하지 않습니다 (또는 적어도 의미 해서는 안 됨 ). time마지막 프레임에 대해 계산 한 값을 의미해야합니다 . 이렇게하면 모든 이전 프레임이 포함되고 가장 최근 프레임에 가장 많은 가중치가 적용됩니다.
j_random_hacker

1
변수 이름 "last_frame"은 잘못된 것입니다. "current_frame"이 더 설명 적입니다. 예제에서 변수 "시간"은 전역이어야하며 (즉, 모든 프레임에서 값을 유지) 이상적으로는 부동 소수점 숫자 여야한다는 것을 아는 것도 중요합니다. 평균 / 집계 값을 포함하고 각 프레임에서 업데이트됩니다. 비율이 높을수록 "시간"변수를 값으로 설정하는 데 더 오래 걸립니다 (또는 값에서 멀어짐).
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으로 설정 한 특별한 이유가 있습니까?
Zolomon 2011

1
여기서 MAXSAMPLES는 fps 값을 얻기 위해 평균화되는 값의 수입니다.
Cory Gross

8
평균 (SMA)을 이동 그것의 간단한
KindDragon

감사합니다! 나는 게임에서 그것을 조정하여 틱 func가 무효이고 다른 함수가 FPS를 반환 한 다음, 렌더링 코드에 FPS가 표시되지 않더라도 매 틱마다 메인을 실행할 수 있습니다.
TheJosh 2013

2
if가 아닌 모듈로를 사용하십시오. tickindex = (tickindex + 1) % MAXSAMPLES;
Felix K.

25

음, 확실히

frames / sec = 1 / (sec / frame)

그러나 지적했듯이 단일 프레임을 렌더링하는 데 걸리는 시간에는 많은 차이가 있으며 UI 관점에서 프레임 속도로 fps 값을 업데이트하는 것은 전혀 사용할 수 없습니다 (숫자가 매우 안정적이지 않은 경우).

당신이 원하는 것은 아마도 이동 평균이나 일종의 비닝 / 리셋 카운터 일 것입니다.

예를 들어, 마지막 30, 60, 100 또는 What-have-you 프레임 각각에 대한 렌더링 시간을 보유한 큐 데이터 구조를 유지할 수 있습니다 (런타임에 제한을 조정할 수 있도록 설계 할 수도 있음). 적절한 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으로 재설정하고 타임 스탬프를 현재 시간으로 설정합니다.


12

화면을 렌더링 할 때마다 카운터를 늘리고 프레임 속도를 측정하려는 일정 시간 간격 동안 해당 카운터를 지 웁니다.

즉. 3 초마다 카운터 / 3를 얻은 다음 카운터를 지 웁니다.


+1 이것은 간격의 새 값만 제공하지만 이해하기 쉽고 배열이나 추측 값이 필요하지 않으며 과학적으로 정확합니다.
opatut

10

이를 수행하는 방법은 최소한 두 가지입니다.


첫 번째는 다른 사람들이 여기서 언급 한 것입니다. 가장 간단하고 선호하는 방법이라고 생각합니다. 당신은 단지 추적하기 위해

  • cn : 렌더링 한 프레임 수 카운터
  • time_start : 계산을 시작한 이후의 시간
  • time_now : 현재 시간

이 경우 fps를 계산하는 것은 다음 공식을 평가하는 것만 큼 간단합니다.

  • FPS = cn / (시간 _ 현재-시간 _ 시작).

그런 다음 언젠가 사용하고 싶은 멋진 방법이 있습니다.

고려해야 할 'i'프레임이 있다고 가정 해 보겠습니다. 이 표기법을 사용할 것입니다 : f [0], f [1], ..., f [i-1] 프레임 0, 프레임 1, ..., 프레임 (i-1 ) 각각.

Example where i = 3

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

그러면 i 프레임 이후 fps의 수학적 정의는 다음과 같습니다.

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

그리고 동일한 공식이지만 i-1 프레임 만 고려합니다.

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

이제 여기서 트릭은 공식 (2)의 오른쪽을 포함하고 왼쪽을 대체하는 방식으로 공식 (1)의 오른쪽을 수정하는 것입니다.

이렇게 (종이에 쓰면 더 명확하게 볼 수 있습니다) :

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, 마지막 프레임을 렌더링하는 데 걸린 시간 및 사용한 프레임 수를 알아야합니다. 렌더링.


1
두 번째 방법은 +1입니다. 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

여기에 좋은 답변이 있습니다. 그것을 구현하는 방법은 필요한 것에 달려 있습니다. 나는 위의 사람이 "time = time * 0.9 + last_frame * 0.1"러닝 평균을 선호합니다.

그러나 저는 개인적으로 새로운 데이터에 대한 평균 가중치를 더 높이고 싶습니다. 게임에서 SPIKES가 가장 어렵 기 때문에 저에게 가장 관심이 많기 때문입니다. 따라서 .7 \ .3 분할과 같은 것을 사용하면 스파이크가 훨씬 더 빨리 표시됩니다 (효과가 화면에서 더 빨리 떨어집니다. 아래 참조).

RENDERING 시간에 초점을 맞추고 있다면 .9.1 분할이 꽤 잘 작동합니다. b / c가 더 부드럽습니다. 게임 플레이 / AI / 물리 스파이크의 경우 일반적으로 게임이 고르지 않게 보이기 때문에 훨씬 더 큰 문제가됩니다 (20fps 이하로 떨어지지 않는다고 가정 할 때 낮은 프레임 속도보다 종종 더 나쁩니다).

그래서 내가 할 일은 다음과 같은 것을 추가하는 것입니다.

#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 2012 년

2

이전 프레임 속도를 많이 사용하는 것보다 훨씬 더 나은 시스템은 다음과 같이하는 것입니다.

new_fps = old_fps * 0.99 + new_fps * 0.01

이 방법은 훨씬 적은 메모리를 사용하고 훨씬 적은 코드를 필요로하며 갑작스러운 프레임 속도 변경의 영향을 부드럽게하는 동시에 이전 프레임 속도보다 최근 프레임 속도에 더 많은 중요성을 둡니다.


1

카운터를 유지하고 각 프레임이 렌더링 된 후 증가한 다음 새로운 초에있을 때 카운터를 재설정 할 수 있습니다 (이전 값을 마지막 초의 렌더링 된 프레임 수로 저장).


1

자바 스크립트 :

// 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을 사용한 완전한 예입니다 (그러나 모든 언어에 쉽게 적용됨). Martin의 대답에서 평활 방정식을 사용하므로 메모리 오버 헤드가 거의 없으며 저에게 적합한 값을 선택했습니다 (사용 사례에 맞게 상수를 자유롭게 사용하십시오).

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으로 설정합니다. 프레임을 그릴 때마다 카운터가 증가합니다. 매초마다 카운터를 인쇄하십시오. 거품을 내고 헹구고 반복하십시오. 추가 크레딧을 원하면 러닝 카운터를 유지하고 러닝 평균의 총 시간 (초)으로 나눕니다.


0

(c ++ 유사) 의사 코드에서이 두 가지는 외부에서 트리거 된 카메라 세트의 이미지를 처리해야하는 산업용 이미지 처리 응용 프로그램에서 사용한 것입니다. "프레임 속도"의 변형은 소스가 다르지만 (벨트에서 더 느리거나 더 빠른 생산) 문제는 동일합니다. (애플리케이션 시작 또는 마지막 호출 이후 msec (nsec?)의 nr과 같은 것을 제공하는 간단한 timer.peek () 호출이 있다고 가정합니다)

솔루션 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 : 모든 프레임 업데이트, 더 많은 메모리 및 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

어떻게하나요!

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)"입니다.


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

시작 시간을 저장하고 루프 당 한 번 프레임 카운터를 증가 시키시겠습니까? 몇 초마다 framecount / (Now-starttime)를 인쇄 한 다음 다시 초기화 할 수 있습니다.

편집 : 죄송합니다. 이중 닌자

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.