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.
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
float distance = length(spot);
float wave = sin(distance * rings - time) * 0.5 + 0.5;
gl_FragColor = vec4(mix(colourA, colourB, wave), 1.0);| Piece | What 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. |
* rings | Squeezes more waves into the same distance. |
- time | Slides the whole pattern outwards as time grows. |
* 0.5 + 0.5 | sin 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.
How many times sin() goes round between the middle and the corner.
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.
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
SPEEDto 0 before anything else. A moving picture is hard to talk about and every question takes twice as long. - Set
RINGSto 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.xchallenge 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.