Course outline
What a Shader Is
In one line
A shader is a tiny program the GPU runs thousands of times at once: a vertex shader places each corner of a triangle, a fragment shader colours each pixel. You write them in GLSL and try them in a Shadertoy-style sandbox.
Think of it like this
Picture a printing press with a stamp for every pixel. You do not tell it where each colour goes. You hand every worker the same rule card, "look at your own position and decide your colour", and all of them follow it at the same moment. The rule card is a shader.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- shader
- A small program that runs on the GPU. Vertex shaders place corners, fragment shaders colour pixels.
- GLSL
- The language shaders are written in. WebGL 2 uses GLSL ES 3.00.
- fragment
- One pixel-sized piece of a triangle, before it is finally written to the screen. It becomes a pixel if it passes the depth test (Topic 1.4).
- swizzle
- Reading a vector's parts in any order:
v.zyx,v.xy,v.rgb. - gl_FragCoord
- The pixel's own position in the picture, counted in pixels, with a half-pixel offset: the first pixel is (0.5, 0.5).
- render target
- A picture the GPU draws into instead of the screen. Reading it back gives exact pixel numbers.
Step by step
01Cover the screen
The vertex shader writes gl_Position = vec4(position.xy, 0.0, 1.0). A PlaneGeometry(2, 2) has corners at -1 and 1, which are already the screen's edges in clip space (Topic 1.4), so no matrices are needed.
02Colour each pixel
The fragment shader turns gl_FragCoord.xy / iResolution into uv, then 0.5 + 0.5 * cos(...) turns a cosine (-1 to 1) into a colour channel (0 to 1), with three phases for red, green and blue.
03Feed it numbers
uniforms.iTime.value is set from a frame counter, frame / 60, so a second always has 60 frames, in any browser.
04Read it back
probe(time) draws into an 8 × 4 render target and reads bytes with readRenderTargetPixels. The numbers it prints are exactly what the GPU computed.
Try it yourself
- 1
Change the colour rule
In the sandbox replace
uv.xyx + vec3(0.0, 2.0, 4.0)withvec3(uv.x). All three channels now follow the same cosine, so the picture is grey and every printed pixel has equal r, g and b:255,255,255,190,190,190,67,67,67at the bottom left. (Changing only the offsets tovec3(0.0)is not enough:uv.xyxstill gives green theyvalue, so red and blue match but green differs a little.) - 2
Make it the other way up
Replace
gl_FragCoord.xy / iResolutionwithvec2(gl_FragCoord.x, iResolution.y - gl_FragCoord.y) / iResolution. The gradient flips top to bottom: GLSL's y grows upwards, the page's y grows downwards. The first printed line becomesiTime 0 | bottom-left 255,5,50 | top-right 203,60,156: the green values of the two corners have swapped places.
Code & diagrams
Try it: interactive
Where the shaders run
Step through the stages. Vertex shaders do the first steps (model to clip space), fragment shaders the colour step. Turn and move the triangle and watch the numbers change.
The colours drift because `iTime` grows. The printed pixels are three fixed times drawn off-screen.
Console
Run the scene to see its console.Expected console output
iTime 0 | bottom-left 255,60,50 | top-right 203,5,156
iTime 1 | bottom-left 190,0,171 | top-right 82,33,247
iTime 2 | bottom-left 67,57,252 | top-right 3,148,229Each row is one GLSL expression run on the GPU for one pixel. The last three rows are undefined behaviour: other GPUs may print other numbers.
Console
Run the scene to see its console.Expected console output
swizzle .zyx reverses a vec3 3, 2, 1, 0
maths works on every component 0.5, 1, 11, 12
mod, fract, floor, abs 1.5, 0.5, -1, 3
clamp, smoothstep, mix, step 1, 0.1563, 12.5, 0
3 / 2 is 1 (integers), mod(-1, 3) 1, 1.5, 2, 0
1/0, -1/0, 0/0 Infinity, -Infinity, NaN, 0
normalize of a zero vector NaN, NaN, NaN, 0
sqrt(-1), pow(-2, 2): undefined 0, 0, 0, 0Break it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Division by zero
In the calculator add the row ["1.0 / zero, 1.0, 1.0, 1.0", "test"].
Myth vs fact
Myth
Shaders are only for fancy effects.
Fact
Every pixel you have drawn so far went through a vertex and a fragment shader. three.js wrote them for you. Custom shaders only replace that default code.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
GPUs run fragment shaders in blocks of pixels in lockstep. Code that branches differently inside one block runs both paths, so
ifcosts more than it looks;mixandstepoften avoid it. - ▸
gl_FragCoordis in drawing-buffer pixels. On a phone with pixel ratio 3, a 390 CSS-pixel canvas is 1170 pixels wide: divide by a resolution uniform that comes fromrenderer.domElement.width, never frominnerWidth.
Remember this
- 1
The problem this solves:
MeshStandardMaterialgives you the looks its authors thought of. A metal-flake paint, a wave, a hologram or a dissolve is not on that list. A shader lets you decide the maths of every vertex and every pixel yourself. - 2
Two programs. A vertex shader runs once per vertex (a corner of a triangle, Topic 1.2) and must set
gl_Position, where the corner lands on screen. A fragment shader runs once per pixel the triangle covers and sets the colour (gl_FragColorin three.js). The GPU runs thousands of copies side by side. Each copy knows only its own inputs and cannot ask its neighbour a question. That limit is what makes it fast. - 3
GLSL (OpenGL Shading Language) looks like C. WebGL 2 uses version GLSL ES 3.00. There are no implicit conversions:
float x = 1;is an error, you write1.0. The types arefloat,int,bool, vectorsvec2,vec3,vec4and matricesmat3,mat4. A vector can be read in any order and any length with swizzles:v.zyx,v.xy,v.rgb(rgbaandxyzwmean the same thing). - 4
Maths acts on every component.
vec2(1.0, 2.0) * 2.0isvec2(2.0, 4.0). The functions you will use all course aremix(a, b, t)(blend),clamp,step(edge, x)(0 below the edge, 1 above),smoothstep(a, b, x)(a soft step),fract(x)(the part after the point) andmod. - 5
The sandbox. The first example is a Shadertoy-style page: one quad that covers the screen, a vertex shader that does almost nothing, and a fragment shader that reads two uniforms,
iTimeandiResolution(names borrowed from Shadertoy so its code pastes in with few changes).gl_FragCoord.xy / iResolutiongives each pixel a position from 0 to 1. The numbers it prints come from a small off-screen picture at a fixed time, so they never depend on the window or the clock.
Explain it without notes
What is the difference between a vertex shader and a fragment shader, and how many times does each run for one triangle?
Why does float x = 1; fail in GLSL?
Practice
Make the sandbox show a vertical white-to-black gradient (white at the top).
Write the GLSL expression for a vec3 whose parts are the reverse of colour.
Done when you can
Say what a vertex and a fragment shader each do
Write
1.0, not1, in GLSLRead a pixel back from a render target
Explain what
gl_FragCoord / resolutiongives you