2. Visibility

Z-Buffer Depth Testing

midgraphics-internalsvisibility

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 color None, 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 -- see init_zbuffer and the depth compare inside rasterize. 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.

hints
depth_buffer.pysign in to run
Loading...
test result

This is a preview. Sign in to run this exercise against the real tests.