Skip to content

Barycentric Coordinates & Color Interpolation

From inside/outside test to per-pixel interpolation.


The edge function revisited

In the previous section, the edge function was used to determine whether a point was inside a triangle. But the three values it produces — e1, e2, e3 — contain much more information than just that.

What are barycentric coordinates

Barycentric coordinates are three weights (λ₁, λ₂, λ₃) that describe the position of any point inside a triangle in terms of its vertices. Their fundamental property:

\[ \lambda_1 + \lambda_2 + \lambda_3 = 1 \]

Each weight is between 0 and 1 — λ₁ = 1 means you're exactly at vertex A, λ₁ = 0 means you're on the opposite side. Geometrically, each λ is the area of the sub-triangle opposite to the corresponding vertex, divided by the total area:

\[ \lambda_1 = \frac{\text{area}(PBC)}{\text{area}(ABC)} \qquad \lambda_2 = \frac{\text{area}(PCA)}{\text{area}(ABC)} \qquad \lambda_3 = \frac{\text{area}(PAB)}{\text{area}(ABC)} \]

And here's the connection to the edge function: each e1, e2, e3 already computed is proportional to those areas. Dividing by the total area gives exactly the barycentric coordinates:

float const area_total = e1 + e2 + e3;

// Normalized barycentric coordinates
float const e1_norm = e1 / area_total; // λ for vertex C
float const e2_norm = e2 / area_total; // λ for vertex A
float const e3_norm = e3 / area_total; // λ for vertex B

Interpolating color

With barycentric coordinates, interpolating any value across the triangle is a weighted sum:

\[ \text{value} = \lambda_1 \cdot \text{value}_A + \lambda_2 \cdot \text{value}_B + \lambda_3 \cdot \text{value}_C \]

For RGB colors, it applies per channel:

// Color interpolation using barycentric coordinates
Vec3 color = colorC * e1_norm + colorA * e2_norm + colorB * e3_norm;
● Interactive change vertex colors to see the blend update in real time

Perspective-correct interpolation

Direct barycentric interpolation works perfectly for colors in 2D. But it breaks for 3D attributes — UV coordinates, normals, depth — because perspective projection distorts distances in screen space. Points that are far away get compressed toward the center, so linear interpolation in screen space produces the wrong result.

The fix involves dividing by w — the original depth of each vertex before the perspective divide — before interpolating. This corrects for the non-linear distortion introduced by perspective. This is covered in full in the UV Texturing section.

Bug — coordinates assigned to the wrong vertex

BUG Colors appear at the wrong vertices
What happened Colors appeared at the wrong vertices — red where blue should be, blue where green should be.
Cause e1 faces vertex C (it's the area of sub-triangle PBA), e2 faces A, and e3 faces B. They had been assigned backwards — e1 to A, e2 to B, e3 to C.
Fix Understand which edge function corresponds to which vertex. e1 = edge(P−A, B−A) measures how much C "weighs" at that point, not A.

Result

And here's the result:

Color interpolation result

Color interpolation working — every pixel is a weighted blend of the three vertex colors.

With barycentric coordinates working, the next step is depth — how to decide which triangle wins when two overlap on screen.