What uv even means
gl_FragCoord.xy hands you the current pixel's position in actual pixels — something like (1417.0, 803.0) on a 1920×1080 canvas. That number is useless the moment your canvas resizes, so the very first thing almost every shader does is divide it down to a 0–1 range: vec2 uv = gl_FragCoord.xy / u_resolution;. Now (0, 0) is always one corner and (1, 1) is always the opposite one, no matter how big the canvas is.
Which corner is which trips people up once: WebGL's pixel coordinates start at the bottom-left, not the top-left like most UI code assumes. So uv.y near 0 is the bottom of the screen, and near 1 is the top. If an effect ever looks vertically flipped compared to a reference image, this is usually why — you'll see it for real in Module 07 when a noise texture comes in upside-down.
The name "uv" isn't ours — it's borrowed from texture mapping, where u and v are just the letters graphics people picked instead of reusing x, y (which were already taken by 3D space). You'll keep calling it uv even in lessons like this one where there's no texture in sight yet.
The aspect-ratio trap
Here's the part that isn't obvious until it bites you: uv.x and uv.y both range from 0 to 1, but they don't cover the same number of real pixels. On a 1920×1080 canvas, uv.x sweeping 0→1 covers 1920 pixels, while uv.y sweeping 0→1 covers only 1080. One "uv unit" is not one uv unit — it's a different physical distance on each axis.
You won't notice with a gradient. You will absolutely notice the moment you draw a circle: define it directly in raw uv space and you get an ellipse, stretched along whichever axis is wider. The fix is to re-center uv around the middle of the screen and rescale one axis by the aspect ratio before you do any shape math:
vec2 p = uv - 0.5; p.x *= u_resolution.x / u_resolution.y;
Now a distance of 0.3 means the same thing horizontally and vertically, and your circle is actually a circle.
A circle, with nothing borrowed
You could reach for GLSL's built-in length() here and be done in one line. We're deliberately not doing that yet — the point of this lab is to see that "distance from the center" is just the Pythagorean theorem you already know: d² = x² + y². No square root even needed if you're only comparing against a fixed radius — compare the squared distance to the squared radius and skip the sqrt() entirely. It's a small trick, but it's the same reasoning that matters later when a shader has to run fast: don't compute what you don't need.
To turn "inside or outside" into a color, step(edge, x) is the tool: it returns 0.0 below the edge and 1.0 at or above it — a hard, aliased boundary. That's fine for now. Smoothing that edge properly is its own lesson, coming in Module 04.