Master the fundamental concepts of software rasterizer through this focused micro-challenge.
You have read the whole brief, and the concepts above stay free on every task. Writing and running the code needs a plan.
Three hints are available for this task, revealed one at a time inside the code workspace so you can struggle productively before seeing them.
Every task includes starter code, theory, and hidden tests so you can implement and verify locally in the browser.
How it worksAfter projection and perspective division, triangle vertices live in normalized device coordinates. Attributes like color, texture coordinates, and normals are not linear in screen space under perspective projection. Linear interpolation stretches values near the camera and compresses them far away.
For example, a textured wall viewed at a steep angle shows visible warping if you interpolate UVs linearly across the triangle. PlayStation 1 titles skipped perspective correction to save cycles, and the swimming textures were a signature artifact.
The fix: interpolate A/w and 1/w in screen space, then divide:
cLoading…
Here u, v, w are screen-space barycentric coordinates and w0, w1, w2 are homogeneous w values from the projection matrix. Any attribute divided by w becomes linear in screen space; the final division corrects perspective distortion.
You will compare linear and perspective-correct color interpolation at a test point with known barycentric coordinates and w values. This task asks you to print both results side by side so the difference is visible. Anyone writing a WebGL fallback or software renderer must implement this 1/w division themselves.
Texture a quad drawn in perspective, such as a floor receding into the distance, with a checkerboard, twice:
Print the two images side by side and count the pixels where they disagree. Everything is computed with exact integer and rational arithmetic, so the images are reproducible bit for bit.
# starts a comment line.
cLoading…
The quad is drawn as triangles (0, 1, 2) and (0, 2, 3).
E(a→b) = (bx-ax)(Py-ay) - (by-ay)(Px-ax). A triangle with negative area has vertices 1 and 2 swapped, and zero area is skipped. Zero edge values count only on top (ay == by && bx > ax) or left (by < ay) edges. A pixel belongs to the first triangle that covers it.l0 = E(b→c), l1 = E(c→a), l2 = E(a→b) at the pixel centre.u = Σ lᵢuᵢ / Σ lᵢ, and the same for v.kᵢ = lᵢ × (L / wᵢ), u = Σ kᵢuᵢ / Σ kᵢ, and the same for v. This is exactly Σ(lᵢuᵢ/wᵢ) / Σ(lᵢ/wᵢ).cu = floor(u × N / 1000) and cv = floor(v × N / 1000), computed exactly from the fractions. The texel is # if cu + cv is odd, else ..cLoading…
affine padded to the canvas width, then | perspective-correct.|, then the perspective row. Trailing spaces don't matter.D of C covered pixel(s) differ.canvas: W (1-30) H (1-30), checks: N (1-64), quad: 4 x (X Y W U V), W 1-20, U V 0-1000, render: define a quad first, unknown command X.Input:
cLoading…
Output:
cLoading…
Hidden tests cover equal w (no difference), strong foreshortening with fine checks, a degenerate quad that covers nothing, and argument errors.