Draw a sunny rainbow small triangle with Javascript
gagaisyou

gagaisyou @gagaisyou

Location:
china
Joined:
Jun 7, 2025

Draw a sunny rainbow small triangle with Javascript

Publish Date: Jul 8
0 0

Hello everyone! Recently, I came across the graphics code I wrote two or three years ago and realized I've forgotten quite a bit. So, I'm writing this article to review it. Here are the previous results:
https://github.com/925558718/CanvasRender

cow.png

More Articles

This time, let's review how to draw a triangle

Prepare the rendering environment: Canvas

const canvas = document.querySelector<HTMLCanvasElement>("#canvas")!;
const ctx = canvas.getContext("2d")!;

const buffer = new Uint8ClampedArray(500 * 500 * 4);

ctx.putImageData(new ImageData(buffer, 500, 500), 0, 0);
Enter fullscreen mode Exit fullscreen mode

We create a buffer, modify it, and then render it to the canvas.

Triangle Data Definition

We can store three vertices in an array, like [p1, p2, p3].

Our current canvas is 500x500, so we define a triangle as [[0,0], [250,500], [500,500]], which makes its bounding box the entire canvas.

The origin of the canvas coordinate system is at the top-left, so our goal is to render a triangle with its tip pointing to the left.

Two Ways to Draw a Triangle

Scanline Algorithm

This method involves scanning vertically from top to bottom, finding the two intersection points of a horizontal line with the triangle, and then iterating through the pixels between these two intersection points to color them.
First, sort the vertices to ensure the higher vertex is first.

  // Sort vertices by y-coordinate
  const sortedVertices = [...points].sort((a, b) => a[1] - b[1]);
  const [p0, p1, p2] = sortedVertices;
Enter fullscreen mode Exit fullscreen mode

For easier manipulation, we operate on the upper and lower regions separately.

Start iterating:

  // Calculate intersection points, which is essentially interpolation. Given the y-value of the midpoint, find the x-component.
  function interpolate(y: number, y0: number, x0: number, y1: number, x1: number): number {
    if (y1 === y0) return x0;
    return x0 + (x1 - x0) * (y - y0) / (y1 - y0);
  }
  function fillUpperTriangle() {
    if (p1[1] === p0[1]) return; // Avoid division by zero
    
    for (let y = p0[1]; y <= p1[1]; y++) {
      // Calculate x-coordinates of the left and right boundaries
      const x1 = interpolate(y, p0[1], p0[0], p1[1], p1[0]); // Edge from p0 to p1
      const x2 = interpolate(y, p0[1], p0[0], p2[1], p2[0]); // Edge from p0 to p2
      
      // Ensure xLeft < xRight
      const xLeft = Math.min(x1, x2);
      const xRight = Math.max(x1, x2);
      
      // Fill the row
      for (let x = Math.ceil(xLeft); x <= Math.floor(xRight); x++) {
        setPixel(x, y, color[0], color[1], color[2], color[3]);
      }
    }
  }
    // Helper function to set pixel color
  function setPixel(x: number, y: number, r: number, g: number, b: number, a: number) {
    if (x < 0 || x >= 500 || y < 0 || y >= 500) return;
    
    const index = (Math.floor(y) * 500 + Math.floor(x)) * 4;
    buffer[index] = r;     // Red
    buffer[index + 1] = g; // Green
    buffer[index + 2] = b; // Blue
    buffer[index + 3] = a; // Alpha
  }
Enter fullscreen mode Exit fullscreen mode

tri.png

The lower part uses a similar operation, just iterating through the lower region.

Iteration + Barycentric Coordinates

The scanline algorithm is fast, but its capabilities are limited. What if I want to render a colored triangle?

To render a colored triangle, we can define the colors of the three vertices, but how do we define the color of points in between? This is where barycentric coordinates come in handy.

What are Barycentric Coordinates?

Simply put, any point P within a triangle can be expressed as P=αP +βP +γP , where α+β+γ=1 and α,β,γ≥0. These three coefficients can then be multiplied by the colors to calculate the interpolated color of that point.

How to Calculate Barycentric Coordinates

Area Method

When point P is connected to the other three vertices, it forms three sub-regions within the triangle. The ratio of each sub-region's area to the total area of the triangle gives its corresponding coefficient. For example, for point P 3, its coefficient is Area(P,P2,P1)/Area(P1,P2,P3), which is precisely the percentage of the total area occupied by the region formed by the other two points and point P.

  function getBarycentricCoordinates(px: number, py: number): [number, number, number] {
    // Calculate barycentric coordinates using the area method
    const denominator = (p1[1] - p2[1]) * (p0[0] - p2[0]) + (p2[0] - p1[0]) * (p0[1] - p2[1]);
    
    if (Math.abs(denominator) < 1e-10) {
      return [0, 0, 0]; // Degenerate triangle
    }
    
    // Barycentric coordinates u, v, w (corresponding to vertices p0, p1, p2)
    const u = ((p1[1] - p2[1]) * (px - p2[0]) + (p2[0] - p1[0]) * (py - p2[1])) / denominator;
    const v = ((p2[1] - p0[1]) * (px - p2[0]) + (p0[0] - p2[0]) * (py - p2[1])) / denominator;
    const w = 1 - u - v;
    
    return [u, v, w];
  }
Enter fullscreen mode Exit fullscreen mode

Then, we start iterating through the entire bounding box:

  // Iterate over all pixels within the bounding box
  for (let y = minY; y <= maxY; y++) {
    for (let x = minX; x <= maxX; x++) {
      // Calculate the barycentric coordinates for the current pixel
      const [u, v, w] = getBarycentricCoordinates(x + 0.5, y + 0.5); // +0.5 for sampling the pixel center
      
      // If the point is inside the triangle, fill it
      if (isInsideTriangle(u, v, w)) {
        // Use barycentric coordinates to interpolate the color
        const [r, g, b, a] = interpolateColor(u, v, w);
        setPixel(x, y, r, g, b, a);
      }
    }
  }
Enter fullscreen mode Exit fullscreen mode

tri.png

However, iterating through every point in the bounding box and then calculating barycentric coordinates is quite inefficient. We can add some optimization to check if a point is inside the triangle before calculating barycentric coordinates, but I won't go into detail here. Let's just look at the results.

Comments 0 total

    Add comment