We continue this pure WebGL tutorial with animation.

## Introduction

The previous post was dedicated to all the function calls necessary to render a simple triangle. In this post, we will animate this triangle and make it rotate around the three axes. The web page used in the previous post must be changed as following:

- Addition of a mechanism to periodically refresh the canvas and manually control the rotation
- Modification of the shader code to include a matrix-based transformation of vertices
- Creation of a function used to pre-compute the transformation matrix

The part about the transformation matrix necessitates some basic linear algebra but should be understandable with high-school level mathematics knowledge.

## Refactoring the code

First of all, in order to make the program more readable, we move some code around. The code of the shaders is placed at the beginning of the Javascript part:

// GLSL ES code to be compiled as fragment shader fragmentShaderCode= 'void main(void) {'+ ' gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);'+ '}'; // GLSL ES code to be compiled as vertex shader vertexShaderCode= 'attribute vec2 ppos;'+ 'void main(void) {'+ ' gl_Position = vec4(ppos.x, ppos.y, 0.0, 1.0);'+ '}';

Note that the vertex shader code above is not modified compared to the previous post, just re-arranged. It will be modified in the next section.

The second step is to separate the initialization code from the code called at each refresh. Hence the new method `draw()`

is created and filled with the end of the `start()`

function. The new global flag `running`

activates or deactivates the refresh commands (the control of this flag is described in a section below) and we define the variable `program`

as a global one:

var running = true; // True when the canvas is periodically refreshed var program; // The program object used in the GL context // Function called periodically to draw the scene function draw() { // Tests if canvas should be refreshed if(!running || !gl) return; // Sets clear color to non-transparent dark blue and clears context gl.clearColor(0.0, 0.0, 0.5, 1.0); gl.clear(gl.COLOR_BUFFER_BIT); // Draws the object gl.drawArrays(gl.TRIANGLES, 0, 3); gl.flush(); }

## Periodic Refresh

The `draw()`

function is now prepared. At the end of `start()`

, the standard Javascript function `setInterval`

is used to indicate to the browser that the function `draw()`

must be called periodically:

// The function draw() will be called every 40 ms setInterval('draw();', 40);

Using `setInterval`

is not a very good solution for animation because this function is completely independent of the refresh cycles of the screen (no smooth animation). Furthermore, it is called even if the page or tab is not currently visible, and a "best-guess" value for the delay in milliseconds must be set. There is an alternative to `setInterval`

called `requestAnimationFrame`

, but it is still in a draft stage and we won’t use it for now (it maybe addressed in a future post).

## Matrix multiplication in shader

The next change is in the vertex shader. We add a transformation implemented as a multiplication of a 4×4 floats matrix (type "

" in GLSL) by the 4D homogeneous coordinates (x, y, z, 1) of each vertex. The matrix is given in the variable named **mat4**`mvp`

:

// GLSL ES code to be compiled as vertex shader vertexShaderCode= 'attribute vec2 ppos;'+ 'uniform mat4 mvp;'+ 'void main(void) {'+ ' gl_Position = mvp * vec4(ppos.x, ppos.y, 0.0, 1.0);'+ '}';

The modifier "

" means that the value of this variable won’t change during the rendering operations. The product of a matrix by a vector is simply stated using the operator "**uniform**`*`

".

As a side note, standard operators are used in GLSL for common matrix or vector operations ("+", "-", "*" of course the vector or matrix sizes must match) and additional functions implement other operations:

is the dot product of two vectors (i.e. **dot(v1,v2)**`x1*y1+x2*y2+x3*y3` equal to 0 for perpendicular vectors),

the cross product (i.e. a vector perpendicular to **cross(v1,v2)**`v1` and `v2` with a length equal to 0 for parallel vectors) and

the component-wise product of two matrices. **matrixCompMult(m1,m2)**

## Animation control

Note: this section deals more with HTML and DOM than WebGL.

We want to control the animation with the mouse, more precisely we want to toggle the refresh flag "`running`

" and change the increments of the rotation angles. The flag "running" is already defined (see section "Refactoring the code"), and it is controlled directly as a click into the `<canvas>`

:

<canvas id='glcanvas' width=320 height=240 onclick='running = !running;'> Your browser may not support HTML5 </canvas>

The rotation angle increments are set by the user with drop down lists (`<select>`

elements with identifiers `dx`

, `dy`

, `dz`

) and the current angles are stored as `<div>`

elements (`ax`

, `ay`

, `az`

). The HTML code for the control of the rotation around the X axis is given hereafter:

<div style='display:inline-block;'>RX:</div> <div style='display:inline-block; width:1.5em;'id='ax'>0</div> <div style='display:inline-block;'> <select id='dx'> <option>-2<optio>-1<option>0<option selected="selected">+1<option>+2 </select> </div>

In the `draw()`

function, the rotation angles and increments are read, and the current angles get updated (EDIT: thanks *canta* I changed "innerText" by "innerHTML" for better compatibility e.g. with Firefox):

// Gets control value angles from HTML page via DOM var ax = parseInt(document.getElementById('ax').innerHTML, 10); var ay = parseInt(document.getElementById('ay').innerHTML, 10); var az = parseInt(document.getElementById('az').innerHTML, 10); // Use increments via DOM to update angles (still in degrees) ax = (ax + parseInt(document.getElementById('dx').value, 10) + 360) % 360; ay = (ay + parseInt(document.getElementById('dy').value, 10) + 360) % 360; az = (az + parseInt(document.getElementById('dz').value, 10) + 360) % 360; // Update HTML page with new values document.getElementById('ax').innerHTML = ax.toString(); document.getElementById('ay').innerHTML = ay.toString(); document.getElementById('az').innerHTML = az.toString();

After that, `ax`

, `ay`

and `az`

contain the current angles in degrees, and must be converted:

// Convert values to radians ax *= 2*Math.PI/360; ay *= 2*Math.PI/360; az *= 2*Math.PI/360;

## Combining the rotation matrices

At this point, almost everything is set up:

- The current rotation angles, in radian (i.e. 0 to 2*PI), are defined (in
`ax`

,`ay`

,`az`

) - They are incremented for each refresh
- The refresh is done in
`draw()`

using the code of the previous post - The shader uses a 4×4 "uniform" matrix that is multiplied with all input vertices
- This matrix must be defined in the Javascript code

In this section we look at the matrix itself, without a full explanation of the mathematics behind it (a future post will be dedicated to the complete transformation matrix). Hence we admit for now that the rotation around an axis is achieved by the product of a "rotation" matrix with the input coordinates. We will use the Computer Algebra System wxMaxima to pre-compute these matrix operations (the matrix formula won’t change during rendering). We use the variables `rx`, `ry`, `rz` for the angles. A rotation matrix around the Z axis has the following form (in wxMaxima: `rotz:matrix([cos(rz),-sin(rz),0,0],[sin(rz),cos(rz),0,0],[0,0,1,0],[0,0,0,1])`

):

Note that after multiplication of the matrix above by a point with coordinates `(x,y,z,1)`, the Z coordinate remains unchanged (as well as the 4th one), and the X and Y coordinates are rotated in the plane `(0,X,Y)` (hence it is a rotation around the Z axis):

The rotation matrices around the X and Y axes are obtained by replacing `az`

by `ax`

and `ay`

, and by permuting the different coordinates. The rotation matrix around the X axis is given hereafter (`rotx:matrix([1,0,0,0],[0,cos(rx),-sin(rx),0],[0,sin(rx),cos(rx),0],[0,0,0,1])`

):

The rotation around the Y axis (`roty:matrix([cos(ry),0,sin(ry),0],[0,1,0,0],[-sin(ry),0,cos(ry),0],[0,0,0,1])`

):

The 3 rotations are combined by multiplying the 3 matrices (here again wxMaxima is of great help!). The order of the matrix multiplications is important, i.e. multiplying rotX.rotY.rotZ is not the same as rotZ.rotY.rotX (matrix product is not "commutative"). In our case, we choose to have the rotation around the Z axis as last operation, hence in wxMaxima, we get the result of the formula `rotz.roty.rotx`

:

The matrix is quite complex now, and it only implements 3 rotations. The good thing is that this computation is necessary only once per refresh. It is implemented in the `getTransformationMatrix(rx,ry,rz)`

function:

// Gets a transformation matrix given the rotation angles function getTransformationMatrix(rx, ry, rz) { // Pre-computes trigonometric values (mainly for better readability) var cx = Math.cos(rx), sx = Math.sin(rx); var cy = Math.cos(ry), sy = Math.sin(ry); var cz = Math.cos(rz), sz = Math.sin(rz); // Returns matrix return new Float32Array([cy*cz, (sx*sy*cz-cx*sz), (sx*sz+cx*sy*cz), 0, cy*sz, (sx*sy*sz+cx*cz), (cx*sy*sz-sx*cz), 0, -sy, sx*cy, cx*cy, 0, 0, 0, 0, 1]); }

The object returned by this function is a one-dimensional array with 16 components, not a real 4×4 matrix. In the WebGL conventions, all matrices are represented as flat arrays when used from Javascript.

## Transformation matrix set for rendering

The transformation is now defined and must be communicated to the shader. First the address of the `mvp`

matrix in the shader must be determined. The function

returns the desired reference. After that the function **gl.getUniformLocation(program, variable-name)**

sets the value of the matrix:**gl.uniformMatrix4fv(uniform-ref, false, matrix-as-array)**

// Gets reference on the &quot;uniform&quot; 4x4 matrix transforming coordinates var amvp = gl.getUniformLocation(program, 'mvp'); if(amvp == -1) {alert('Error during uniform address retrieval');running=false;return;} // Creates matrix using rotation angles var mat = getTransformationMatrix(ax, ay, az); // Sets the model-view-projections matrix in the shader gl.uniformMatrix4fv(amvp, false, mat);

As usual, you can have a look at the result online. It should look like this:

## Summary

The main points regarding animation in WebGL:

- Functions used for the refresh are put in a new function, which is called periodically through
`setInterval("code", refresh-period)`

- A "transformation matrix" is set as a "uniform" variable in the shader
- A reference to such a variable is retrieved by the function
**gl.getUniformLocation(program, variable-name)** - The vertex transformation operation in the shader is the product matrix * coordinates
- Our transformation matrix is built as a product of three 2D rotations using three variable angles (formula found with wxMaxima)
- Once computed, the matrix is set in the shader through
**gl.uniformMatrix4fv(uniform-ref, false, matrix-as-array)**

#1 by

Cantaon November 10, 2012 - 23:13Love the tutorial! :D

Just one thing: i had to change innerText for innerHTML in order to make it also work in Firefox.

Thanks!

#2 by blogoben on November 13, 2012 - 20:31

Thanks, and thanks for the tip for Firefox compatibility.

#3 by Artneiver on September 18, 2015 - 22:30

this is fantastic tutorial about webgl..