Your First Shader

Writing code that paints every pixel on screen

Now we write code. We will start with the simplest possible shader: painting every pixel the same color. Then we will add a single ingredient, the pixel's position, and watch solid color transform into gradients. By the end of this chapter, you will understand the fundamental anatomy of every fragment shader.

The Anatomy of a Fragment Shader

Every fragment shader contains a function called main. This function runs once for each pixel being rendered. Its job is to set a special output variable called gl_FragColor, which holds the final color of that pixel.

void main() {
  gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
glsl

This shader paints every pixel red. Let us break down what is happening.

gl_FragColor expects a vec4: four numbers representing red, green, blue, and alpha (transparency). Each component ranges from 0.0 to 1.0, where 0.0 means none of that color and 1.0 means full intensity.

So vec4(1.0, 0.0, 0.0, 1.0) means:

  • Red: 1.0 (full)
  • Green: 0.0 (none)
  • Blue: 0.0 (none)
  • Alpha: 1.0 (fully opaque)

A Solid Red Shader

gl_FragColor = vec4(1.00, 0.00, 0.00, 1.0)

That is it. Every pixel asks "what color should I be?" and the shader answers "red." Every single pixel gets the same answer because we have not given them any way to differentiate themselves.

Enter gl_FragCoord

To create anything more interesting than a solid color, each pixel needs to know where it is. The built-in variable gl_FragCoord provides exactly this. It contains the pixel's position in screen coordinates, where (0, 0) is the bottom-left corner.

gl_FragCoord is a vec4, but we usually only care about .x and .y:

  • gl_FragCoord.x is the pixel's horizontal position
  • gl_FragCoord.y is the pixel's vertical position

If we use these raw coordinates as colors, we will get numbers like 0, 1, 2... up to the screen width or height. Since colors need to be between 0 and 1, we need to normalize these coordinates.

Normalizing Coordinates

To convert pixel coordinates to a 0-1 range, we divide by the resolution. This is such a common operation that shader environments typically provide the screen dimensions as a uniform variable. In our setup, this is u_resolution.

void main() {
  vec2 uv = gl_FragCoord.xy / u_resolution;
  gl_FragColor = vec4(uv.x, 0.0, 0.0, 1.0);
}
glsl

Now uv.x goes from 0.0 at the left edge to 1.0 at the right edge. We use this value as the red channel, creating a horizontal gradient.

Horizontal Gradient: Red increases from left to right

Left (x=0)
Right (x=1)

Toggle between horizontal and vertical to see how uv.x and uv.y create different gradients

The left pixels have uv.x close to 0, so they appear black. The right pixels have uv.x close to 1, so they appear bright red. Every pixel in between gets a proportional shade.

This is the core insight of shader programming: position becomes color. The pixel's coordinates determine its color through whatever mathematical relationship we define.

Two-Axis Gradients

Let us use both x and y. By mapping x to the red channel and y to the green channel, we create a gradient that varies in two dimensions:

void main() {
  vec2 uv = gl_FragCoord.xy / u_resolution;
  gl_FragColor = vec4(uv.x, uv.y, 0.0, 1.0);
}
glsl

Two-Axis Gradient: Red and green vary with position

(0, 0) Black
(1, 0) Red
(0, 1) Green
(1, 1) Yellow
x=0, y=0
x=1, y=0
x=0, y=1
x=1, y=1

Study this image. The bottom-left corner is black (x=0, y=0). The bottom-right is red (x=1, y=0). The top-left is green (x=0, y=1). The top-right is yellow (x=1, y=1), because mixing full red and full green produces yellow.

This pattern, mapping UV coordinates to the red and green channels, is so fundamental that it has become a debugging tool. When something goes wrong in shader code, experienced developers often output the UV coordinates to visually verify that the coordinate system is correct.

Your Turn

Now it is your turn. The editor below contains a shader you can modify. Try these experiments:

  1. Paint the screen solid blue
  2. Create a vertical gradient (dark at bottom, bright at top)
  3. Make the blue channel vary with position while keeping red and green at 0.5
  4. Create a gradient that goes from cyan (0, 1, 1) to magenta (1, 0, 1)

Interactive: Your First Shader

Output

Available Uniforms

vec2 u_resolutionfloat u_timevec2 u_mouse

Do not worry about making mistakes. The worst that can happen is you see an error message or a strange color. Play with the numbers, change expressions, and observe what happens.

The Pattern

Every shader we write will follow this same structure:

  1. Calculate normalized UV coordinates
  2. Transform or use those coordinates somehow
  3. Convert the result to a color
  4. Assign to gl_FragColor

What changes is step 2. The transformations get more sophisticated, the math gets more interesting, but the fundamental flow remains: position in, color out.

Key Takeaways

  • main() runs once per pixel and must set gl_FragColor
  • gl_FragColor is a vec4 with RGBA values from 0.0 to 1.0
  • gl_FragCoord.xy gives the pixel's screen position in pixels
  • Dividing by u_resolution normalizes coordinates to the 0-1 range
  • The relationship between position and color is the heart of shader programming