What is WebGL?

WebGL is a rasterization engine. That means that it draws primitives (points, lines, and triangles) using pixels. That is all that it can do. Using WebGL to draw complex scenes is up to the developer.

WebGL is based on OpenGL ES.

Shaders

WebGL runs on the GPU, so developers need to provide it code that can run on the GPU. That code is supplied in the form of pairs of functions called shaders. Each pair of shaders contain one vertex shader and one fragment shader. Collectively, the pair of shaders is called a shader program.

Shaders are written in a strictly-typed C-like language called GLSL.

Each shader fulfills a different purpose:

  • The vertex shader is run once for each vertex in the shape being drawn; it computes the position of that vertex and outputs it as a clip space coordinate. Clip space is a coordinate system in the interval [1,1][-1,1] in every direction, regardless of the size of the viewport.
  • Once the vertex shader has been run the proper number of times for the type of primitive being drawn (11 time for a point, 22 times for a line, or 33 times for a triangle), WebGL rasterizes (draws with pixels) that primitive.

The fragments from the fragment shader constitute a bitmap image stored in a color buffer, which is then displayed on a canvas.

A minimal vertex shader looks like this:

#version 300 es

// Every shader has a main function to be executed.
void main() {
	/*
	Setting the special variable "gl_Position" is how the
	vertex shader outputs clip space coordinates.
	*/
	gl_Position = vec4(0, 0, 0, 1);
}

A minimal fragment shader looks like this:

#version 300 es

// Must set a default precision for fragment shaders.
precision highp float;

// Must declare an output variable for fragment shaders.
out vec4 outColor;

void main() {
	/*
	Setting the output variable is how the fragment shader
	outputs colors.
	*/
	outColor = vec4(0, 0, 0, 1);
}

The first line of every WebGL2 shader must be #version 300 es. This line must come before even comments and whitespace. It tells the WebGL API that the program is written in GLSL ES 3.00, which is required for WebGL2.

The WebGL API

The WebGL API is how developers interact with WebGL through JavaScript. It can be accessed through the rendering context on an HTML canvas element.

A canvas can be created in HTML like this:

<canvas></canvas>

The canvas can then be referenced in JavaScript to access its rendering context:

const canvas = document.querySelector("canvas");
const gl = canvas.getContext("webgl2");

The WebGL API can be used to compile shader programs like this:

// Put the vertex shader source code into a string.
const vss =
`#version 300 es
void main() {
	gl_Position = vec4(0, 0, 0, 1);
}`;

// Create the vertex shader.
const vs = gl.createShader(gl.VERTEX_SHADER);

// Assign the vertex shader's source code to it.
gl.shaderSource(vs, vss);

// Compile the vertex shader.
gl.compileShader(vs);

// Check for compilation errors.
if (!gl.getShaderParameter(vs, gl.COMPILE_STATUS)) {
	// If there's an error, log it to the console.
	console.error(gl.getShaderInfoLog(vs));

	// Stop execution.
	throw new Error("Failed to compile.");
}

// Repeat for the fragment shader.
const fss =
`#version 300 es
precision highp float;
out vec4 outColor;
void main() {
	outColor = vec4(0, 0, 0, 1);
}`;
const fs = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fs, fss);
gl.compileShader(fs);
if (!gl.getShaderParameter(fs, gl.COMPILE_STATUS)) {
	console.error(gl.getShaderInfoLog(fs));
	throw new Error("Failed to compile.");
}

// Create the shader program.
const program = gl.createProgram();

// Attach the shaders to the program.
gl.attachShader(program, vs);
gl.attachShader(program, fs);

// Link the shader program.
gl.linkProgram(program);

// Check for linking errors.
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
	// If there's an error, log it to the console.
	console.error(gl.getProgramInfoLog(program));

	// Stop execution.
	throw new Error("Failed to link.");
}

The WebGL API is very verbose, so a variety of libraries exist to reduce the complexity of using it. Some of these are high-level, such as three.js and Babylon.js. Others are low-level, such as TWGL and Umbra. Umbra is my library, so it is what will be used in this set of articles.

With Umbra, the following line of code is equivalent to the entire block above:

const program = Program.fromSource(gl, vss, fss);

The next article is about attributes.