A rasterizer's core question, asked once per pixel per triangle: "is this
pixel inside the triangle?" Software rasterizers answer it with
BARYCENTRIC COORDINATES -- express a point p as a weighted combination
of the triangle's 3 vertices, p = w0*v0 + w1*v1 + w2*v2 (with w0 + w1 + w2 == 1). p is inside the triangle exactly when every weight is >= 0
-- a negative weight means p has escaped past the edge opposite that
vertex. These same weights are also exactly what real rasterizers reuse
afterward to interpolate per-vertex color/depth/UV smoothly across the
triangle's interior (a later exercise's DepthBuffer assumes a depth
value arrived at a pixel via interpolation like this).
A real rasterizer never tests every pixel on screen -- only the triangle's screen-space bounding box, clipped to the framebuffer.
Implement:
barycentric_coordinates(px, py, v0, v1, v2)-- return(w0, w1, w2)such that(px, py) == w0*v0 + w1*v1 + w2*v2, orNoneif the triangle is degenerate (its 3 vertices are collinear -- zero area, no interior).point_in_triangle(px, py, v0, v1, v2)--Trueiff every weight is>= 0(inside, or exactly on an edge/vertex). A degenerate triangle contains no points.rasterize_triangle(v0, v1, v2, width, height)-- every(x, y)integer pixel whose center (sampled at the integer coordinate itself) is covered, restricted to the triangle's bounding box AND clipped to[0, width) x [0, height).
Example
barycentric_coordinates(0, 0, (0, 0), (4, 0), (0, 4)) # -> (1.0, 0.0, 0.0) -- exactly vertex v0
point_in_triangle(1, 1, (0, 0), (4, 0), (0, 4)) # -> True
point_in_triangle(3, 3, (0, 0), (4, 0), (0, 4)) # -> False -- outside the hypotenuse
rasterize_triangle((0, 0), (4, 0), (0, 4), width=10, height=10)
# -> every (x, y) with x, y >= 0 and x + y <= 4
Constraints
- The formula must work regardless of the triangle's winding order (CW or CCW) -- both must classify the same points as inside.
- Points exactly on an edge or vertex count as inside (
>= 0, not> 0). rasterize_trianglemust clip to the framebuffer bounds -- never return a pixel outside[0, width) x [0, height).
Real-world grounding
ssloy/tinyrenderer's software rendering pipeline computes exactly this: barycentric coordinates over a triangle's screen-space bounding box, used both for the inside test and to interpolate depth (see the next exercise) and shading:our_gl.cpp(see therasterizefunction).
Real rasterizers additionally handle sub-pixel precision, a documented
top-left fill rule (so a pixel shared by two adjacent triangles is drawn
exactly once, never twice or zero times), and perspective-correct
interpolation via w -- this exercise implements the barycentric
inside-test and bounding-box scan those refinements build on top of.