Basics, part 1

Where is this pixel?

A shader colors one pixel at a time, and its first clue is where that pixel sits. Let's read UV, move the middle to (0, 0) and draw a graph.

  • Part 15 min dose
  • Beginner

Here’s a strange thing about shaders. They never see the whole picture! The graphics card runs the same small program once for every pixel your mesh covers, thousands or millions of times, side by side. Each run picks one color, and the one clue it starts from is where its pixel sits. Everything I draw in a shader grows out of that clue, so that’s where the Basics begin.

UV shown as red and green
Drag the probe, or press Tab to reach it and use the arrow keys

Turn on JavaScript to run this demo.

lib.hlsl and uv.hlsl draw the demo above
Fig. 1

Drag the white dot, the probe. Each pixel’s color comes from its position alone. The pink line is the way in from the left edge, the mint line the way up from the bottom, and the numbers show both.

Two numbers called UV

The position comes as two numbers called UV. uu says how far across the pixel is, vv how far up. Both run from 0 to 1, with (0,0)(0, 0) in the bottom left corner, (1,1)(1, 1) in the top right and (0.5,0.5)(0.5, 0.5) in the middle. They aren’t pixels or centimetres. Halfway across is 0.5 on a tiny picture and on a huge one.

The demo shows them as color, red for uu and green for vv, while blue stays 0. So the bottom left is black, the bottom right red and the top left green. In the top right, full red and full green light make yellow.

UV comes from the mesh, not the screen. Each demo here is drawn like Unity’s quad, a flat square made of two triangles, whose UVs are laid out exactly like this.

Moving the middle

Zero in a corner suits textures, but shapes like to grow from the middle. So nearly every demo on this site starts with centered(uv). On a wide picture like this one, it does two things:

x=(2u−1) ay=2v−1\begin{aligned} x &= (2u - 1)\,a \\ y &= 2v - 1 \end{aligned}

First, 2u−12u - 1 stretches 0 to 1 into -1 to 1, so the middle lands on 0. Then xx is multiplied by the aspect aa, the picture’s width divided by its height. This picture is 16 wide and 9 tall, which makes aa about 1.78. Now yy runs from -1 to 1 and xx from -1.78 to 1.78. On a tall picture, yy gets the extra room instead.

Why bother with the aspect? Without it, -1 to 1 fits the short side and the long side alike, so one step across is longer on screen than one step up, and every circle comes out as an oval.

A circle and its stretched twin

Turn on JavaScript to run this demo.

How big both circles are, each in its own units.0.75
grid.hlsl draws the demo above
Fig. 2

Slide Radius and both shapes follow, because they share it. The mint circle uses centered(uv) and stays round. The pink one uses 2u−12u - 1 and 2v−12v - 1 and skips the aspect, so it stretches with the picture. The two brighter lines are the axes, crossing at (0,0)(0, 0). Grid squares are 0.25 units.

I’ll often call the pair p=(x,y)p = (x, y), the pixel’s position. Vectors shows how one letter can hold two numbers.

Reading a graph

Shader math is full of small rules that turn one number into another: halve it, square it, keep it between 0 and 1. A graph is a picture of such a rule. Call the rule ff, and y=f(x)y = f(x) means you put in xx and get out yy. To read it, find xx on the horizontal axis, go straight up or down to the curve, and see how high you are.

Three curves to read

Turn on JavaScript to run this demo.

Where to read the three curves.1.20
Code for the plot above
  • f1(x):0.5 * x
  • f2(x):x * x
  • f3(x):saturate(x)
Fig. 3

Move Pick x and watch the three dots ride along, each at the height f(x)f(x) of its curve. Mint halves xx, pink multiplies xx by itself, and butter is saturate, which keeps any number between 0 and 1.

The next lesson, Sine and cosine, reads two famous wave graphs this way.

Drawing a curve, pixel by pixel

That graph is a shader too, and a shader can’t move a pen along a line. Each pixel only knows its own (x,y)(x, y), so it asks a different question. How far am I above the curve?

d=y−f(x)d = y - f(x)

dd is positive above the curve, negative below and 0 right on it. The bars in ∣d∣|d| drop the minus sign and leave the gap either way. Where ∣d∣|d| is under about a pixel, the pixel belongs to the line. A hard yes or no would give jagged stairs, so the color fades out over a pixel instead. That soft edge is anti-aliasing. Here is y=x2y = x^2 in a Unity shader, on a square quad:

float2 p = i.uv * 2.0 - 1.0;      // -1 to 1, the middle is (0, 0)
float d = p.y - p.x * p.x;        // above the curve y = x * x it is positive
float px = max(fwidth(d), 1e-5);  // how much d changes per pixel, never 0
float ink = saturate(1.5 - abs(d) / px);
col = lerp(col, float3(1.0, 1.0, 1.0), ink);

fwidth(d) is how much dd changes from one pixel to the next, so dividing by it turns the gap into pixels. On the steep parts dd changes faster, so the line reaches further up and down and doesn’t get thin there. The smooth edges lesson takes the soft edge apart, and Drawing shapes with distance grows this idea into whole shapes.

In practice

  • UV in Unity. The mesh’s UV reaches each pixel, usually as i.uv. You’ll find where it comes from in the Unity shader file lesson.
  • UVs on a model. On a character or a piece of clothing, the UVs are a flat map of the surface, cut into pieces like a sewing pattern. A pattern drawn from UV follows the fabric and jumps at the seams, where the pieces meet.
  • The aspect in Unity. Copy for Unity, the Unity button on a demo’s code window, reads the aspect from the quad’s scale, so a quad scaled to 2 by 1 still draws round circles. In your own shader, multiply xx by the width over the height.

Takeaways

  • A shader colors one pixel at a time, and its first clue is where that pixel sits.
  • UV runs from 0 to 1 with (0,0)(0, 0) at the bottom left. centered(uv) moves (0,0)(0, 0) to the middle and keeps circles round.
  • To draw y=f(x)y = f(x), each pixel measures its gap to the curve and takes the line’s color where that gap is under a pixel.
Search the lessons

Type any word from the lessons, like normalize or smoothstep. The start of a word is enough.