Lesson 1 of 5
Your First Triangle
One green triangle, drawn by the graphics card. You write the two programs that put it there.
What you will learn
- <canvas> for WebGL
- getContext("webgl")
- vertex shader
- fragment shader
- GLSL
- buffers
- attributes
- uniforms
- clip space
- drawArrays
Before you start
The JavaScript course. If you did the platformer course you have used a canvas before, with getContext("2d"). This lesson asks the same canvas for a different tool.
What this page does
It puts one green triangle on the screen. That is all it does, and it takes about fifty lines. By the end of this course you will draw a whole solar system in fewer lines than that. Both facts are worth holding at once.
Two programs, not one
The program you hand the graphics card comes in two halves. They run at different times and they have different jobs.
| Shader | Runs | Its job |
|---|---|---|
| Vertex shader | Once per corner. Three times here. | Decide where that corner goes. |
| Fragment shader | Once per pixel inside the shape. Thousands of times. | Decide what colour that pixel is. |
Both are written in a language called GLSL, not in JavaScript. That is why they sit in triangle.js as strings. The graphics card compiles them while the page is running.
precision mediump float;
uniform vec3 colour;
void main() {
gl_FragColor = vec4(colour, 1.0);
}A colour here is four numbers: red, green, blue, and alpha. Each one runs from 0.0 to 1.0. Not from 0 to 255, and not #4ade80. The toGl function in the file turns a hex colour into the three numbers WebGL wants, because hex is how the web writes colours and this is not the web any more.
Where things are
WebGL does not measure in pixels. It has its own coordinates, and they are the same on every canvas whatever size it is.
(0, 1)
|
(-1, 0) ---+--- (1, 0)
|
(0, -1)- The middle of the canvas is
0, 0. - The left edge is
-1. The right edge is1. - Up is positive. On a 2D canvas, down is positive. This catches everybody once.
Move the corners
These two sliders change the numbers in the `corners` list. Watch the triangle, and watch the number change in `triangle.js` at the same time.
1 reaches the edge of the canvas. There are no pixels in these numbers.
Getting numbers into a shader
A shader cannot see your JavaScript variables. There are two ways to hand it a number, and the difference between them is the whole model.
| Changes | Used here for | |
|---|---|---|
| attribute | Per corner | Where each corner is |
| uniform | Never, during one draw | The colour of the whole triangle |
The corners live in a buffer, which is a block of memory on the graphics card. You copy your numbers into it once. The card reads them from there every time it draws, without asking JavaScript again.
Change the colours
The triangle colour is a uniform. The background is not part of the drawing at all, it is what the canvas is wiped to first.
The fragment shader paints every pixel this colour.
When nothing appears
The only way to find out is to ask. That is what buildShader does in this file, every single time:
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
report.textContent = "Shader problem: " + gl.getShaderInfoLog(shader);
return null;
}Write that check before you write your first shader, not after your first blank canvas. Try it now: delete a semicolon inside fragmentShaderSource and run the page. It tells you the line.
The order it happens in
Ask the canvas for WebGL
A canvas can hand out different drawing tools.
"2d"is the one from the platformer course."webgl"is the graphics card.var gl = canvas.getContext("webgl");Compile both shaders
Each one is compiled on its own, and each one can fail on its own.
Link them into a program
A program is the two shaders joined together. The graphics card runs programs, not loose shaders.
var program = gl.createProgram(); gl.attachShader(program, vertexShader); gl.attachShader(program, fragmentShader); gl.linkProgram(program); gl.useProgram(program);Put the corners in a buffer
Six numbers, read two at a time as x and y. Then tell the vertex shader that its
cornerattribute is made of 2 floats at a time.Wipe the canvas, then draw
clearfills it with the background.drawArraysis the line that actually draws. Three corners, read as one triangle.gl.clear(gl.COLOR_BUFFER_BIT); gl.drawArrays(gl.TRIANGLES, 0, 3);
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
- Run it first, then change
TRIANGLEto another colour, then reload. Do not explain anything for the first five minutes. - Change one number in
cornersand reload. Do it three or four times, letting the class predict which corner moves, before you name a single API function. - Break a shader on purpose early. The habit you want is reading the message, and the only way to build it is to see one.
- Teach the coordinate system with the diagram on the board, not in code. The up-is-positive flip is the single most common source of confusion for anyone who has done the platformer course.
- Do not try to cover buffers properly in this session. It is enough that the numbers get copied to the card once and live there.