Hello Triangle

A Minimal WebGL 2 Hello Triangle

A minimal program needs two shaders linked into a program, a buffer, an attribute pointer and a draw call. Compiling and linking repeat everywhere, so they go in webgl-helper.js, which later sections extend:

webgl-helper.js, first version: shaders, programs and buffersJavaScript
// webgl-helper.js: a small WebGL 2 toolkit that grows through Chapter 3
const GLH = {
  shader(gl, type, source) {                 // compile; throw the info log on failure
    const shader = gl.createShader(type);
    gl.shaderSource(shader, source.trim());  // #version must be the very first line
    gl.compileShader(shader);
    if (gl.getShaderParameter(shader, gl.COMPILE_STATUS)) return shader;
    const stage = type === gl.VERTEX_SHADER ? 'Vertex' : 'Fragment';
    throw new Error(`${stage} shader failed to compile:\n${gl.getShaderInfoLog(shader)}`);
  },
  program(gl, vsSource, fsSource) {          // compile both, link, throw the link log
    const program = gl.createProgram();
    gl.attachShader(program, GLH.shader(gl, gl.VERTEX_SHADER, vsSource));
    gl.attachShader(program, GLH.shader(gl, gl.FRAGMENT_SHADER, fsSource));
    gl.linkProgram(program);
    if (gl.getProgramParameter(program, gl.LINK_STATUS)) return program;
    throw new Error('Program failed to link:\n' + gl.getProgramInfoLog(program));
  },
  buffer(gl, data, target = gl.ARRAY_BUFFER) {   // upload a typed array, leave it bound
    const buffer = gl.createBuffer();
    gl.bindBuffer(target, buffer);
    gl.bufferData(target, data, gl.STATIC_DRAW);
    return buffer;
  },
};

The triangle needs three 2D positions, a vertex shader that passes them on and a one-color fragment shader:

Hello triangle in WebGL 2HTML
<canvas id="c" width="960" height="340" style="width:480px;height:170px"></canvas>
<script src="webgl-helper.js"></script>
<script>
const gl = document.getElementById('c').getContext('webgl2');
const program = GLH.program(gl, `#version 300 es
in vec2 aPosition;
void main() { gl_Position = vec4(aPosition, 0.0, 1.0); }`, `#version 300 es
precision mediump float; out vec4 outColor;
void main() { outColor = vec4(0.12, 0.37, 0.55, 1.0); }`);
gl.useProgram(program);
GLH.buffer(gl, new Float32Array([0.0, 0.85, -0.45, -0.85, 0.45, -0.85]));   // x, y pairs
const aPosition = gl.getAttribLocation(program, 'aPosition');
gl.enableVertexAttribArray(aPosition);
gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, 0, 0);   // 2 floats per vertex
gl.clearColor(0.96, 0.95, 0.92, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.TRIANGLES, 0, 3);
window.__done = true;           // tells the book's screenshot tool the frame is ready
</script>
Browser output of Listing 3.4
Browser output of 4

Clip space runs from -1 to 1 on both axes, so the triangle stretches with the canvas until MVP Matrices adds a projection. The drawing buffer is twice the CSS size to stay sharp on high-density screens.

Hello triangle in WebGL 2 with a small compile-and-link helperHTMLLive
<!doctype html>
<style>
  body { margin: 0; background: #f7f4ee; }
  canvas { display: block; width: 100%; max-width: 480px; height: auto; }
</style>
<canvas id="c" width="960" height="340"></canvas>
<script>
// webgl-helper.js, first version: a small WebGL 2 toolkit
const GLH = {
  shader(gl, type, source) {                 // compile; throw the info log on failure
    const shader = gl.createShader(type);
    gl.shaderSource(shader, source.trim());  // #version must be the very first line
    gl.compileShader(shader);
    if (gl.getShaderParameter(shader, gl.COMPILE_STATUS)) return shader;
    const stage = type === gl.VERTEX_SHADER ? 'Vertex' : 'Fragment';
    throw new Error(`${stage} shader failed to compile:\n${gl.getShaderInfoLog(shader)}`);
  },
  program(gl, vsSource, fsSource) {          // compile both, link, throw the link log
    const program = gl.createProgram();
    gl.attachShader(program, GLH.shader(gl, gl.VERTEX_SHADER, vsSource));
    gl.attachShader(program, GLH.shader(gl, gl.FRAGMENT_SHADER, fsSource));
    gl.linkProgram(program);
    if (gl.getProgramParameter(program, gl.LINK_STATUS)) return program;
    throw new Error('Program failed to link:\n' + gl.getProgramInfoLog(program));
  },
  buffer(gl, data, target = gl.ARRAY_BUFFER) {   // upload a typed array, leave it bound
    const buffer = gl.createBuffer();
    gl.bindBuffer(target, buffer);
    gl.bufferData(target, data, gl.STATIC_DRAW);
    return buffer;
  },
};

// The triangle: three 2D positions, a pass-through vertex shader, a one-colour fragment shader.
const gl = document.getElementById('c').getContext('webgl2');
const program = GLH.program(gl, `#version 300 es
in vec2 aPosition;
void main() { gl_Position = vec4(aPosition, 0.0, 1.0); }`, `#version 300 es
precision mediump float; out vec4 outColor;
void main() { outColor = vec4(0.12, 0.37, 0.55, 1.0); }`);
gl.useProgram(program);
GLH.buffer(gl, new Float32Array([0.0, 0.85, -0.45, -0.85, 0.45, -0.85]));   // x, y pairs
const aPosition = gl.getAttribLocation(program, 'aPosition');
gl.enableVertexAttribArray(aPosition);
gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, 0, 0);   // 2 floats per vertex
gl.clearColor(0.96, 0.95, 0.92, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.TRIANGLES, 0, 3);
</script>