When two triangles both cover the same pixel, the renderer must show whichever one is actually nearest to the camera -- regardless of the order the triangles happen to be drawn in. A Z-BUFFER (depth buffer) solves this per-pixel: alongside the color buffer, maintain a same-sized buffer holding the closest depth written to each pixel so far. Before writing a fragment's color, compare its depth against what's already stored there; only write through (both depth AND color) if this fragment is nearer. This is what makes rendering triangles in ANY order still produce a correct image.
Implement DepthBuffer.test_and_write(x, y, depth, color): smaller
depth means nearer (the common convention). Write through and return
True only if depth is strictly smaller than self.depth[y][x];
otherwise leave both buffers untouched and return False.
Example
buf = DepthBuffer(width=4, height=4)
buf.test_and_write(1, 1, depth=5.0, color="red") # -> True (buffer was empty)
buf.test_and_write(1, 1, depth=8.0, color="blue") # -> False (farther -- rejected)
buf.color[1][1] # -> "red" (unchanged)
buf.test_and_write(1, 1, depth=2.0, color="green") # -> True (nearer -- wins)
buf.color[1][1] # -> "green"
Constraints
- Every pixel starts with depth
float("inf")(infinitely far) and colorNone, so the very first write to any pixel always wins. - Equal depth does NOT win -- the comparison is strict (
<, not<=). - Buffers are indexed
[y][x](row-major, matching how a real framebuffer is usually laid out) and pixels are independent -- writing one never affects another.
Real-world grounding
ssloy/tinyrenderer's rasterizer keeps exactly this kind of per-pixel depth buffer and gates every fragment write on it:our_gl.cpp-- seeinit_zbufferand the depth compare insiderasterize. Note the real code's polarity is flipped from this exercise's (it keeps the LARGEST device-space z as nearest, seeded at-1000., because of how its specific perspective transform maps depth) -- the mechanism this exercise implements is identical (per-pixel sentinel, compare-then- conditionally-write), just with the more common "smaller depth = nearer" polarity used by most graphics APIs' default depth convention.
Real z-buffers also handle precision (fixed-point depth in a limited bit width causes "z-fighting" between near-coincident surfaces) and early-z optimizations that skip shading a fragment before it's even depth-tested -- this exercise implements the core per-pixel compare-and-write the rest of that machinery sits on top of.