WebGL Square

Covering the Canvas

This is a follow-up to WebGL Triangle tutorial. We'll start from what we built in the previous tutorial. This time we'll draw a square that covers the entire canvas and make some improvements to the code along the way.

View the demo or jump ahead and edit the code.

The next tutorial, WebGL Gradient, builds on this one.

Cleanup

First let's address the repetitiveness of the shader setup code:

    const vertexShader = gl.createShader(gl.VERTEX_SHADER);
    gl.shaderSource(vertexShader, vertexCode.trim());
    gl.compileShader(vertexShader);
    if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) {
      throw gl.getShaderInfoLog(vertexShader);
    }

    const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
    gl.shaderSource(fragmentShader, fragmentCode.trim());
    gl.compileShader(fragmentShader);
    if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) {
      throw gl.getShaderInfoLog(fragmentShader);
    }

    const program = gl.createProgram();
    gl.attachShader(program, vertexShader);
    gl.attachShader(program, fragmentShader);
    ...

We can make a reusable function to do this:

function createShader(shaderType, sourceCode) {
  const shader = gl.createShader(shaderType);
  gl.shaderSource(shader, sourceCode.trim());
  gl.compileShader(shader);
  if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
    throw gl.getShaderInfoLog(shader);
  }
  return shader;
}

And use it to setup the program:

const program = gl.createProgram();
gl.attachShader(program, createShader(gl.VERTEX_SHADER, vertexCode));
gl.attachShader(program, createShader(gl.FRAGMENT_SHADER, fragmentCode));

Drawing a Square

With that out of the way, let's draw a square. We can do that by adding three more vertices to our list of vertices:

const vertices = [
  [-1, -1, 0],
  [1, -1, 0],
  [1, 1, 0],
  [1, 1, 0],
  [-1, 1, 0],
  [-1, -1, 0],
];

This can be improved though. When we call gl.drawArrays(gl.TRIANGLES, 0, vertices.length);, the gl.TRIANGLES parameter tells WebGL to draw one triangle for every three vertices. There are other options, including gl.TRIANGLE_STRIP.

With triangle strips, every vertex in the list defines a triangle with the following two vertices (as long as there are at least two vertices following it). For example, the list [A,B,C,D,E] defines three triangles: [A,B,C] , [B,C,D], and [C,D,E].

Let's try it:

    const vertices = [
      [-1, -1, 0],
      [1, -1, 0],
      [1, 1, 0],
      [-1, 1, 0],
    ];
    ...
    gl.drawArrays(gl.TRIANGLE_STRIP, 0, vertices.length);

This doesn't draw a square though. The order of our vertices are wrong:

When we connect vertices 1,2,3 and 2,3,4 into triangles, they overlap instead of covering the canvas. We need to draw the vertices in a "Z" shape to get the intended result:

const vertices = [
  [-1, -1, 0],
  [1, -1, 0],
  [-1, 1, 0],
  [1, 1, 0],
];

And now we have a square again:

2D Vertices

We can simplify this more. We're always setting the z coordinate to 0 because we're drawing 2D. In the vertex shader, z defaults to 0 if we don't provide it, so we don't need to provide it:

const vertices = [
  [-1, -1],
  [1, -1],
  [-1, 1],
  [1, 1],
];

For this to work, we have to tell gl.vertexAttribPointer() that we are giving it 2D vertices by changing the second parameter to 2:

gl.vertexAttribPointer(vertexPosition, 2, gl.FLOAT, false, 0, 0);

Result

It's not much to look at, but now we have the entire canvas to play around with:

Here's the full HTML page with code. Changes to the previous tutorial are highlighted.

  
    <!doctype html>
    <html>
    <body>
      <canvas id="canvas" width="500" height="500"></canvas>

      <script id="vertex" type="x-shader/x-vertex">
        #version 300 es

        in vec4 vertexPosition;

        void main() {
          gl_Position = vertexPosition;
        }
      </script>

      <script id="fragment" type="x-shader/x-fragment">
        #version 300 es
        precision highp float;

        out vec4 fragColor;

        void main() {
          fragColor = vec4(1, 0, 0, 1);
        }
      </script>

      <script>
        const canvas = document.getElementById("canvas");
        const vertexCode = document.getElementById("vertex").textContent;
        const fragmentCode = document.getElementById("fragment").textContent;

        const gl = canvas.getContext("webgl2");
        if (!gl) throw "WebGL2 not supported";

        function createShader(shaderType, sourceCode) {
          const shader = gl.createShader(shaderType);
          gl.shaderSource(shader, sourceCode.trim());
          gl.compileShader(shader);
          if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
            throw gl.getShaderInfoLog(shader);
          }
          return shader;
        }

        const program = gl.createProgram();
        gl.attachShader(program, createShader(gl.VERTEX_SHADER, vertexCode));
        gl.attachShader(program, createShader(gl.FRAGMENT_SHADER, fragmentCode));
        gl.linkProgram(program);
        if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
          throw gl.getProgramInfoLog(program);
        }
        gl.useProgram(program);

        const vertices = [
          [-1, -1],
          [1, -1],
          [-1, 1],
          [1, 1],
        ];
        const vertexData = new Float32Array(vertices.flat());
        gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
        gl.bufferData(gl.ARRAY_BUFFER, vertexData, gl.STATIC_DRAW);

        const vertexPosition = gl.getAttribLocation(program, "vertexPosition");
        gl.enableVertexAttribArray(vertexPosition);
        gl.vertexAttribPointer(vertexPosition, 2, gl.FLOAT, false, 0, 0);

        gl.drawArrays(gl.TRIANGLE_STRIP, 0, vertices.length);
      </script>
    </body>
    </html>
  

View the demo.

Try it on CodePen.

Go to the next tutorial, WebGL Gradient.


Table of Contents:
  1. WebGL Concepts
  2. WebGL Triangle
  3. WebGL Square
  4. WebGL Gradient
  5. WebGL Animation