Concepts3 min read

Fragments and Pixels

What fragments are, how rasterization works, and how the fragment shader decides what color each pixel becomes.

Reading Time
3 min
Word Count
435
Sections
7
Try It Live

Turn the guide into code

Take the key idea from this page into the playground and validate it in a real shader instead of leaving it as theory.

Open Playground

When people say "pixel shader" they really mean "fragment shader." A fragment is a potential pixel — the GPU generates one for every pixel that a triangle covers. Your fragment shader runs once per fragment and decides its final color.

From Triangles to Fragments

The GPU draws everything as triangles. After the vertex shader positions each triangle's corners, the rasterizer figures out which pixels on screen fall inside each triangle. Each of those pixels becomes a fragment.

A single pixel can have multiple fragments — when triangles overlap, each one generates its own fragment at that pixel location. Depth testing decides which one wins.

What the Fragment Shader Receives

Every value you write to output.* in the vertex shader arrives in the fragment shader through input.*, automatically interpolated across the triangle surface.

If vertex A has output.color = float4(1,0,0,1) (red) and vertex B has output.color = float4(0,0,1,1) (blue), a fragment halfway between them receives input.color = float4(0.5, 0, 0.5, 1) (purple).

bwsl
vertex {
output.position = float4(attributes.position, 1.0);
output.color = attributes.color; // per-vertex color
}
fragment {
// input.color is smoothly blended across the triangle
output.color = input.color;
}

You don't write any interpolation code. The GPU does it in hardware between the vertex and fragment stages.

Fragment Position

input.position in the fragment shader gives you the fragment's screen-space position:

  • .xy — pixel coordinates (e.g., 640.5, 360.5 — the .5 means pixel center)
  • .z — depth value (0 to 1, used for depth testing)
  • .w — the reciprocal of clip-space W (used internally for perspective-correct interpolation)
bwsl
fragment {
// Normalize to 0..1 range for a screen-space gradient
float2 uv = input.position.xy / float2(1920.0, 1080.0);
output.color = float4(uv, 0.5, 1.0);
}

Depth Testing

When two triangles overlap, the GPU compares their fragment depths (input.position.z). By default, the closer fragment wins and the farther one is discarded. This is depth testing — it's why 3D objects occlude each other correctly without you sorting anything.

You can optionally write a custom depth value:

bwsl
fragment {
output.depth = customDepthValue;
output.color = float4(1.0, 1.0, 1.0, 1.0);
}

Discarding Fragments

Sometimes you want a fragment to produce no output at all — for cutout effects, alpha testing, or masking. Use discard:

bwsl
fragment {
float4 texColor = sample(resources.albedoMap, input.uv);
if texColor.a < 0.5 {
discard; // fragment produces nothing
}
output.color = texColor;
}

discard can hurt performance on some GPUs because it breaks early depth testing optimizations. Use it for effects that need it (foliage, fences), but prefer alpha blending when possible.

Output

The fragment shader must write output.color — a float4 with RGBA channels. This is the final color that gets written to the render target (screen or texture).

ChannelMeaningRange
.rRed0.0 to 1.0
.gGreen0.0 to 1.0
.bBlue0.0 to 1.0
.aAlpha (opacity)0.0 to 1.0

Values outside 0–1 are clamped by the hardware when writing to a standard render target. HDR render targets can store values beyond 1.0.

See Also