Skip to main content
← 3D on the Web

Lesson 2 of 5

A Program Per Pixel

Two triangles covering the canvas, and one small program that paints every pixel of them.

What you will learn

  • varyings
  • uniforms over time
  • TRIANGLE_STRIP
  • length() and sin()
  • mix()
  • the render loop

Before you start

Your First Triangle. The setup code is nearly the same. What changes is that the shape stops mattering and the fragment shader starts doing all the work.

What this page does

Rings move out from the middle of the canvas. There is no ring anywhere in the code. There is no circle either. There are two triangles making a rectangle, and one program that decides, for each of 240,000 pixels, what colour that pixel should be.

How a pixel knows where it is

The vertex shader knows where each corner is. The fragment shader needs to know where each pixel is. A varying is the bridge.

In the vertex shader
attribute vec2 corner;
varying vec2 spot;

void main() {
    spot = corner;
    gl_Position = vec4(corner, 0.0, 1.0);
}

spot is written once per corner and read once per pixel. In between, the value is blended. A pixel halfway between two corners receives a value halfway between theirs. Nobody writes that blending. It is what the hardware is for, and it is where the word varying comes from.

Rings out of a sine wave

The whole of the picture
float distance = length(spot);
float wave = sin(distance * rings - time) * 0.5 + 0.5;

gl_FragColor = vec4(mix(colourA, colourB, wave), 1.0);
PieceWhat it does
length(spot)How far this pixel is from the middle.
sin(...)Goes up and down over and over as its input grows. That is the rings.
* ringsSqueezes more waves into the same distance.
- timeSlides the whole pattern outwards as time grows.
* 0.5 + 0.5sin answers between -1 and 1. Colours need 0 to 1.
mix(a, b, wave)Slides between two colours. 0 is all of the first, 1 is all of the second.

Change the wave

Set the speed to 0 first. A still picture is much easier to reason about than a moving one.

9.0

How many times sin() goes round between the middle and the corner.

1.2

Set it to 0 to stop the rings. A still picture is easier to think about.

Change the colours

Move these and watch your page. The code changes too.

#1e1b4b
#22d3ee

mix() slides between these two, and the wave decides how far.

Four corners, two triangles

var corners = new Float32Array([
    -1, -1,
     1, -1,
    -1,  1,
     1,  1
]);

gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);

gl.TRIANGLES would need six corners for two triangles, with two of them written twice. gl.TRIANGLE_STRIP reuses the last two corners for the next triangle, so four corners make two triangles. The rectangle covers the canvas exactly, because the canvas runs from -1 to 1.

Making it move

function drawFrame(milliseconds) {
    gl.uniform1f(timeSlot, (milliseconds / 1000) * SPEED);
    gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
    requestAnimationFrame(drawFrame);
}

requestAnimationFrame(drawFrame);

This is the same loop as the platformer course. Change one number, draw, ask for the next frame. The only value that changes between one frame and the next is time. The corners never move.

The honest limitation

None of this is 3D. It is a flat rectangle with a clever paint job, and no amount of ring detail will make it a shape you could walk around. What makes it worth doing is that the paint job is the same mechanism a 3D engine uses for every lit surface in every game you have played. The next lesson starts using one.

Try these changes

Make these changes in the files beside this guide. Each one only uses what this lesson has already covered. Tick each one when it works.

Check yourself

Three quick questions on this lesson. Get them all right to finish the lesson and earn a pack.

Loading your answers

Teacher's versionHow to run this lesson, what to check, and any answers. Students do not need this.

How to teach it

  • Set SPEED to 0 before anything else. A moving picture is hard to talk about and every question takes twice as long.
  • Set RINGS to 2, then 40, asking for a prediction each time. Then let them find a value they like. This is the part they remember.
  • The spot.x challenge is the best single question in the lesson. Give it time and do not answer it. A pixel's colour now depends only on how far right it is, so every column is one colour, so the picture is stripes.
  • Draw the sine wave on the board with the y axis labelled -1 to 1, then draw the line at 0 and shade what goes below it. That is the whole reason for * 0.5 + 0.5.
  • If a student asks whether this is how real games work: yes, and say so. Water, fire, fog and every glowing thing are fragment shaders.