# WebGL / Tutorials / #4 Animation

## Changes over Time

This is a follow-up to WebGL Tutorial #3 Gradient. We’ll start from what we built in the previous tutorial. This time we’re going to draw changes over time to create animation.

View the demo or jump ahead and edit the code.

## Drawing Repeatedly

To see changes over time, we need to run our WebGL program repeatedly to draw many times a second. We can use JavaScript’s
`requestAnimationFrame`

function to draw
as many frames per second (FPS) as our browser and CPU will allow, typically up to 60 FPS.

The `gl.drawArrays(gl.TRIANGLE_STRIP, 0, vertices.length);`

call
runs our WebGL program once and draws the results on the canvas. Let’s put that in a
`draw()`

function and have it call itself via `requestAnimationFrame`

.
Then call it once to start the animation loop.

` ````
function draw() {
gl.drawArrays(gl.TRIANGLE_STRIP, 0, vertices.length);
requestAnimationFrame(draw);
}
draw();
```

## What time is it?

Repeatedly drawing is pointless if the shaders draw the same thing every time. To change what they draw, they need to
know what time it is and change their behavior based on the current time. The GPU can’t access the system clock directly,
so we need to access it in JavaScript and pass it to our WebGL program. We will use a **uniform**
variable, similar to `canvasSize`

. Unlike `canvasSize`

, our `time`

variable will only be a single floating point number,
which we define with `gl.uniform1f(...)`

:

` ````
const timeUniform = gl.getUniformLocation(program, 'time');
gl.uniform1f(timeUniform, performance.now() / 1000);
```

The `1f`

means a 1D float value, which is just a floating point number (not a 1D vector containing a float).

`performance.now()`

returns the time since the page loaded, in milliseconds.
We convert it to seconds with the `/ 1000`

because I prefer to work with seconds.

We want to continuously update the time, so we’ll set the value of `timeUniform`

inside our `draw()`

loop:

` ````
const timeUniform = gl.getUniformLocation(program, 'time');
function draw() {
gl.uniform1f(timeUniform, performance.now() / 1000);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, vertices.length);
requestAnimationFrame(draw);
}
draw();
```

A rule of thumb is to keep the `draw()`

loop as lightweight and
fast as possible. Only do what you absolutely need to in there and let the GPU do the heavy lifting.

## Functions of Time

We have everything we need to make a shader that animates. We define our `time`

uniform in the shader,
just like we did for `canvasSize`

, except this is a `float`

.
Let’s adjust the gradient fragment shader from the previous tutorial
so it only draws the gradient when the x-coordinate is less than a threshold that varies over time:

` ````
uniform vec2 canvasSize;
uniform float time;
out vec4 fragColor;
void main() {
vec2 coord = gl_FragCoord.xy/canvasSize.xy;
if (coord.x < sin(time)) {
fragColor = vec4(coord.x, coord.y, 1.-coord.x, 1);
} else {
fragColor = vec4(1,1,1,1); // white
}
}
```

This animates the width of the gradient in a loop.
`sin()`

is the sine function, which, along
with `cos()`

(cosine), is great for creating looping animations. These functions oscillate between -1 and 1, but our
normalized `coord`

goes from 0 to 1. The negative `sin()`

values result in the gradient being invisible for half
of the loop. We can shift the range from [-1, 1] to [0, 1] with some arithmetic:

` ````
if (coord.x < (sin(time) + 1.)/2.) { // oscillate between 0 and 1
```

Now the gradient is visible at all times (except the moment when `(sin(time)+1.)/2.`

is 0).
Shifting, stretching, and otherwise transforming ranges of numbers like this happens a lot in graphics programming.

## Stretching the Gradient

At this point you know how to animate shaders. The rest of this tutorial will make the animation more complex by working through several practical issues.

Let’s store the `sin()`

expression in a variable and use it to animate in the `y`

direction too:

` ````
float threshold = (sin(time) + 1.)/2.; // oscillate between 0 and 1
if (coord.x < threshold && coord.y < threshold) {
```

This looks like a growing/shrinking square window into a stationary gradient. All you see is blue when the square is small:

What if we want the gradient to stretch with the square? Instead of changing the threshold for where we draw the gradient, we can change the coordinate system.

` ````
// float threshold = (sin(time) + 1.)/2.;
float scale = (sin(time) + 1.)/2.;
coord = vec2(coord.x/scale, coord.y/scale);
if (coord.x > 0. && coord.x < 1. &&
coord.y > 0. && coord.y < 1.) {
```

I used `coord = vec2(coord.x/scale, coord.y/scale);`

for clarity. You can perform arithmetic
operations directly between a vector and a float and it will apply the operation to each element in the vector. It’s preferable to
take advantage of that and write this code as the equivalent `coord = coord/scale;`

. We can further
simplify with `coord /= scale;`

.

Our coordinate system in in the range [0,1], and `scale`

oscillates between 1 and 0, so the resulting
coordinate system animates between a range of [0,1] and [0,infinity].
You might have noticed there’s a division by 0 at infinity. The GPU program doesn’t
crash or raise an error. It acts like `1/0`

in JavaScript, which is the floating point value for `Infinity`

.

The updated `if`

condition checks that x and y are between 0 to 1. When the coordinate system has the range [0,1],
the gradient fills the canvas. As the coordinate system’s bounds go towards infinity, we’re effectively zooming out
on our gradient, which still only goes from 0 to 1, until eventually the gradient is so small it briefly disappears just as we
start zooming back in.

Because we’ve transformed the entire coordinate system, and the gradient is computed from those coordinates via
`vec4(coord.x, coord.y, 1.-coord.x, 1);`

, the gradient stretches with our square:

## Centering the Animation

Lastly, let’s center the animation. This requires additional adjustments to the coordinate system.
Inverse to how we adjusted `sin()`

to go from -1 to 1, we can change [0,1] to [-1,1] by multiplying by 2 and subtracting 1:

` ````
vec2 coord = gl_FragCoord.xy/canvasSize.xy; // [0,1]
coord = 2.*coord - 1.; // [-1,1]
```

This makes the coordinate system go to -1 in all directions with the origin (0,0) in the center. This is a very common coordinate system and you may find yourself using it a lot.

Our square is only in the upper right quadrant because its corner is at the center (0,0),
so update the `if`

condition to make it go to the edges of the coordinate system:

` ````
if (coord.x > -1. && coord.x < 1. &&
coord.y > -1. && coord.y < 1.) {
```

We can simplify this by taking the absolute value of the coordinates:

` ````
if (abs(coord.x) < 1. && abs(coord.y) < 1.) {
```

But wait! The gradient is messed up. Everything except the upper right quadrant is wrong. The entire lower left quadrant is blue:

We’re using the coordinates to compute our RGB color values via `vec4(coord.x, coord.y, 1.-coord.x, 1);`

but we made the coordinates go from -1 to 1 and RGB values go from 0 to 1. So we have to convert back to the original
0 to 1 range with `coord = (coord + 1.)/2.;`

If our coordinate system can go to infinity when we divide by 0 when `scale`

is 0, it doesn’t seem like adding 1 and dividing
by 2 to `coord`

should really work. Keep in mind we are only going through this code path when
`abs(coord.x) < 1. && abs(coord.y) < 1.`

,
so we really are just converting [-1,1] back to [0,1].

Putting it all together:

` ````
void main() {
vec2 coord = gl_FragCoord.xy/canvasSize.xy; // [0,1]
coord = 2.*coord - 1.; // [-1,1]
float scale = (sin(time) + 1.)/2.; // from 1 to 0
coord /= scale; // from [-1,1] to [-infinity,infinity]
if (abs(coord.x) < 1. && abs(coord.y) < 1.) {
coord = (coord + 1.)/2.; // [0,1]
fragColor = vec4(coord.x, coord.y, 1.-coord.x, 1);
} else {
fragColor = vec4(1,1,1,1);
}
}
```

This pattern of translating (moving) the coordinates, scaling/rotating, and then translating back to their original position is very common in graphics programming.

## Result

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;
uniform vec2 canvasSize;
uniform float time;
out vec4 fragColor;
void main() {
vec2 coord = gl_FragCoord.xy/canvasSize.xy;
coord = 2.*coord - 1.;
float scale = (sin(time) + 1.)/2.;
coord /= scale;
if (abs(coord.x) < 1. && abs(coord.y) < 1.) {
coord = (coord + 1.)/2.;
fragColor = vec4(coord.x, coord.y, 1.-coord.x, 1);
} else {
fragColor = vec4(1,1,1,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);
const canvasSizeUniform = gl.getUniformLocation(program, 'canvasSize');
gl.uniform2f(canvasSizeUniform, canvas.width, canvas.height);
const timeUniform = gl.getUniformLocation(program, 'time');
function draw() {
gl.uniform1f(timeUniform, performance.now() / 1000);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, vertices.length);
requestAnimationFrame(draw);
}
draw();
</script>
</body>
</html>
```

That’s it for the basic WebGL tutorials. Hopefully this has taught you enough to get started with your own experiments.