Unfortunately, even the simplest WebGL program is somewhat long and involved. Below is the complete code for the program followed by more detailed explanations of the different parts of that code.
<!doctype html>
<canvas width="500" height="500" id="mainCanvas"></canvas>
<script>
function main()
{
// Configure the canvas to use WebGL
//
var gl;
var canvas = document.getElementById('mainCanvas');
try {
gl = canvas.getContext('webgl');
} catch (e) {
throw new Error('no WebGL found');
}
// Copy an array of data points forming a triangle to the
// graphics hardware
//
var vertices = [
0.0, 0.5,
0.5, -0.5,
-0.5, -0.5,
];
var buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
// Create a simple vertex shader
//
var vertCode =
'attribute vec2 coordinates;' +
'void main(void) {' +
' gl_Position = vec4(coordinates, 0.0, 1.0);' +
'}';
var vertShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertShader, vertCode);
gl.compileShader(vertShader);
if (!gl.getShaderParameter(vertShader, gl.COMPILE_STATUS))
throw new Error(gl.getShaderInfoLog(vertShader));
// Create a simple fragment shader
//
var fragCode =
'void main(void) {' +
' gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);' +
'}';
var fragShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragShader, fragCode);
gl.compileShader(fragShader);
if (!gl.getShaderParameter(fragShader, gl.COMPILE_STATUS))
throw new Error(gl.getShaderInfoLog(fragShader));
// Put the vertex shader and fragment shader together into
// a complete program
//
var shaderProgram = gl.createProgram();
gl.attachShader(shaderProgram, vertShader);
gl.attachShader(shaderProgram, fragShader);
gl.linkProgram(shaderProgram);
if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS))
throw new Error(gl.getProgramInfoLog(shaderProgram));
// Everything we need has now been copied to the graphics
// hardware, so we can start drawing
// Clear the drawing surface
//
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
// Tell WebGL which shader program to use
//
gl.useProgram(shaderProgram);
// Tell WebGL that the data from the array of triangle
// coordinates that we've already copied to the graphics
// hardware should be fed to the vertex shader as the
// parameter "coordinates"
//
var coordinatesVar = gl.getAttribLocation(shaderProgram, "coordinates");
gl.enableVertexAttribArray(coordinatesVar);
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.vertexAttribPointer(coordinatesVar, 2, gl.FLOAT, false, 0, 0);
// Now we can tell WebGL to draw the 3 points that make
// up the triangle
//
gl.drawArrays(gl.TRIANGLES, 0, 3);
}
window.onload = main;
</script>
<canvas width="500" height="500" id="mainCanvas"></canvas>
<script>
function main()
{
// Configure the canvas to use WebGL
//
var gl;
var canvas = document.getElementById('mainCanvas');
try {
gl = canvas.getContext('webgl');
} catch (e) {
throw new Error('no WebGL found');
}
// Copy an array of data points forming a triangle to the
// graphics hardware
//
var vertices = [
0.0, 0.5,
0.5, -0.5,
-0.5, -0.5,
];
var buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
// Create a simple vertex shader
//
var vertCode =
'attribute vec2 coordinates;' +
'void main(void) {' +
' gl_Position = vec4(coordinates, 0.0, 1.0);' +
'}';
var vertShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertShader, vertCode);
gl.compileShader(vertShader);
if (!gl.getShaderParameter(vertShader, gl.COMPILE_STATUS))
throw new Error(gl.getShaderInfoLog(vertShader));
// Create a simple fragment shader
//
var fragCode =
'void main(void) {' +
' gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);' +
'}';
var fragShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragShader, fragCode);
gl.compileShader(fragShader);
if (!gl.getShaderParameter(fragShader, gl.COMPILE_STATUS))
throw new Error(gl.getShaderInfoLog(fragShader));
// Put the vertex shader and fragment shader together into
// a complete program
//
var shaderProgram = gl.createProgram();
gl.attachShader(shaderProgram, vertShader);
gl.attachShader(shaderProgram, fragShader);
gl.linkProgram(shaderProgram);
if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS))
throw new Error(gl.getProgramInfoLog(shaderProgram));
// Everything we need has now been copied to the graphics
// hardware, so we can start drawing
// Clear the drawing surface
//
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
// Tell WebGL which shader program to use
//
gl.useProgram(shaderProgram);
// Tell WebGL that the data from the array of triangle
// coordinates that we've already copied to the graphics
// hardware should be fed to the vertex shader as the
// parameter "coordinates"
//
var coordinatesVar = gl.getAttribLocation(shaderProgram, "coordinates");
gl.enableVertexAttribArray(coordinatesVar);
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.vertexAttribPointer(coordinatesVar, 2, gl.FLOAT, false, 0, 0);
// Now we can tell WebGL to draw the 3 points that make
// up the triangle
//
gl.drawArrays(gl.TRIANGLES, 0, 3);
}
window.onload = main;
</script>
The first part of the HTML page creates the HTML element where the actual 3d drawing will occur. WebGL uses the canvas element to define its drawing surface, which can also used in HTML5 for doing 2d drawing.
<canvas width="500" height="500" id="mainCanvas"></canvas>
Then, we get into the actual code. First, we need to configure the canvas for use with WebGL instead of for 2d drawing by grabbing a WebGL context object that lets us invoke WebGL commands on the canvas.
var gl;
var canvas = document.getElementById('mainCanvas');
try {
gl = canvas.getContext('webgl');
} catch (e) {
throw new Error('no WebGL found');
}
var canvas = document.getElementById('mainCanvas');
try {
gl = canvas.getContext('webgl');
} catch (e) {
throw new Error('no WebGL found');
}
Next, we have an array holding the coordinates of the three points that make up a triangle.
As mentioned in part 1, we need to copy this triangle data to the graphics hardware before we can draw it. This is done by using createBuffer() to tell WebGL to that we want to set aside some memory at the graphics hardware for our data, bindBuffer() to select this buffer as something we want to manipulate, and then bufferData() to actually copy the triangle data to the currently selected buffer in the graphics hardware.
var vertices = [
0.0, 0.5,
0.5, -0.5,
-0.5, -0.5,
];
var buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
0.0, 0.5,
0.5, -0.5,
-0.5, -0.5,
];
var buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
The “vertices” array that holds the triangle data is put inside a Float32Array object before being sent to the bufferData() call. This Float32Array object specifies how the array data should be laid out in memory. JavaScript is purposely vague about the exact memory layout of objects, but these details are important when working with graphics hardware. In this case, we specify that the triangle data should be stored as consecutive 32-bit floating point numbers.
As mentioned in part 1, the WebGL graphics pipeline requires us to define vertex shader and fragment shader programs in order to draw anything on the screen. We first define the vertex shader program.
var vertCode =
'attribute vec2 coordinates;' +
'void main(void) {' +
' gl_Position = vec4(coordinates, 0.0, 1.0);' +
'}';
var vertShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertShader, vertCode);
gl.compileShader(vertShader);
if (!gl.getShaderParameter(vertShader, gl.COMPILE_STATUS))
throw new Error(gl.getShaderInfoLog(vertShader));
'attribute vec2 coordinates;' +
'void main(void) {' +
' gl_Position = vec4(coordinates, 0.0, 1.0);' +
'}';
var vertShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertShader, vertCode);
gl.compileShader(vertShader);
if (!gl.getShaderParameter(vertShader, gl.COMPILE_STATUS))
throw new Error(gl.getShaderInfoLog(vertShader));
The actual code for the vertex shader is held in a string. The vertex shader allows us to move the points of a triangle or other small transformations before they are displayed. Each point that makes up a triangle is given to the vertex shader, and the vertex shader program returns the final position of the point, plus additional data that it may want to specify. In our case, we don't need to move the points around, but we need to put the data in a proper form for WebGL. WebGL displays the parts of triangles that fit inside the 3d cube between (-1,-1,-1) and (1,1,1). Our triangle coordinates are only (x,y) values, so we need to specify an extra z value so that WebGL can determine where the triangle is in 3d space. We just use 0 for this z coordinate. In fact, WebGL needs us to specify four values: x, y, z, and a fourth value that is normally always 1. So our vertex shader will take the 2d (x,y) coordinates for a point in the triangle and transform it to (x,y,0,1).
attribute vec2 coordinates;
void main(void) {
gl_Position = vec4(coordinates, 0.0, 1.0);
}
void main(void) {
gl_Position = vec4(coordinates, 0.0, 1.0);
}
Next we define the fragment shader.
var fragCode =
'void main(void) {' +
' gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);' +
'}';
var fragShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragShader, fragCode);
gl.compileShader(fragShader);
if (!gl.getShaderParameter(fragShader, gl.COMPILE_STATUS))
throw new Error(gl.getShaderInfoLog(fragShader));
'void main(void) {' +
' gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);' +
'}';
var fragShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragShader, fragCode);
gl.compileShader(fragShader);
if (!gl.getShaderParameter(fragShader, gl.COMPILE_STATUS))
throw new Error(gl.getShaderInfoLog(fragShader));
A fragment shader controls the color of each pixel making up the triangle. For each pixel, a fragment shader should return four numbers describing the color: the amount of red, the amount of green, the amount of blue, and the amount of transparency. If we look at the code for the fragment shader, we can see that the fragment shader is fairly simple. It simply uses the same color for every pixel: an opaque white color.
void main(void) {
gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
}
gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
}
After defining a vertex shader and fragment shader, we need to put these two shaders together in a single program for drawing things.
var shaderProgram = gl.createProgram();
gl.attachShader(shaderProgram, vertShader);
gl.attachShader(shaderProgram, fragShader);
gl.linkProgram(shaderProgram);
if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS))
throw new Error(gl.getProgramInfoLog(shaderProgram));
gl.attachShader(shaderProgram, vertShader);
gl.attachShader(shaderProgram, fragShader);
gl.linkProgram(shaderProgram);
if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS))
throw new Error(gl.getProgramInfoLog(shaderProgram));
Now that we've copied the triangle data and shader programs over to the graphics hardware, we're finally ready to do some drawing. First, we clear the drawing surface to black so that the white triangle we're drawing will show up.
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.clear(gl.COLOR_BUFFER_BIT);
Then we specify which shader program to use for drawing.
gl.useProgram(shaderProgram);
We then tell WebGL to feed our buffer of triangle data through this shader program. To do this, we need to specify how the values in this array of points should be given to the program. We want our point data to be given to the vertex shader as the “coordinates” variable. So we get a handle for this variable and configure the variable. We then select our buffer of data, and use vertexAttribPointer() to specify that the buffer should be divided into groups of two floating-point numbers, and these numbers should be fed into the shader program as the “coordinates” variable.
var coordinatesVar = gl.getAttribLocation(shaderProgram, "coordinates");
gl.enableVertexAttribArray(coordinatesVar);
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.vertexAttribPointer(coordinatesVar, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(coordinatesVar);
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.vertexAttribPointer(coordinatesVar, 2, gl.FLOAT, false, 0, 0);
Finally, now that we've properly configured everything, we can now instruct WebGL to actually draw something. We tell it to take the three (x,y) points in our array, feed them through the shader program, and draw the result as a triangle.
gl.drawArrays(gl.TRIANGLES, 0, 3);
So this is the end of the pre-tutorial. To include anything more would turn this into an actual tutorial.
Here is part 1 of the pre-tutorial in case you missed it.