WebGL Canvas

Introduction

WebGL lets JavaScript talk to the GPU through an HTML <canvas>. If you have worked through some of the OpenGL tutorials, a lot of the ideas will feel familiar: clear colours, viewports, depth buffers, shaders and buffers are all still there. The browser just gives us a different way to get at them.

What are we actually doing?

The canvas itself is just an area on the page. Calling canvas.getContext("webgl2") asks the browser to give us a WebGL rendering context for that canvas. If WebGL 2 is unavailable, we try webgl instead. If both calls fail, we stop there rather than trying to call methods on a missing context.

For this first tutorial we are deliberately keeping the rendering boring: a nice blue clear colour and a depth buffer. That gives us a clean, modern starting point before we pile shaders and geometry on top of it.

The HTML

Our page only needs a canvas and a JavaScript file:

<canvas id="webgl-canvas" width="500" height="500">
    Your browser does not support the HTML canvas element.
</canvas>
<script src="webgl-canvas.js"></script>

The JavaScript

We keep the rendering code in a separate file instead of stuffing a big script directly into the HTML. It is easier to read, easier to debug and much easier to reuse as the tutorials grow.

let gl = null;
let canvas = null;

function resizeCanvasToDisplaySize() {
    const pixelRatio = window.devicePixelRatio || 1;
    const displayWidth = Math.round(canvas.clientWidth * pixelRatio);
    const displayHeight = Math.round(canvas.clientHeight * pixelRatio);

    if (canvas.width !== displayWidth || canvas.height !== displayHeight) {
        canvas.width = displayWidth;
        canvas.height = displayHeight;
    }
}

function initWebGL() {
    canvas = document.getElementById("webgl-canvas");
    gl = canvas.getContext("webgl2") || canvas.getContext("webgl");

    if (!gl) {
        canvas.insertAdjacentHTML(
            "afterend",
            "<p>WebGL could not be initialised in this browser/device.</p>"
        );
        return false;
    }

    gl.clearColor(0.4, 0.6, 0.9, 1.0);
    gl.clearDepth(1.0);
    gl.enable(gl.DEPTH_TEST);
    gl.depthFunc(gl.LEQUAL);
    return true;
}

function displayWebGL() {
    resizeCanvasToDisplaySize();
    gl.viewport(0, 0, canvas.width, canvas.height);
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
}

function main() {
    if (!initWebGL()) return;
    displayWebGL();
}

window.addEventListener("DOMContentLoaded", main);

Why resize the drawing buffer?

A canvas has a CSS display size and a drawing-buffer size. On a high-DPI screen those are not necessarily the same thing. Matching the drawing buffer to the displayed size (including the device pixel ratio) avoids a blurry canvas and also means our viewport uses the real number of pixels we are drawing into.

Where do we go from here?

At this point we have a real WebGL context, sensible error handling, a correctly sized viewport and a frame that clears cleanly. The next interesting step is shaders and buffers — that is where the page stops being a blue rectangle and starts behaving like a graphics tutorial 🙂

Source & downloads

Questions or corrections? If something doesn’t work or you spot something I’ve got wrong, leave a comment below. These tutorials have been around a long time, so useful corrections are always welcome!

  • February 10, 2011
  • 2