Frame Independent Problem

Started by
14 comments, last by Medo Mex 7 years, 12 months ago

Hi Guys,

I'm using QueryPerformanceCounter() to get the elapsed time in seconds in a float variable.

Then I multiply the elapsed time with the velocity as the following in entity Update(float timeElapsed) function:


void Update(float timeElapsed)
{
velocity *= timeElapsed;
setPosition(getPosition() + velocity); // Now, update the entity position based on velocity
velocity = D3DXVECTOR3(0.0f, 0.0f, 0.0f);
...
...

I can notice that the entity movement speed is not the same when the frame rate drop, for example If I enable VSync during rendering to drop the frame rate to 60, the entity move faster than when the frame rate is 130

Any idea what could be causing this problem?

Advertisement

How do you calculate the velocity in the first place? Normalize it before multiplying it with the delta time and see if that makes it move at the same speed regardless of the frame rate. If that helps then it means that the calculations of the velocity themselves depend on the frame rate.


I'm using QueryPerformanceCounter() to get the elapsed time in seconds in a float variable.

How exactly are you using QPC to get the elapsed time?


I can notice that the entity movement speed is not the same when the frame rate drop

Whenever your framerate drops the time elapsed increases which is the expected obviously. In your example, when the elapsed time varies, so do an entity speed. A solution to that is use a fixed time step, which won't guarantee that your frame rate is the same every time, but will integrate your position in a more deterministic way. Read this article:

http://gafferongames.com/game-physics/fix-your-timestep/

Could you paste your game loop here?

@GuyWithBeard: Even after I normalize the velocity, I still have the same problem.

@Irlan Robson:

In class construction:


LARGE_INTEGER freq;
QueryPerformanceFrequency(&freq);
secondsPerCount = 1.0 / (double)freq.QuadPart;

Every frame:


void FrameUpdate()
{
     LARGE_INTEGER liCurrTime;
     QueryPerformanceCounter(&liCurrTime);
     currentTime = (double)liCurrTime.QuadPart;
     if (lastTime == 0)
     {
         lastTime = currentTime;
     }

     double timeElapsed = (currentTime - lastTime) * secondsPerCount;

     lastTime = currentTime;

     // Code here to do all the game updates...
     // ...
     // ...
}
Whatever behaviour you're trying to achieve, normalizing the velocity wouldn't help at all.

Your game loop is correct. Try disabling the v-sync to make sure your problem isn't related to the CPU and check the results. Also, you should rely on elapsed microseconds or miliseconds per frame rather than FPS for measurement accuracy.

@Irlan Robson:

@GuyWithBeard:

I notice that I don't have this problem if the entity is not assigned to the physics engine.

I'm using Bullet Physics and doing simulation as the following:


m_DynamicsWorld->stepSimulation(elapsedTime);

function Update(float timeElapsed)
{
velocity *= timeElapsed;
setPosition(getPosition() + velocity); // Now, update the entity position based on velocity
velocity = D3DXVECTOR3(0.0f, 0.0f, 0.0f);
...
...


The problem isn’t with the timers, but rather the units. The code is demoting velocity from units per second to units. For example:

1. Set velocity to 10 units per second (u/s).
2. Multiplying velocity by elapsed time in seconds (s) gives you a distance in units (u).
3. The result is then fed back into velocity-- reinterpret_casting from u to u/s, as it were. (BAD!)
4. Multiplying the new velocity in units with a the elapsed time gives you u*s units. (NO!)
5. Repeat steps 3-5, but with u*s^N units for velocity each iteration.


Be mindful of the units of your operands whenever you do any sort of math:
float3 distance_traveled = velocity * timeElapsed;  // u/s * s -> u
setPosition( getPosition() + distance_traveled );   // u + u -> u
If your intention was to have damping:
const float DAMPING = 0.8f;        // (scalar) reduces velocity 20% every 1 second (roughly) 
velocity *= DAMPING * timeElapsed; // u/s * scalar -> u/s
float3 distance_traveled = velocity * timeElapsed; // u/s * s -> u 
setPosition( getPosition() + velocity * timeElapsed ); // u + u -> u


Whatever behaviour you're trying to achieve, normalizing the velocity wouldn't help at all.

Well it helps in the sense that it narrows the problem down. I never meant he should leave the normalization in there (obviously).

Since the problem is still there after normalization, it means that the velocity value is fine (or at least it means that there is a problem somewhere else, ie. the delta time value).

Now, it's clear that the problem is caused by the physics engine, since the movement is frame independent only if I disable the physics engine or if I don't assign the entity to the physics engine.

Here is what I'm doing in the physics engine each frame:

1. Iterate through all the body dynamics and update their transformation based on the entities transformation

2. Simulate physics using: m_DynamicsWorld->stepSimulation(elapsedTime);

3. Iterate through all the body dynamics and update the entities transformation based on the rigid bodies transformation.

for a proper implementation of fixed timestep al la gaffer, you get ET, add it to your accumulator, then while the accumulator >=DT, you run one update and subrtract DT from the accumulator. so velocity will never be multiplied by ET, only perhaps by DT and/or some constant to make it run at the correct speed with fixed timestep. where DT is the fixed time step per update.

what you want to do is go with the final algo listed in the gaffer article. right now it looks like your using the 4th or 5th algo out of the six or so listed. he shows the evolution of the algo from naive brute force implementation up through fully functional with all cases covered. so only the last algo in the article works correctly with no flaws. what you're doing now looks like one of the algos just before the last one in that article.

here's timer code you can use to double check yours:

// timers
DWORD Ztimer[10];
LARGE_INTEGER Ztimer_freq,Ztimer2[10];

init:

QueryPerformanceFrequency(&Ztimer_freq);

// start timer a
void Zstarttimer(int a)
{
Ztimer[a]=GetTickCount();
QueryPerformanceCounter(&Ztimer2[a]);
}
// returns elapsedtime of timer a in millisecs
int Zelapsedtime(int a)
{
LARGE_INTEGER c,f;
float g,h;
QueryPerformanceCounter(&c);
f.QuadPart=c.QuadPart-Ztimer2[a].QuadPart;
g=(float)f.QuadPart;
h=(float)Ztimer_freq.QuadPart;
g*=1000.0f;
g/=h;
return((int)g);
}
// returns elapsed ticks of timer a
int Zelapsedticks(int a)
{
LARGE_INTEGER c,f;
QueryPerformanceCounter(&c);
f.QuadPart=c.QuadPart-Ztimer2[a].QuadPart;
return((int)f.QuadPart);
}

Norm Barrows

Rockland Software Productions

"Building PC games since 1989"

rocklandsoftware.net

PLAY CAVEMAN NOW!

http://rocklandsoftware.net/beta.php

This topic is closed to new replies.

Advertisement