WebGL is an open web standard that allows you to create highly efficient graphical applications to run in web browsers through hardware acceleration.
This article will walk you through how to create a WebGL context and render a simple triangle (as shown in the picture below) using WebGL 2.0.
Setting up WebGL: To develop and run a WebGL application all you need is a web browser and a code editor. That’s it! You don’t need to install anything extra on your system. WebGL is currently supported by the major browsers Apple (Safari), Google (Chrome), Microsoft (Edge), Opera (Opera web browser), and Mozilla (Firefox). So, make sure to update your browser to the latest version.
Creating a WebGL context: To begin we create an HTML file with an <canvas> element to draw and display animations in the browser.
index.html
<!DOCTYPE html> < html > < head > < title ></ title > </ head > < body > < canvas id = "canvas" width = "400" height = "300" > Your browser does not support HTML5 </ canvas > < script src = "app.js" ></ script > </ body > </ html > |
Now, to start rendering content, we must first create a WebGL context using JavaScript.
app.js
// Request html canvas element var canvas = document.getElementById( "canvas" ); // Create a WebGL rendering context var gl = canvas.getContext( "webgl2" ); // Tell user if their browser does not support WebGL if (!gl) { alert( "Your browser does not support WebGL" ); } // Set the color of the canvas. // Parameters are RGB colors (red, green, blue, alpha) gl.clearColor(0, 0.6, 0.0, 1.0); // Clear the color buffer with specified color gl.clear(gl.COLOR_BUFFER_BIT); |
If the browser supports WebGL, you see an empty box with a green background waiting to receive content.
Defining the Shaders: So far, we have our canvas and WebGL prepared and ready to render some content. Now, let’s define our shaders.
Shaders are programs that are executed in the GPU written in OpenGL Shading Language (GLSL) and allow us to perform mathematical operations to transform vertices and/or colors. In WebGL, we just need to provide two shades: vertex shader, and fragment shader. The vertex shader is used to specify the final position of the vertices, and the fragment shader is used to define to the color surface of our model. GLSL is semantically similar to the C language. We have a main function to perform all the operations and we have input and output parameters denoted as in, out, and data-types as vec2, vec3, vec4.
Let’s take a look at these two shaders for our simple triangle.
Javascript
// Define shaders: vertex shader and fragment shader const shaders = { vs: ` #version 300 es in vec2 vertPosition; in vec3 vertColor; out vec3 fragColor; void main() { fragColor = vertColor; gl_Position = vec4(vertPosition, 0, 1); }`, fs: ` #version 300 es precision mediump float; in vec3 fragColor; out vec4 outColor; void main() { outColor = vec4(fragColor, 1); }` }; |
Once defined the shaders in GLSL, we are now ready to create the shader objects and compile them so that WebGLProgram can use them. We use gl.createShader(), gl.shaderSource(), and gl.compileShader() methods for our vertexShader and fragmentShader.
Javascript
// Create WebGl Shader objects var vertexShader = gl.createShader(gl.VERTEX_SHADER); var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); // sets the source code of the WebGL shader gl.shaderSource(vertexShader, shaders.vs); gl.shaderSource(fragmentShader, shaders.fs); // Compile GLSL Shaders to a binary data // so WebGLProgram can use them gl.compileShader(vertexShader); gl.compileShader(fragmentShader); |
Creating the WebGLProgram: The next step is to create the WebGLProgram to host our vertex shader and fragment shader in the GPU.
Javascript
// Create a WebGLProgram var program = gl.createProgram(); // Attach pre-existing shaders gl.attachShader(program, vertexShader); gl.attachShader(program, fragmentShader); gl.linkProgram(program); |
Creating the Triangle Object: In this step, we create the vertex attributes and the input data to the shaders (the triangle). We define two attributes for this triangle “position” and “color”. We create a “data” array for each attribute using 32 bits as this is required in the vertex buffers. The “data” arrays contain the coordinates of the triangle in the XY plane and the RGB colors for each vertex of the triangle.
Javascript
const vertexAttributes = { position: { numberOfComponents: 2, // X and Y ordered pair coordinates data: new Float32Array([0.0, 0.5, -0.5, -0.5, 0.5, -0.5]) }, color: { numberOfComponents: 3, // RGB triple data: new Float32Array([1, 0, 0, 0, 1, 0, 0, 0, 1]) } }; |
Creating Vertex Buffers, and Rendering: Now, we need to create buffers to store the vertex attributes. In OpenGL/WebGL applications, buffers are memory spaces in the graphics card (VRAM) that are used by the shaders to perform mathematical operations. Remember, shaders are executed in the GPU and we need the memory from the graphics card for fast data access. Here, we create two buffers one for the position attribute and the other one for the color attribute.
We then use gl.getAttribLocation() and gl.vertexAttribPointer() to tell the shaders how we are sending the data in the vertex attributes.
Finally, we execute the shaders using gl.drawArrays()
Javascript
// Create an initialize vertex buffers var vertexBufferObjectPosition = gl.createBuffer(); var vertexBufferObjectColor = gl.createBuffer(); // Bind existing attribute data gl.bindBuffer(gl.ARRAY_BUFFER, vertexBufferObjectPosition); gl.bufferData(gl.ARRAY_BUFFER, vertexAttributes.position.data, gl.STATIC_DRAW); var positionAttribLocation = gl.getAttribLocation(program, 'vertPosition' ); gl.vertexAttribPointer(positionAttribLocation, vertexAttributes.position.numberOfComponents, gl.FLOAT, gl.FALSE, 0, 0); gl.enableVertexAttribArray(positionAttribLocation); // Bind existing attribute data gl.bindBuffer(gl.ARRAY_BUFFER, vertexBufferObjectColor); gl.bufferData(gl.ARRAY_BUFFER, vertexAttributes.color.data, gl.STATIC_DRAW); var colorAttribLocation = gl.getAttribLocation(program, 'vertColor' ); gl.vertexAttribPointer(colorAttribLocation, vertexAttributes.color.numberOfComponents, gl.FLOAT, gl.FALSE, 0, 0); gl.enableVertexAttribArray(colorAttribLocation); // Set program as part of the current rendering state gl.useProgram(program); // Draw the triangle gl.drawArrays(gl.TRIANGLES, 0, 3); |
Putting All Together: Here we have all pieces together in the app.js file. To test this code, please, download both the html.index and the app.js files and put them in the same directory, and then open the index.html on your web browser.
index.html
<!DOCTYPE html> < html > < head > < title ></ title > </ head > < body > < canvas id = "canvas" width = "400" height = "300" > Your browser does not support HTML5 </ canvas > < script src = "app.js" ></ script > </ body > </ html > |
app.js
// Request html canvas element var canvas = document.getElementById( "canvas" ); // Create a WebGL rendering context var gl = canvas.getContext( "webgl2" ); // Tell user if their browser does not support WebGL if (!gl) { alert( "Your browser does not support WebGL" ); } // Set the color of the canvas. // Parameters are RGB colors (red, green, blue, alpha) gl.clearColor(0, 0.6, 0.0, 1.0); // Clear the color buffer with specified color gl.clear(gl.COLOR_BUFFER_BIT); // Define shaders: vertex shader and fragment shader const shaders = { vs: ` #version 300 es in vec2 vertPosition; in vec3 vertColor; out vec3 fragColor; void main() { fragColor = vertColor; gl_Position = vec4(vertPosition, 0, 1); }`, fs: ` #version 300 es precision mediump float; in vec3 fragColor; out vec4 outColor; void main() { outColor = vec4(fragColor, 1); }` }; // Create WebGl Shader objects var vertexShader = gl.createShader(gl.VERTEX_SHADER); var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); // sets the source code of the WebGL shader gl.shaderSource(vertexShader, shaders.vs); gl.shaderSource(fragmentShader, shaders.fs); // Compile GLSL Shaders to a binary data so // WebGLProgram can use them gl.compileShader(vertexShader); gl.compileShader(fragmentShader); // Create a WebGLProgram var program = gl.createProgram(); // Attach pre-existing shaders gl.attachShader(program, vertexShader); gl.attachShader(program, fragmentShader); gl.linkProgram(program); const vertexAttributes = { position: { // X and Y ordered pair coordinates numberOfComponents: 2, data: new Float32Array([0.0, 0.5, -0.5, -0.5, 0.5, -0.5]) }, color: { numberOfComponents: 3, // RGB triple data: new Float32Array([1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0]) } }; // Create an initialize vertex buffers var vertexBufferObjectPosition = gl.createBuffer(); var vertexBufferObjectColor = gl.createBuffer(); // Bind existing attribute data gl.bindBuffer(gl.ARRAY_BUFFER, vertexBufferObjectPosition); gl.bufferData(gl.ARRAY_BUFFER, vertexAttributes.position.data, gl.STATIC_DRAW); var positionAttribLocation = gl.getAttribLocation(program, 'vertPosition' ); gl.vertexAttribPointer(positionAttribLocation, vertexAttributes.position.numberOfComponents, gl.FLOAT, gl.FALSE, 0, 0); gl.enableVertexAttribArray(positionAttribLocation); // Bind existing attribute data gl.bindBuffer(gl.ARRAY_BUFFER, vertexBufferObjectColor); gl.bufferData(gl.ARRAY_BUFFER, vertexAttributes.color.data, gl.STATIC_DRAW); var colorAttribLocation = gl.getAttribLocation(program, 'vertColor' ); gl.vertexAttribPointer(colorAttribLocation, vertexAttributes.color.numberOfComponents, gl.FLOAT, gl.FALSE, 0, 0); gl.enableVertexAttribArray(colorAttribLocation); // Set program as part of the current rendering state gl.useProgram(program); // Draw the triangle gl.drawArrays(gl.TRIANGLES, 0, 3); |
Output: