Friday, August 16, 2013

Frame Interpolation

Doing things a bit out of order this week: a video first! Keep in mind, this is a tech video about framerates:

The first part of the video shows the game running at a rate of 20 physical frames per second. That is, the game updates its world model 20 times per second. Although the real rate will probably be 60 updates per second (as is common), the very small limit of 20 serves to illustrate the new feature better.

In this first part of the video, the game loop is running rather naively. Although the game only pushing out 20 physical frames per second, it's actually rendering hundreds of frames per second (on my 5 year old desktop, it's something like 750-850 FPS for the demo scene). But the game is still choppy, because it's still only updating the world model 20 times per second! With 750 rendered frames per second, it's actually rendering the same frame over and over about 37 times before the next physical update. What a waste!

The second part of the video is much smoother. You might think that I pumped up the physical frames per second to a much more reasonable number, like 60 or 200 (or maybe not--I think YouTube caps the framerate at something fairly low). But I didn't--the game is actually still running at 20 frames a second. Except now, I've introduced frame interpolation.

Frame interpolation is exactly what it sounds like--in between any two frames (namely, the last update and the update before that one), I am able to calculate an interpolated position. For example, let's say a monster was at position (30, 40) at frame #53, and at position (40, 50) at frame #54. The sequence of events is as follows:

1. The game update loop begins frame #54 (that is, the physical world updates--this has nothing to do with rendering!)
2. All entities (actually, just entities that need to be updated--more on this later) have their transformation stamped/saved. This occurs before anything else, so the stamped values represent the position, rotation, scale, etc. of the entity as it was at the end of frame #53.
3. The update loop runs as normal for frame #54. Entities might move around via controllers, scripts, collisions, or whatever.
4. At this point, the monster has a stamped position of (30, 40) and a current position of (40, 50).

Now that the update is complete, the rendering process begins. Let's say we're running at 20 physical frames per second--that's a whopping 50 milliseconds before the next update must occur, and we'll use the time to draw as many frames as possible. Let's say that rendering a frame takes 5 milliseconds.

1. The game loop needs to accumulate 50 ms before the next physical update. We set the counter to 0.
2. The interpolation value is calculated as 0/50, or 0
3. The game renders with an interpolation value of 0, which means the monster is drawn at (30, 40).
4. The counter is incremented to 5, so the interpolation value is now 5/50, or 0.1
5. The game renders with an interpolation value of 0.1, which means the monster is drawn at (31, 41).
6. This process continues until the interpolation value exceeds 1, at which point the monster will be drawn near (40, 50) and the next update will occur.

This exact process is what you're seeing in the second part of the video. Of course, it's still a little messed up--you can see a noticeable lag in when the sword swings and the enemies are hit. No matter how smooth the rendering is, a game updated at 20 frames per second is going to play weird and have lag.

Other than the points below, that's all for this week. I've also implemented a shield, but I'd rather wait until I have real graphics instead of the hideous temp art before showing off that one. Until next week!

Some points of interest / objections:

Why not just run at a variable timestep, updating as often as possible?

A lot of games do this. Sometimes it's a good solution, sometimes it isn't; I've read both sides of the argument. In the end, I've chosen for a fixed timestep because it yields repeatable and reliable physics results. Variable timesteps can lead to quirky behavior, such as giving those with a lower (or higher) framerate an 'advantage' in certain situations.

If the interpolation is linear and the object is moving non-linearly (e.g. sinusoidal or accelerated movement), won't that look weird?

No, not really. It's almost totally unnoticeable at 60 FPS.

So why bother at all?

On some machines, I was getting a very noticeable stutter when the timestep was capped at 60 FPS. The very popular "Fix Your Timestep" article illustrates why this happens (near the end). This stutter was a result of 'temporal aliasing.'

No comments:

Post a Comment