Skip to content

How It Works

GradientMesh treats a 2D gradient as a geometry and interpolation problem.

Instead of drawing a static bitmap gradient, it builds mesh vertices, assigns colors to those vertices, and lets the renderer interpolate color values across triangles.

Rectangular gradient mesh

For a rectangular gradient, the mesh is built as a regular grid:

text
v1 ----- v2 ----- v3
 | \      | \      |
 |  \     |  \     |
v4 ----- v5 ----- v6
 | \      | \      |
 |  \     |  \     |
v7 ----- v8 ----- v9

Each grid cell is split into two triangles:

text
(v1, v2, v5)
(v1, v5, v4)

If a rectangle has width w, height h, and anchor point (ax,ay), a vertex at normalized grid coordinates (ui,vj) can be written as:

Pij=((uiax)w, (vjay)h)

For each cell, the two triangles can be represented as:

T1=(Pij,Pi+1,j,Pi+1,j+1)T2=(Pij,Pi+1,j+1,Pi,j+1)

Color interpolation

Inside a triangle, the renderer interpolates vertex colors.

Conceptually, a simple two-color gradient can be written as:

C(t)=(1t)C0+tC1

where:

0t1

For a triangle, this idea generalizes through barycentric interpolation.

If a point inside a triangle has barycentric weights λ1, λ2, and λ3, then:

λ1+λ2+λ3=1

and the interpolated color is:

C(P)=λ1C1+λ2C2+λ3C3

That is the small trick behind the visual result: the Lua code builds the geometry, while the rendering pipeline does the smooth color blending.

Radial and polygon gradients

For circular and polygon-based gradients, the library creates rings of vertices around a center point.

A radial vertex can be described as:

Pij=(cx+sxpjxrcos(θi),cy+sypjyrsin(θi))

where:

  • (cx,cy) is the center point;
  • sx and sy are scale factors from scalePolygon;
  • r is the base radius;
  • pjx and pjy are normalized radial percentages for the current color stop;
  • θi is the angular position of the current polygon vertex.

For a regular polygon with n edges, the angle of each vertex is:

θi=π2+(2i1)πn+ρ

where ρ is the mesh rotation angle from rotationMesh, expressed in radians.

This is why the same function can produce circles, ellipses, regular polygons, rotated polygons, and radial texture masks: changing n, r, sx, sy, and ρ changes the generated mesh.

Inner holes

When hole = true, the radial mesh starts from an inner radius instead of the center.

If rin is the inner radius and r is the outer radius, the normalized inner position is:

pin=rinr

For multiple radial color stops, the intermediate percentages can be distributed between the inner and outer radius:

pj=rinr+jrrinmr,j=1,2,,m

where m is the number of color stops.

This creates donut-like gradients and ring-shaped meshes while keeping the same polygon construction logic.

Antialiasing ring

When jaggedFree = true, the mesh adds a thin outer fade ring.

Conceptually, it creates one ring close to the edge:

pfade=rδr

and another one at the final boundary:

pouter=1

Then the outer alpha fades toward zero:

αouter=0

This soft transparent ring helps reduce jagged polygon edges.

Texture coordinates

When a texture is attached, the mesh also generates texture coordinates.

For a rectangular mesh, texture coordinates follow the same normalized grid idea:

Uij=dxui+ax(twdx)Vij=dyvj+ay(thdy)

where:

  • tw and th are the texture width and height;
  • dx and dy are the visible texture dimensions after scaling;
  • ax and ay are the texture anchor values.

For polygon meshes, the texture coordinates follow the same radial idea as the geometric vertices, which is what allows image textures to be clipped, rotated, and tinted by polygon geometry.

Built with VitePress and deployed with GitHub Pages.