# Rasterization from Scratch: How Triangles Become Pixels

## Why rasterization exists

At some point, all rendering boils down to a simple question:

> Given a triangle, which pixels on the screen belong to it?

GPUs answer this billions of times per second. But the underlying idea is simple—and independent of any graphics API.

In this post, we’ll build a **minimal rasterizer from scratch**, and then map the same logic to a **Vulkan-style compute model**.

## The setup

Assume we already transformed a triangle into **screen space**:

```cpp
v0 = (x0, y0);
v1 = (x1, y1);
v2 = (x2, y2);
```

Our job:

*   Find all pixels covered by this triangle
    
*   Assign them values (color, depth, etc.)
    

* * *

## Step 1: Bounding box

Instead of testing every pixel, restrict work to the triangle’s bounding box:

```cpp
minX = floor(min(v0.x, v1.x, v2.x));
maxX = ceil(max(v0.x, v1.x, v2.x));

minY = floor(min(v0.y, v1.y, v2.y));
maxY = ceil(max(v0.y, v1.y, v2.y));
```

* * *

## Step 2: The edge function

```cpp
float edge(vec2 a, vec2 b, vec2 p) {
    return (p.x - a.x)*(b.y - a.y) - (p.y - a.y)*(b.x - a.x);
}
```

### What this does:

*   Computes a **signed area**
    
*   Tells which side of the edge the point lies on
    

If a point is on the same side of all three edges → it’s inside the triangle.

* * *

## Step 3: Rasterization loop (CPU)

```cpp
for (int y = minY; y <= maxY; y++) {
    for (int x = minX; x <= maxX; x++) {
        vec2 p = vec2(x + 0.5f, y + 0.5f);

        float w0 = edge(v1, v2, p);
        float w1 = edge(v2, v0, p);
        float w2 = edge(v0, v1, p);

        if (w0 >= 0 && w1 >= 0 && w2 >= 0) {
            framebuffer[x][y] = color;
        }
    }
}
```

* * *

## Step 4: Barycentric coordinates [\*](https://en.wikipedia.org/wiki/Barycentric_coordinate_system)

Normalize the weights:

```cpp
float area = edge(v0, v1, v2);

float alpha = w0 / area;
float beta  = w1 / area;
float gamma = w2 / area;
```

Now interpolate:

```cpp
color = alpha * c0 + beta * c1 + gamma * c2;
```

* * *

## From CPU to GPU thinking

Rasterization is:

> Evaluating the same function independently for many pixels.

* * *

## Vulkan-style compute model

```cpp
int x = global_id.x;
int y = global_id.y;

vec2 p = vec2(x + 0.5f, y + 0.5f);

float w0 = edge(v1, v2, p);
float w1 = edge(v2, v0, p);
float w2 = edge(v0, v1, p);

if (w0 >= 0 && w1 >= 0 && w2 >= 0) {
    vec3 color = interpolate(w0, w1, w2);
    imageStore(outputImage, ivec2(x, y), vec4(color, 1.0));
}
```

* * *

## Key insight

CPU and GPU differ only in **execution model**, not math.

* * *

## Final takeaway

Rasterization isn’t magic.

> It’s evaluating edge equations per pixel and interpolating values.
