W06: Linear algebra becomes graphics and spatial computation
On this page
- Prerequisites and observable goals
- The central question: which space contains the quantity?
- Order matters because the output of one map is the next input
- Derive barycentric coordinates rather than guess weights
- Why screen-linear attributes can be wrong under perspective
- Geometry, spatial indexing, and verification
- Independent practice
- Complete solutions: W06
- Retention and readiness
Prerequisites and observable goals#
A01–A02, spatial vectors, C08, and T02: matrices, geometric representations, and parallel-computing concepts.
Keep coordinate spaces and homogeneous conventions explicit; derive barycentric and perspective-correct interpolation; connect geometric correctness to numerical and performance evidence.
The central question: which space contains the quantity?#
A coordinate tuple is a representation relative to a basis, origin, and units. The same physical point may have model, world, camera, clip, normalized-device, and screen coordinates. Applying a matrix without naming its source and destination spaces can produce a plausible image that is nevertheless mathematically inconsistent. Treat a transform as a typed function from one representation to another.
Use column vectors in this workshop. In two dimensions, represent a point as and a direction as . A homogeneous translation matrix
moves points but not directions. The final component encodes this distinction; it is not decorative padding. A general projective output with becomes Cartesian . If , the same division is unavailable and the representation has a different projective meaning.
Order matters because the output of one map is the next input#
Let scaling be and translation . For point , first scales to and then translates to . In the opposite order, first translates to and then scales to . Matrices generally do not commute. Row-vector conventions reverse how composed matrices are written; copying a formula from another convention without changing the interpretation is a representation error, not a mysterious graphics bug.
The same idea explains normals. If tangent vectors transform by an invertible linear map , a transformed normal must satisfy whenever . Choosing gives . Normalize afterward when a unit normal is required. Transforming normals with works in some special cases, such as rotations, but not arbitrary nonuniform scaling. Singular transforms require separate treatment because the inverse does not exist.
Derive barycentric coordinates rather than guess weights#
For a nondegenerate triangle with vertices , write a point as
Subtract and eliminate :
The determinant is twice the signed triangle area. A zero determinant means a degenerate triangle and no unique two-dimensional barycentric coordinates. A very small determinant relative to coordinate scales indicates sensitivity; a floating-point nonzero check is not a robust geometric predicate for every adversarial mesh.
For and , the equations are , so weights are . Their sum is one and the reconstructed position is exactly . A point inside the closed triangle has nonnegative weights. An outside point may have a negative weight even though the sum and reconstruction still hold. Interpolation and containment are different uses of the same representation.
Why screen-linear attributes can be wrong under perspective#
Perspective division is nonlinear. Suppose the vertices’ pre-division homogeneous components are , and screen-space barycentric weights are . An attribute that is affine in the corresponding pre-projection geometry must be interpolated as
To see the normalization, let pre-projection weights be . Projection gives screen weights proportional to , so . Solving for and imposing yields . Substituting gives the formula. This derivation assumes a consistent projective representation and a valid visible triangle; clipping and sign-changing require additional rules.
Use the weights , , and scalar attributes . The numerator is and denominator , giving . Naive screen-linear interpolation gives , a different result. The issue is not that one is more aesthetically pleasing; they interpolate under different coordinate relationships.
Geometry, spatial indexing, and verification#
A spatial index usually produces candidates, not a geometric theorem for free. A grid bucket overlapping a query can contain points outside the exact region; candidate filtering must be followed by the final predicate. J05 makes this distinction explicit for axis-aligned boxes. In triangle work, a bounding box supplies a broad filter and barycentric coordinates supply a more precise test, with boundary and numerical policies documented.
Parallel execution adds another layer. Independent pixel or particle outputs can often be partitioned among workers, but shared reductions change accumulation order. Floating-point addition is not associative, so bitwise agreement with a sequential order may be unavailable even when both approximate the same real sum. Choose a tolerance or reproducibility contract appropriate to the use, test against an analytically justified or higher-accuracy reference, and include transfer/setup costs when claiming acceleration. A faster image with different geometry or different precision is not automatically a like-for-like speedup.
Independent practice#
F foundation C standard course work T transfer S optional stretch L runnable lab or project. Attempt the complete set before opening its solutions.
With the workshop’s column-vector convention, apply scaling and then translation to point and direction . Reverse the order for the point. Explain the three different results using input/output spaces.
Let a tangent be and an orthogonal normal . Apply . Compare transforming both by with transforming the normal by . Check orthogonality explicitly and explain the invertibility condition.
Find barycentric coordinates of relative to . Reconstruct the point, decide whether it is inside, and then repeat the containment decision for .
Use screen weights , , and attributes to derive both perspective-correct and naive interpolation. Explain when the two formulas agree for every attribute and identify one invalid denominator case excluded by the lab contract.
Implement barycentric and perspective in integration_code/starters/graphics.py. Validate dimensions, finite values, a nonzero triangle determinant, nonnegative normalized interpolation weights, positive , and consistent attribute widths. Test reconstruction, vertices, an outside point, a degenerate triangle, and the exact rational interpolation example. State why the implementation is not a robust production rasterizer.
Optional small hints
For .02, orthogonality is a dot-product condition, not equal lengths. For .03, the weights for are the coordinates divided by 4. For .04, recover and normalize the pre-projection weights.
Complete solutions: W06#
Compare the first step where your work differs. A corrected answer is not yet an independent success.
W06.01Show solution
The homogeneous coordinate determines whether translation acts
Point under becomes ; direction becomes because translation multiplies its zero last component. The point under becomes . The first composition maps model coordinates through scaling into the translated world frame; reversing it scales the translation too. None is contradictory: they are different maps with an explicit multiplication convention. A graphics API’s storage order is a separate issue from whether vectors are mathematically columns or rows.
Back to W06.01W06.02Show solution
Preserve the annihilated tangent directions
The transformed tangent is . Transforming the normal by gives , whose dot product with the tangent is , not zero. The inverse-transpose gives , whose dot product is . It can then be normalized without changing orthogonality. The derivation uses ; if is singular, it may collapse tangent dimensions and a separate geometric normal construction is required. Rotation matrices are a special case where inverse-transpose equals the original matrix.
Back to W06.02W06.03Show solution
Interpolation coordinates also encode a containment test
For , . They sum to one and give . All are nonnegative, so the point is inside. For , . Reconstruction still holds, but the negative weight places the point outside the closed triangle. Testing only that weights sum to one would not distinguish the two cases.
Back to W06.03W06.04Show solution
Normalize after accounting for projective scale
The normalized pre-projection weights are because the unnormalized values are with sum . Their attribute combination is . Naive screen interpolation is 6. For a given nontrivial set of weights, agreement for every possible attribute requires equal on all vertices with nonzero weights; then the common reciprocal cancels. Vertices with zero weights do not matter for that point. A zero makes an individual reciprocal invalid; mixed signs can also produce a zero total denominator, which the positive- contract excludes. Visibility/clipping is not implemented by simply ignoring a bad denominator.
Back to W06.04W06.05Show solution
Reference and numerical boundaries
The barycentric solver computes the determinant and solves for two weights, deriving the third from the sum. The interpolator supports multiple attribute components and divides each weighted numerator by the shared normalization. It rejects invalid shapes before arithmetic rather than allowing silently truncated zip operations.
"""W06: triangle coordinates and perspective interpolation, CPU-only."""
import math
def barycentric(point,a,b,c):
if any(len(p)!=2 or any(not math.isfinite(x) for x in p) for p in (point,a,b,c)):
raise ValueError("finite two-dimensional points required")
ux,uy=b[0]-a[0],b[1]-a[1]
vx,vy=c[0]-a[0],c[1]-a[1]
px,py=point[0]-a[0],point[1]-a[1]
determinant=ux*vy-uy*vx
if not math.isfinite(determinant) or determinant==0:
raise ValueError("degenerate or unrepresentable triangle")
beta=(px*vy-py*vx)/determinant
gamma=(ux*py-uy*px)/determinant
result=(1-beta-gamma,beta,gamma)
if not all(math.isfinite(x) for x in result):raise ValueError("nonfinite weights")
return result
def perspective(weights,w,attributes):
if len(weights)!=3 or len(w)!=3 or len(attributes)!=3 or not attributes[0]:
raise ValueError("three vertices and nonempty attributes required")
width=len(attributes[0])
if width>64 or any(len(row)!=width for row in attributes):raise ValueError("attribute shape")
if any(not math.isfinite(x) for x in [*weights,*w,*[v for row in attributes for v in row]]):
raise ValueError("finite inputs required")
if any(x<0 for x in weights) or not math.isclose(math.fsum(weights),1.,rel_tol=0.,abs_tol=1e-12):
raise ValueError("inside-triangle weights summing to one required")
if any(x<=0 for x in w):raise ValueError("positive clip w required in this bounded lab")
coefficients=[a/b for a,b in zip(weights,w)]
denominator=math.fsum(coefficients)
if not math.isfinite(denominator) or denominator<=0:raise ValueError("invalid denominator")
result=[math.fsum(c*row[k] for c,row in zip(coefficients,attributes))/denominator
for k in range(width)]
if not all(math.isfinite(x) for x in result):raise ValueError("nonfinite attribute")
return result
if __name__=='__main__':
print(barycentric((1,1),(0,0),(4,0),(0,4)))
print(perspective([.5,.25,.25],[1,2,4],[[0],[8],[16]]))Tests compare reconstruction and the analytic value, as well as shape and domain rejection. An exact-zero determinant check does not address near-degenerate triangles, floating-point robustness, clipping, edge tie rules, depth buffering, antialiasing, or parallel rasterization. Those are substantial additional contracts. The lab teaches the geometry that a production implementation must preserve, not the claim that a short script is a complete renderer.
Back to W06.05Retention and readiness#
Label the coordinate space and units on every vector in a graphics derivation. Reconstruct a point from barycentric weights, and explain why perspective division changes interpolation. In a performance report, state which geometric and precision contract stayed fixed.