ACG: Minimal WebGL Example

Creating a WebGL example is a bit complicated given that HTML, javascript, and GLSL have to be combined all together. Here we attempt to break this down into the basic necessary structure.

WebGL Skeleton

The WebGL skeleton is pretty straight forward. It contains the minimum HTML code needed to set up an OpenGL canvas and provide some vertex and fragment shaders. Javascript is used to initialize and set up the OpenGL. For brevity, the skeleton doesn’t contain the shader nor javascript code which are explained in the next sections.

<html>
<head>
 <title>Minimal WebGL Example</title>
</head>
<body>
 <canvas id="glCanvas" style="width:400px; height:400px;" />
 <!-- type="notjs" makes it not get identified as javascript. -->
 <script id="vertex-shader" type="notjs">
   VERTEX CODE
 </script>
 <script id="fragment-shader" type="notjs">
   FRAGMENT CODE
 </script>
 <script type="text/javascript">
   JAVASCRIPT CODE
 </script>
</body>
</html>

Vertex Shader

The vertex shader is very simple. We are receiving the position as an attribute using the in keywoard, and this directly determines the output pixel. At the same time, we are converting from screen coordinates in the [-1,1] range to the [0,1] range which can be used for textures and the likes. This is set as the variable coord which gets passed to the fragment shader using the in and out qualifiers.

#version 300 es
in vec4 pos;
out vec2 coord;
void main() {
   coord = pos.xy*0.5+0.5;
   gl_Position = pos;
}

Fragment Shader

The fragment shader receives the coord variable from the vertex shader and uses this to determine the colour of each pixel.

#version 300 es
precision mediump float;
in vec2 coord;
out vec4 color_out;
void main() {
   color_out = vec4(coord.x, 0.5, coord.y, 1.0);
}

WebGL Javascript

Javascript is the core of the WebGL and is used to set up the shaders and vertices to draw on screen. In this particular case, we are just using a gl.TRIANGLE_STRIP to draw two triangles to fill the entire screen. The shader code is taken from the HTML elements and compiled into a program.

function createProgram( gl, vertexSrc, fragmentSrc ) {
   let vshd = gl.createShader( gl.VERTEX_SHADER );
   gl.shaderSource( vshd, vertexSrc );
   gl.compileShader( vshd );
   if (!gl.getShaderParameter( vshd, gl.COMPILE_STATUS ))
      throw new Error( "Unable to compile shader: "+gl.getShaderInfoLog( vshd ));
   let fshd = gl.createShader( gl.FRAGMENT_SHADER );
   gl.shaderSource( fshd, fragmentSrc );
   gl.compileShader( fshd );
   if (!gl.getShaderParameter( fshd, gl.COMPILE_STATUS ))
      throw new Error( "Unable to compile shader: "+gl.getShaderInfoLog( fshd ));
   let prog = gl.createProgram();
   gl.attachShader( prog, vshd );
   gl.attachShader( prog, fshd );
   gl.linkProgram( prog );
   if (!gl.getProgramParameter( prog, gl.LINK_STATUS ))
      throw new Error( "Unable to link program: "+gl.getProgramInfoLog( prog ));
   return prog;
}

function init() {
   var c = document.getElementById("glCanvas");
   var gl = c.getContext('webgl2');
   if (!gl)
      throw new Error("WebGL unsupported!");

   // Clear screen
   gl.clearColor(0, 0, 0, 0);
   gl.clear(gl.COLOR_BUFFER_BIT);

   /*
    2___3
    |\  |
    | \ |
    |__\|
    0   1
   */
   var vertexPosBuffer = gl.createBuffer();
   gl.bindBuffer(gl.ARRAY_BUFFER, vertexPosBuffer);
   var vertices = [
      -1, -1,
       1, -1,
      -1,  1,
       1,  1 ];
   gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
   vertexPosBuffer.itemSize = 2;
   vertexPosBuffer.numItems = 4;

   // Create our shader program
   var vs = document.querySelector("#vertex-shader").text.trim();
   var fs = document.querySelector("#fragment-shader").text.trim();
   var program = createProgram(gl, vs, fs);
   gl.useProgram(program);

   // Get and set vertex attribute
   program.vertexPosAttrib = gl.getAttribLocation(program, 'pos');
   gl.enableVertexAttribArray(program.vertexPosAttrib);
   gl.vertexAttribPointer(program.vertexPosAttrib, 2, gl.FLOAT, false, 0, 0);

   // Draw triangles
   gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
}
window.onload = init;

Example Output:

Below we can see an example of what you should expect from this demo.