7.8 - Screen Updates and Animation

The last two lessons have shown you how to change the position and orientation of objects over time. This lesson explains how the screen is rendered so that you use an accurate frame rate for your motion calculations.

Double Buffering

WebGL automatically implements double buffering, which always renders your graphics into an offscreen frame buffer. What is visible on the screen is a “second” frame buffer - thus the term “double” buffering. Rendering is done offscreen so that the changing of individual pixels is never visible to a user. A user always sees a completed rendering after all pixels in the offscreen frame buffer have been assigned a color and the offscreen buffer has been copied to the screen’s frame buffer. Double buffering is standard practice and that is why WebGL implements it automatically.

Updating the Screen

Your computer monitor displays a raster image that refreshed many times a second. It’s refresh rate is typically 60 times per second, but many newer monitors update at 120 times a second.

When WebGL recognizes that a new rendering has been completed in its offscreen frame buffer, it copies the contents of the offscreen buffer to the screen’s visible frame buffer. It only performs this copy when the video memory is not busy with a screen refresh. Therefore, if a screen is being updated 60 times per second, there are 60 possible time spans between screen refreshes when the visible buffer might be updated, assuming that your WebGL program can generate a new image in less than 1/60th of a second. If your rendering process takes longer than 1/60th of a second, then your program will only be able to update the screen every other refresh cycle, which would give you 30 frames per second. If your render processing takes more that 2/60th of a second, then your program will only be able to update the screen every 3rd refresh cycle. Hopefully you see a pattern. A WebGL program is typically designed to animate at a particular frame rate and that frame rate needs to be a multiple of the monitor’s refresh rate. For a 60 Hz screen, the possible frame rates are 60, 30, 15, 20, 10, 6, 5, 4, 3, 2, and 1.

The requestAnimationFrame Function

You can build WebGL animations using JavaScript timer events, but timer events are not designed for animations. For example, what if an animation is running in a browser tab and the tab gets covered up by another tab or another application window. A JavaScript timer event will continue to fire and perform lots of computations for a rendering that can’t be seen! So JavaScript introduced a function specifically for animations called requestAnimationFrame. This function will request that a specified callback function be called before the next refresh paint process tries to update the screen frame buffer, with the caveat that nothing is done if the WebGL window is not visible.

The typical animation function performs the following tasks:

  • Calculate the amount of elapsed time since the last frame rendering.
  • If it is time to render a new frame:
    • Change appropriate scene variables
    • Render the scene
  • Call requestAnimationFrame to continue rendering at a future time.

An example animation function is shown below.

//------------------------------------------------------------------------------
self.animate = function () {

  var now, elapsed_time;

  if (scene.animate_active) {

    // How much time has elapsed since the last frame rendering?
    now = Date.now();
    elapsed_time = now - previous_time;

    if (elapsed_time >= frame_rate) {
      // Change the scene.
      scene.angle_x -= 0.5;
      scene.angle_y += 1;

      // Render the scene.
      scene.render();

      // Remember when this scene was rendered.
      previous_time = now;
    }

    requestAnimationFrame(self.animate);
  }
};

Notice the following about this code:

  • The requestAnimationFrame sets the callback to the same function it is in.
  • The previous_time variable is declared outside this function so it can retain its value from one function call to the next.
  • There must be some mechanism for stopping an animation. This code uses an value from the scene object called animate_active. The animation can be stopped by an external process by setting this variable to false.
  • Accurate timing requires that you track time from the start of the rendering of one frame to the start of the next frame. Notice that Date.now() is called once between any rendering and the value is saved in a local variable. That local variable is used to update the previous_time value after the rendering is finished. Don’t called Date.now() again. if you did you would be calculating the time between frames without taking into account the rendering time.
  • The variable frame_rate is set to the number of milliseconds allocated for each frame. A typical assignment would be: var frame_rate = 16; // 16 milliseconds = 1/60 sec

Glossary

double buffering
The use of two frame buffers: one offscreen frame buffer to render into, another onscreen frame buffer that is visible to the user.
refresh rate
The number of times per second a computer monitor refreshes the color of each pixel on its screen. The color values come from video memory.
Hz
Hertz; cycles per second; a unit of measurement used to specify refresh rates.
frame rate
The number of frames per second in an animation.
Next Section - 8.1 - Introduction to Projections