Interpolation - Can I make it better?

Started by
9 comments, last by Khatharr 6 years, 9 months ago

Hello everyone,

I’m finishing up learning about interpolation and wanted to see if my code is calculating in the best way possible. When I programmed a toggle to turn it off and on, I can visually see smooth motion almost at any frame rate. The only issue is when render frames start to go lower than 30, it’s not as jittery and doesn’t skip with it on, with it off it will jitter, and sometimes warp around if the updates are not called fast enough. (This is normal as logic and input is going at 30 ticks per second, and rendering is happening as fast as possible. I did some extra code to lock the render frames at lower FPS to test it.)

The basic loop code for the Time Step (I Took some stuff out just to make the example clearer, so you won’t see my loops code or max frames, ect… as I have code to break out of the loop if it falls behind.):

 


const float ticksPerSecond = 30.f; // Updates per second
const float skipTicks = 1000.f / ticksPerSecond; // This would be 33.33333333333333
sf::Clock mainClock;
double nextTick = mainClock.restart().asMilliseconds();

while (mainClock.getElapsedTime().asMilliseconds() > nextTick) {
			
  // Updates – LOGIC
  testSpritePosPrev = testSpritePos;

  if (sf::Keyboard::isKeyPressed(sf::Keyboard::Up)) {
    testSpritePos.y -= testSpriteSpeed;
  }

  if (sf::Keyboard::isKeyPressed(sf::Keyboard::Down)) {
    testSpritePos.y += testSpriteSpeed;
  }

  if (sf::Keyboard::isKeyPressed(sf::Keyboard::Left)) {
    testSpritePos.x -= testSpriteSpeed;
  }

  if (sf::Keyboard::isKeyPressed(sf::Keyboard::Right)) {
    testSpritePos.x += testSpriteSpeed;
  }

  nextTick += skipTicks;
}

interpolation = float(((mainClock.getElapsedTime().asMilliseconds() + skipTicks) - nextTick)) / (skipTicks);

if (toggleInterpolation == true) {
	testSprite.setPosition(testSpritePosPrev.x + ((testSpritePos.x - testSpritePosPrev.x) * interpolation), testSpritePosPrev.y + ((testSpritePos.y - testSpritePosPrev.y) * interpolation));
}
else {
  testSprite.setPosition(testSpritePos.x, testSpritePos.y);
}

// Clear Window
mainWindow.clear();

// Draw to Window
mainWindow.draw(testSprite);

// Display
mainWindow.display();

 

Just to clarify. All logic and Input is handled at 30 ticks per second, and the rendering happens as fast as possible with no limitation.

Programmer and 3D Artist

Advertisement

Try this out


constexpr Int32 logicFPS = 30;
constexpr Int32 msPerTick = 1000 / logicFPS;
constexpr float ticksPerMS = 1.0f / msPerTick; //to avoid frivolous divisions

sf::Clock mainClock;

Int32 nextTickTime = 0;
while(running) {
  Int32 tickStart = nextTickTime;
  nextTickTime += msPerTick;
  
  sf::Vector2f displacement(0, 0);
  if(sf::Keyboard::isKeyPressed(sf::Keyboard::Up))    { displacement.y--; }
  if(sf::Keyboard::isKeyPressed(sf::Keyboard::Down))  { displacement.y++; }
  if(sf::Keyboard::isKeyPressed(sf::Keyboard::Left))  { displacement.x--; }
  if(sf::Keyboard::isKeyPressed(sf::Keyboard::Right)) { displacement.x++; }
  displacement.normalize(); //this is not an sfml function, so you'll have to write one (divide the vector by its own length)
  displacement *= testSpriteSpeed;
  
  auto prevPos = testSpritePos;
  testSpritePos += displacement;

  for(Int32 now = mainClock.getElapsedTime().asMilliseconds(); now < nextTickTime; now = mainClock.getElapsedTime().asMilliseconds()) {
    float alpha = float(now - tickStart) * ticksPerMS;
    sf::Vector2f offset = displacement * alpha;
   
    testSprite.setPosition(prevPos + offset);

    mainWindow.clear();
    mainWindow.draw(testSprite);
    mainWindow.display();
  }
  
}

It's untested so it may have a snag or two, but I think this is closer to what you're aiming for.

void hurrrrrrrr() {__asm sub [ebp+4],5;}

There are ten kinds of people in this world: those who understand binary and those who don't.
2 hours ago, Khatharr said:

Try this out



constexpr Int32 logicFPS = 30;
constexpr Int32 msPerTick = 1000 / logicFPS;
constexpr float ticksPerMS = 1.0f / msPerTick; //to avoid frivolous divisions

sf::Clock mainClock;

Int32 nextTickTime = 0;
while(running) {
  Int32 tickStart = nextTickTime;
  nextTickTime += msPerTick;
  
  sf::Vector2f displacement(0, 0);
  if(sf::Keyboard::isKeyPressed(sf::Keyboard::Up))    { displacement.y--; }
  if(sf::Keyboard::isKeyPressed(sf::Keyboard::Down))  { displacement.y++; }
  if(sf::Keyboard::isKeyPressed(sf::Keyboard::Left))  { displacement.x--; }
  if(sf::Keyboard::isKeyPressed(sf::Keyboard::Right)) { displacement.x++; }
  displacement.normalize(); //this is not an sfml function, so you'll have to write one (divide the vector by its own length)
  displacement *= testSpriteSpeed;
  
  auto prevPos = testSpritePos;
  testSpritePos += displacement;

  for(Int32 now = mainClock.getElapsedTime().asMilliseconds(); now < nextTickTime; now = mainClock.getElapsedTime().asMilliseconds()) {
    float alpha = float(now - tickStart) * ticksPerMS;
    sf::Vector2f offset = displacement * alpha;
   
    testSprite.setPosition(prevPos + offset);

    mainWindow.clear();
    mainWindow.draw(testSprite);
    mainWindow.display();
  }
  
}

It's untested so it may have a snag or two, but I think this is closer to what you're aiming for.

 

Thank you for the response. I made a test run, and coded in a function to normalize the displacement with some modifications. The end result is pretty much what I'm dealing with. I guess I'll stick to my above code as it keeps my Time Step in it's original form. Unless I'm wrong (please correct me) your code is just a different way to accomplish the same end result assuming less stutter at lower render rates?

Programmer and 3D Artist

Your code is running multiple logic updates and then rendering a single interpolated frame. My code is running a logic update and then rendering multiple interpolated frames until the next tick is due.

In both cases the tighter loop can be skipped if the time is already elapsed, so it may come out similarly, but I felt that this approach was more intentional.

void hurrrrrrrr() {__asm sub [ebp+4],5;}

There are ten kinds of people in this world: those who understand binary and those who don't.
13 minutes ago, Khatharr said:

Your code is running multiple logic updates and then rendering a single interpolated frame. My code is running a logic update and then rendering multiple interpolated frames until the next tick is due.

In both cases the tighter loop can be skipped if the time is already elapsed, so it may come out similarly, but I felt that this approach was more intentional.

Hello Khatharr,

Logic updates are happening at a fixed rate of 30 ticks per second, and rendering as fast as possible otherwise. On my test application I'm taking in 30 ticks per second, while the entire render is being hit around 3000 times per second, which means the interpolation is being calculated for every render in a given second, which could be 3000+ times.

The example would look something like this based on the Time Step. (Note: This is a fixed time step while allowing rendering to be independent of this.)

- Logic/Input Update - Hit 1 (wait until another 0.033333 secs passes based on game time to cycle through another logic/input loop)

- Draw (Render) This could happen 100 times before the next Logic/Input Update is due (during this time input isn't even allow anyhow)


In Update x 1

Rendering x 100

In Update x 1

Rendering x 110

In Update x 1

ect... Update would repeat in this fashion around 30 times, which in turn would have around 3000 render updates as well.
 

Unless I'm confused here, we're both running logic updates within the tick time, and rendering until the next tick is due. This is exactly how the Time Step is programmed to work. Please let me know if I'm missing something as I didn't get any different result from both methods, assuming we're doing the same thing (different approaches), but doing logic updates per tick cycle, and rendering in the remaining time until the next tick is due.

Programmer and 3D Artist

Quote

Programs must be written for people to read, and only incidentally for machines to execute.

If you're running an update and then rendering repeatedly until the next one, then put the render in the inner-loop instead of the update.

(That was my take, anyway.)

void hurrrrrrrr() {__asm sub [ebp+4],5;}

There are ten kinds of people in this world: those who understand binary and those who don't.
1 hour ago, Khatharr said:

If you're running an update and then rendering repeatedly until the next one, then put the render in the inner-loop instead of the update.

It's been a long day, and I'm not able to follow what you're saying. Having the interpolation run outside of the logic for updates is how you're supposed to do this according to deWitters Game Loop, otherwise it wouldn't show smooth movement on render. It uses a sub-frame interpolation method.

http://www.koonsolo.com/news/dewitters-gameloop/

You run all logic and input updates, then right before the render you interpolate, and update the new view position of the object. This loop is exactly like the very last example in which we have Constant Game Speed independent of Variable FPS.

The graphics are running very smooth, just at lower render frames you can see the jitters. The reason for the post was to see if I could get rid of this because I've seen videos in the past were this was solved, however it could be possible they were using a Variable Time Step and calculating where an object started at, where it should be based on speed and current time passed, and interpolate based on that information.

My movement is handled based on a maximum of 30 input ticks per second at a speed of 5 pixels per update cycle, which wouldn't allow more than 150 pixels to be moved per second.

I have two videos - set videos to 1080p, it defaults to 480p:

VIDEO 1 - Running with toggle at 5000 render frames + (very hard to notice a bit b/c the upload forces FPS)

https://vid.me/pWW6

VIDEO 2 - Running with interpolation on at 58 render frames - You will see the image skip sometimes.

https://vid.me/3QQV

I appreciate your input and help!

I appreciate your input and help!

Programmer and 3D Artist

I'm not sure that it's your interpolation causing that. I'd output a frame log and try to find the specifics of what's going on before proceeding.

void hurrrrrrrr() {__asm sub [ebp+4],5;}

There are ten kinds of people in this world: those who understand binary and those who don't.
4 hours ago, Khatharr said:

I'm not sure that it's your interpolation causing that. I'd output a frame log and try to find the specifics of what's going on before proceeding.

Yea, I'll need to break down what's going on to see. On the plus side, it's not such a major issue thankfully that will make or break the game for now.

Thanks again for all your help. I'm so use to using frameworks that handle all this for me, so I haven't been in a situation that required me to do all this manually before. I know the "quick fix" is to just put on VSYNC, but I cannot always guarantee this is working because on graphic cards you can just globally set it off.

Programmer and 3D Artist

You also don't know the refresh rate of the monitor.

void hurrrrrrrr() {__asm sub [ebp+4],5;}

There are ten kinds of people in this world: those who understand binary and those who don't.

This topic is closed to new replies.

Advertisement