Program Structure

You may need to read the article about shaders before this one.


OpenGL (and, by extension, WebGL) is a state machine. The collection of variables that define how OpenGL should operate is commonly referred to as the OpenGL context. The majority of the calls to the OpenGL API are to change its state, with only a few final calls being made to actually render that state.

Initialization Step

The initialization step is executed once at the beginning of the program. It is typically used to get the rendering context, compile shaders, link shader programs, and prepare static data.

Render Step

The render step is executed once per frame (the content of a framebuffer). The frame rate is the rate at which the framebuffer is written to.

The viewport specifies the affine transformation of the horizontal and vertical axes from clip space to screen space, which is a coordinate system that represents the canvas in the ranges [0,x][0, x] and [0,y][0, y] on the horizontal and vertical axes, respectively, where xx is the width of the rendering context in fragments and yy is the height of the rendering context in fragments. The negative boundaries for each direction are left and up, respectively. In other words, the viewport determines how clip space coordinates are mapped to fragments.

The render step is typically used to modify the context and rasterize.

The requestAnimationFrame function can be used to execute the render step as fast as the GPU is able to. Since the frame rate depends on the user's hardware, it is sometimes necessary to calculate it to make animations consistent across all devices.

let then = 0;

function renderStep(now) {
	requestAnimationFrame(renderStep);

	const deltaTime = now - then;
	then = now;
	const frameRate = 1000 / deltaTime;
}
requestAnimationFrame(renderStep);

Resizing the Viewport

Every canvas has two sizes: the physical size of the canvas in pixels and the size of the draw buffer in fragments. If the size of the draw buffer does not match the physical size of the canvas, the size of fragments will differ from the size of pixels, causing the rendered primitives to appear blurry and/or stretched. Therefore, it is a good idea to resize the draw buffer every frame in case the physical size of the canvas changes.

If the size of the viewport is smaller than that of the draw buffer, only part of the canvas will be utilized. If the size of the viewport is larger than that of the draw buffer, only part of the draw buffer will be utilized. In most cases, it is a good idea to resize the viewport every frame to match the size of the draw buffer (and, by extension, the canvas).

canvas.width = canvas.clientWidth;
canvas.height = canvas.clientHeight;

gl.viewport(0, 0, canvas.width, canvas.height);

Clearing Color Buffers

Before rendering a new frame, the draw buffer should be cleared of the fragments from the previous frame. A value can be set for fragments in the draw buffer to be cleared to with clearColor. WebGL can be told which buffers to clear with clear.

gl.clearColor(0, 0, 0, 0);
gl.clear(gl.COLOR_BUFFER_BIT);

Cheat Sheet

A typical program that uses the OpenGL API follows this structure:


The next article is about attributes.