Back

Intro to WebGL

Everything changes here. In 2D Canvas, you called drawing commands — fillRect, arc, drawImage — and the browser figured out the pixels. WebGL is different. You're programming the GPU directly, writing tiny programs called shaders that run on thousands of data points in parallel.

What is WebGL?

The GPU is a massively parallel processor. A 400×300 canvas has 120,000 pixels — the GPU can compute the colour of every single one simultaneously. That's the power you're tapping into.

The WebGL pipeline works like this: you define vertices (points in space), the GPU runs a vertex shader on each one to position it, then it rasterises (figures out which pixels are inside the shape), then it runs a fragment shader on every pixel to decide its colour. Those pixels hit the screen.

The shaders are written in GLSL (OpenGL Shading Language) — a C-like language that runs on the GPU. Your JavaScript sets up the data and tells the GPU what to draw. The GLSL does the actual rendering.

Shaders

Two shaders, two jobs. The vertex shader positions things. The fragment shader colours them. Both are written in GLSL and compiled at runtime by the browser.

GLSL has types like float, vec2, vec3, vec4, and mat4. Vectors are everywhere — a position is a vec4, a colour is a vec4 (r, g, b, a). The vertex shader must set gl_Position — a vec4 in “clip space” where x and y range from -1 to +1 with (0, 0) at the centre. The fragment shader must set gl_FragColor — RGBA values from 0.0 to 1.0.

Three keywords you'll use constantly: attribute — per-vertex input data (like position), set from JavaScript. uniform — data that stays the same for every vertex/pixel in a draw call (like time or resolution). varying — data passed from vertex shader to fragment shader, automatically interpolated across the surface.

The precision mediump float; line in the fragment shader is required boilerplate — GLSL needs you to declare float precision. mediump is fine for everything we'll do. Memorise it and move on.

Your first triangle

Fair warning: this is more code than anything you've written so far. WebGL is a low-level API — even a single triangle requires compiling shaders, linking a program, creating buffers, and wiring up attributes. But every step has a purpose, and once you understand this one example, you have the foundation for everything else.

JavaScript

Try changing the vertex positions — clip space goes from -1 to +1 on both axes with (0, 0) at the centre. Move the top vertex higher with 0.0, 0.8. Change the colour in the fragment shader — vec4(0.42, 0.39, 1.0, 1.0) gives purple. What happens if you set a value above 1.0?

Colour with varyings

In the previous example every pixel was the same colour because the fragment shader hard-coded it. To get different colours per vertex, we add a second attribute (a_color) and pass it to the fragment shader via a varying. The magic: the GPU automatically interpolates the varying across the triangle surface. If one vertex is red and another is blue, the pixels between them blend smoothly — you get a gradient for free.

JavaScript

The classic rainbow triangle. Change the vertex colours — try making all three different shades of pink and orange. What happens if two vertices share the same colour? That edge becomes a flat fill. Try making the triangle bigger by pushing vertices towards 0.9.

Uniforms

Attributes are per-vertex. uniform values stay the same for an entire draw call — things like the current time, canvas resolution, or a colour controlled from JavaScript. You declare a uniform in the shader, then send it from JS with gl.uniform1f (or uniform2f, uniform4f, etc.) before each draw call. Combined with animate(), you can send the timestamp to the GPU every frame.

JavaScript

A spinning, pulsing triangle. Change the rotation speed by adjusting 0.001 in the vertex shader. Make the pulse more dramatic: 0.5 + 0.5 * sin(...). Try scaling instead of rotating — replace the rotation maths with vec2 scaled = a_position * (0.8 + 0.2 * sin(u_time * 0.002)).

Putting it all together

Many of the most beautiful WebGL effects are purely fragment shader work. You draw a fullscreen rectangle (two triangles forming a quad) and let the fragment shader compute every pixel from scratch using coordinates and time. gl_FragCoord.xy gives you the pixel position — divide by resolution to get normalised 0–1 coordinates. This is the same idea as the pixel manipulation you did with createImageData, except the GPU runs it on every pixel in parallel.

JavaScript

Fragment shader art — every pixel computed from maths, running in parallel on the GPU. Change the frequency multipliers (10.0, 12.0, 8.0) to morph the pattern. Replace atan(p.y, p.x) with p.x * p.y * 20.0 for a completely different look. Try removing the - u_time * ... from one channel to freeze that colour while the others animate.