The Source for Java Technology Collaboration
User: Password:
Register | Login help    

Search

Online Books:
java.net on MarkMail:


Make Your Animations Less Ch-Ch-Choppy

Posted by chet on February 21, 2006 at 4:22 PM PST

This article is Part One of a 2-part series. In Part One, I look at some of the problems contributing to choppy animations. In Part Two, Smooth Moves, I look at some of the possible solutions and offer a demo application for trying out some of the results.

The Problem

I was working on some animation code the other day and ran into some artifacts that made the animation look choppy. In this particular example, there were both fading and moving/scaling animations. The fade looked great, but the animation with movement looked stuttery. This was particularly odd since the fade happened over the entire screen, whereas the scrolling animation occupied only a small portion of the screen.

It seemed obvious that the fading animation must be quicker than the other one and that we were hitting some performance bottleneck on the other animation.

I inserted some simple timing code in both animation loops to verify this easy assumption. Strangely, I found that the fade was happening at a rate of about 10 frames per second (fps); not very fast, considering that minimal frame rates for animation are usually considered to be 30 fps, although 60 fps is a more usual minimal for highly interactive content such as games. Stranger still, I found that the other animation was getting about 15-20 fps; faster than the visually smoother fading animation.

So here was the quandary; why did the large fading animation with a lower frame rate actually look better than the small scrolling animation with the higher frame rate? It just didn’t compute.

I had stumbled, once again, across another example of perceived performance. This topic comes up often in the GUI space, where the true performance of an application is often not as important as how fast the user thinks it is. This is why, for example, you should run your long non-graphical operations on a separate thread from the GUI thread, so that if your app has to go out and query the database for several seconds, at least the GUI is not frozen waiting for that to finish. Make the application GUI snappy, and the user will be happier with the performance of the application - even if the data takes exactly the same time to actually populate the GUI. Perceived performance also relates to the gray rect fix in Mustang that Scott Violet and I blogged about; the application may not actually be any faster, but it certainly looks faster to the user, and that’s worth a lot. In this case, I perceived the fading animation to be more performant than the scrolling animation simply because it looked smoother.

After this realization, I figured that it was time to investigate the factors that could affect the smoothness of an animation; what makes an animation appear choppy, and what could be done to reduce or eliminate the choppiness.

What Makes an Animation Choppy

There are various elements that contribute to choppy animations, some of which were factors in my application, but all of which are worth considering whenever you develop animation code.

I’ll break down the factors into three main categories: timing, color, and vertical retrace.

1) Timing is (Nearly) Everything

In general, you want your animations to be speedy. I don’t mean that they should zip across the screen as fast as possible (you have to give the player the chance to actually hit the alien spaceship, don’t you?), but rather that the animations should move quickly in small increments. There are various elements related to the speed at which an animation can run: raw performance, framerate, consistency, and realistic movement.

Performance

Raw performance is one of the most important factors affecting animation smoothness, and probably the most obvious; if you run your animation faster, it will tend to look smoother. This is because the amount of change between each frame of your object is smaller, and thus the eye will tend to perceive the steps as being smoother. The way to improve the performance of your application is by making your rendering faster. Are you wasting time drawing anything in a sub-optimal way? Are you drawing anything complex that could be rendered into an intermediate image instead? Are you limiting your rendering to only the area affected by the animation, or are you re-rendering the entire window? All of these factors, and more, can contribute to slower rendering and, therefore, a slower frame rate.

Framerate

Framerate is closely related to Performance: You want to make the time between sequential frames of your animation as short as possible. One way to affect framerate is by improving the performance of your rendering code. Another way is by running your animation at a different “resolution”. At the core of your animation is a timer, which will kick off timing events at some specified interval. You can set this timer up to call you every half-second, which would result in a very unanimated animation running at a whopping 2 frames per second (fps). Or you can set it up to call you every 30 milliseconds, which would give you a more reasonable 30 fps (assuming your rendering loop could do its job in that small window of time to maintain this frame rate).

Consistency

Ideally, you will have as small a timer resolution as possible so that your animation can move to the next state very quickly. However, if there are some states that require significantly more time to render, or if other things are happening in your program that make this frame rate unachievable, then you will end up with intermittent pauses in your animation. These pauses do not have to be very long to be noticeable to the user. Let’s say that your animation runs swimmingly at 30 fps most of the time, but takes a time hit of about 100 ms every 4th frame. The net result is that the user would see your animation run relatively smoothly and then pause and jump at frequent intervals, several times per second.

It is far better to set a frame rate that you know is achievable in most situations than to have a jumpy animation; an animation running at a consistent 20 fps is better than one running at 30 fps with occasional noticeable lags and jumps.

Related to consistency is using time-based animation, rather than speed-based animations. A very simple approach to animation is to update the animation state to the next step the next time you are called. This works on systems where timing is very predictable, but breaks down when you run on different systems, or when things happen to perturb the timing between steps. It is far better to base your animation state on the time that has elapsed; that way the animation will always proceed in a logical fashion, regardless of how much time passes between steps. I discuss this more in the “Animation Fraction” section of Timing is Everything.

Realistic timing

The simplest motion to calculate is linear interpolation. That is, for any fraction of the elapsed animation, you might move the object by that fraction between the starting and ending points of the animation. It's easy in code:

                x = x0 + (x1 - x0) * fraction
                
Unfortunately, this results in a movement that looks pretty unnatural to humans; we simply don't live in a linearly-interpolated world. With effects like gravity, anticipation, acceleration, deceleration, and (in my case) stumbling, we're used to objects moving with a much different feel than a linearly-interpolated animation would display.
The other problem with linear interpolation is that it is very easy to detect inconsistencies in movement in strictly linear movement; if an object is moving at a constant rate and pauses briefly, this will be quite obvious to the user. If, on the other hand, the object is moving in a non-linear fashion (such as accelerating), it will be more difficult to detect slight differences in performance.

2) Color: What’s the Difference?

I think the Timing section above details probably the most obvious element here: if you want to make your animation look better, run it faster, with smaller incremental steps, at a consistent and realistic rate. This next section is about a factor that is perhaps less obvious, but one which contributed greatly to the choppiness I was seeing in my particular application.

The apparent smoothness of an animation is strongly related to the rate of change of the pixel colors affected. That is, the display pixels affected by each of the animations in my application (the pixels being modulated in the fade and those being altered as the scrolling animation moved across the window) were changing at some rate, and that rate was perceived to be smoother in the case of the fading animation.

Let's take a step back and think about what makes an animation smooth in general. It's pretty simple to understand that having a higher frame rate will give (or render, if you're one for topical and dull puns) a smoother animation. Imagine trying to animate an image from the left side of this page to the right over the course of one second. You could simply move it from left to right in one step; the image sits over on the left until the animation begins, at which time you erase it from the left side and draw it on the right side. You've just animated that movement at an enormous 1 frame per second. Of course it's only “animated” in the technical sense; it didn't really animate at all, it just performed a single movement.

I think you'll agree that that animation would be just a bit choppy.

Let's try for something a little better. Let's divide the distance into 10 steps and perform the animation by moving the image a smaller distance in each of those steps, ending up at the same place as before. This looks a little better; at least it looks like the image is moving this time, instead of just appearing in the new place. Now we're getting a more reasonable ten frames per second, which is a rate that at least allows us to consider it an animation ... but it's still pretty choppy.

Now let's take it up another order of magnitude; imagine dividing the space into 100 equal parts, and doing the same as before, where we copy the image to each of these places along the way toward the final goal. Now we're getting somewhere; the image is moving much more smoothly than before, at a rate of 100 frames per second. You may still notice some choppiness (due to the same factors that I'll get to eventually here), but you can at least consider this an animation. And if the image was an evil droid sprite, you might even be inclined to crank up your power blaster and nail the sucker.

What made this image movement an “animation” was increasing the frame rate, and decreasing the amount of movement per interval, so that each step was small enough that overall movement began to appear smooth. This interval decreasing was done in the time and space dimensions (decreasing the time between steps and the space between each movement). Now imagine doing the same for color.

In particular, think about the color of each of the pixels being affected by an animation. How much do they change between each step? Do we have large numbers of pixels changing drastically every step? Or small numbers of pixels undergoing minor changes each time?

In the first case above, where we moved the object from the left to the right in one single frame, we changed all of the pixels in the place the object came from (we erased them to the background color) and in the place the object moved to (we colored them from the background color to the image color). That's a lot of pixels that underwent significant change in one step. In the final animation, however, where we moved the object gradually from the left to the right, the only pixels affected in each step were those on the leading and trailing edges of the object. The changes to those pixels may have been just as drastic (which is what makes that final animation still look choppy), but the fact that there were far less pixels affected by each step made the animation appear much more smooth than the first attempt.

In order to smooth out an animation, we want to minimize the amount of color change per pixel in each step of the animation. Let's think about the original animation comparison. In the fading animation of my application, each pixel was modulating between the original color and the new color at a rate of 10 frames per second. In the extreme case of a pixel moving between black ((0,0,0) in RGB space) and white ((255, 255, 255) in RGB space), the change for every step would be 10% of that color difference, or (25.6, 25.6, 25.6). We can calculate this Euclidean distance in RGB space as sqrt(25.6^2 + 25.6^2 + 25.6^2) = 44.34. Meanwhile, the moving animation was changing pixels between the background color and the object color in one step (for however many pixels were affected in each step of that animation). In the extreme case (black object and white background), pixels would change as much as (256, 256, 256) in each step, for a Euclidean distance of ten times the fading amount, or 443.4 in RGB space.

One of the interesting things to me in this investigation is that the amount of change of individual pixels appears to have much more visual impact that the amount of change overall. That is, a large color change happening in a small number of pixels is far more detectable than a small change happening in a large number pixels. In the fading case, that animation took over the entire 800x800 window. If that animation were running at 10 frames per second fading from black to white, this means there would have been 640,000 pixels changing by 44.34 each frame in RGB space. You would think that all of that color change would be pretty easily detectable by our eyes. Meanwhile, the scrolling animation affected only a tiny portion of the window in comparison; the animation happened in an area of about 200x50, or about 10,000 pixels total. Also, the scrolling animation did not affect every pixel in that area, since it just involved some objects shifting slightly every time. Assuming that the scrolling animation affected only a quarter of the pixels in that region, this means that there were only about 2,500 pixels affected each frame. In the extreme black-white case again, we have a color change of 443.4 in RGB space for each of these pixels. So that's 44.34/pixel for 640,000 pixels versus 443.4/pixel for 2,500 pixels. By numbers alone, it seems like the fade is much more significant. Nevertheless, the fade looked far smoother than the scrolling animation.

I don’t think I’m going too far out on a limb here in claiming that the rate of change of any single pixel is more significant than the total change over a group of pixels. If you've got a whole screen of a million pixels changing by just one incremental color value, that will be far less noticeable than a single pixel flickering betwen black and white in the middle of the screen. If you imagine it this way, I think you'll agree; that huge change of a single pixel's color value is far more discernible to our eyes.

Maybe it comes from our ability to edge-detect very well. Or our carnivore instinct, evolved through millenia of hunting flickery pixels in the wastelands of Cro-Magnon times. I have no idea, but I do know that’s what it looks like, and in graphics it’s all about how it looks.

3) Vertical Retrace: That Syncing Feeling

Another major factor that I noticed in my animations is the impact of the “vertical retrace” of the display.

A typical computer display (traditional CRT or modern LCD display) will update the screen from video memory at some frequency (typically 60 Hz for LCD isplays, and typically anywhere from 60-90 Hz for CRTs). You can think of this as a linear process where every pixel on the screen is updated one-by-one, left-to-right and top-to-bottom. It’s done so fast and seamlessly that you would not usually notice this happening. But fast animations on the screen can cause an artifact called “tearing” that make vertical retrace an issue.

Imagine that the vertical retrace is in the middle of refreshing the screen, and it happens to be refreshing the pixels in the area where you are copying pixels to the screen. The effect will be that the pixels below and to the right of the refresh location will show up in their new location, but the pixels to the left and above that location will still be shown in their old location; this is because the refresh is only updating the pixels to the right and below on this refresh. This effect does not last long; it lasts exactly until the next refresh, which is in 1/60th of a second for an LCD display. Since the artifact is not permanent, you might not even notice or be disturbed by it. But if you are animating things constantly, and if those animations are happening in large increments, then the tearing will be so obvious that this will add to any perception of a choppy animation.

It might be easier to see this effect with a picture.

Suppose we are trying to move our image between frame n and frame n+1 like so:

vsyncNoArtifact.GIF

Now suppose that the vertical retrace (represented by the red line below) is happening right in the middle of this area as we are doing this copy:

vsyncArtifact.GIF

The net result is that the user will see “Frame 2” as having a broken object in it very quickly, until it is fixed by the next refresh.

The problem here is that copies to the screen do not, by default, pay any attention to the state of the vertical retrace process, so it is entirely possible, even likely, that animations will run into this rather jarring (if transient) artifact.

Segue

Now for the cliff-hanger: please check out the article Smooth Moves on java.net for the stunning conclusion of this series. That article examines possible solutions to the color difference and vertical retrace problems, and offers an applet and sample code the demonstrates both the problems and solutions.

Related Topics >> Java Desktop      
Comments
Comments are listed in date ascending order (oldest first)