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>
Go to the next tutorial, WebGL Gradient.