Skip to content
pixi-silk
GitHub repository
Architecture

Anti-aliasing compared

MSAA, resolution, curve smoothness, supersampling, FXAA, @pixi/graphics-smooth and analytic SDF anti-aliasing in PixiJS v8, compared, with fixes by symptom.

Updated pixi-silk 1.0.1View as Markdown

Anti-aliasing gives each edge pixel a partial colour, in proportion to how much of it the shape covers. PixiJS v8 offers five ways to get there. pixi-silk adds a sixth: it computes coverage from each shape’s exact signed distance, per pixel. That is what the rest of the docs mean when they say an edge stays sharp at any zoom, rotation and device pixel ratio.

The options at a glance#

TechniqueHow to turn it on in PixiJS v8Edge qualityCostApplies to
MSAAapp.init({ antialias: true })4 samples, so 5 coverage levels per edge pixelmemory bandwidth, a resolve passthe main canvas. Render textures and filters opt in
Higher resolutionresolution: devicePixelRatio, autoDensity: truesmaller stairs, still aliasedfill rate grows with DPR squaredeverything
Curve smoothnessapp.init({ bezierSmoothness: 0.9 })fewer facets on curves, edges still aliasedmore trianglesGraphics curves
Supersamplingdraw into a 2x render texture, then draw it scaled downhigh4x the pixels and an extra texturewhat you draw into it
FXAAa post-process filtersofter edges, blurred detail and textone full-screen passthe filtered object
Analytic SDF (pixi-silk)new SilkGraphics()exact coverage, continuousone instanced draw call per objecteverywhere, filters and render textures included

MSAA in PixiJS v8#

import { Application } from 'pixi.js';

const app = new Application();

await app.init({
    antialias: true, // MSAA on the main canvas
    resolution: window.devicePixelRatio,
    autoDensity: true,
});

MSAA belongs to a render target, so it does not follow your objects. Render textures are not multisampled unless you create them with antialias: true. Filters render into their own targets without MSAA and at resolution 1, unless you change both.

MSAA works for large shapes at rest. UI and charts show where it stops:

  • Slowly moving or rotating edges jump between the five coverage levels, which reads as shimmer.
  • Lines thinner than a pixel fall between the four samples and look dotted.
  • Zooming in shows the facets of curves, because Graphics fixed its triangles when you drew them.

Analytic anti-aliasing#

A signed distance field gives the distance from any point to the shape’s edge. pixi-silk evaluates each primitive’s exact distance per pixel and turns it into coverage over one device pixel:

float px = sqrt(abs(dFdx(p).x * dFdy(p).y - dFdx(p).y * dFdy(p).x)); // one device pixel, in shape units
float coverage = clamp(0.5 - d / px, 0.0, 1.0); // d: signed distance to the edge

The pixel size comes from screen-space derivatives. So the result holds at any scale, rotation, resolution and render target, with nothing to tessellate and no samples to miss. Each SilkGraphics draws all its primitives in one instanced call. How it works follows the shader step by step.

import { createSilkApp, SilkGraphics } from 'pixi-silk';

const { app } = await createSilkApp({ parent: document.body });
const g = new SilkGraphics();

g.circle(200, 150, 80).stroke({ width: 2, color: 0x64d2ff });
g.roundRect(100, 60, 200, 180, 40, 0.6).stroke({ width: 0.5, color: 0xffffff });
app.stage.addChild(g);

The circle is exact at any zoom. The half-pixel stroke is drawn one pixel wide at half the alpha, so it never breaks up. createSilkApp keeps MSAA off, because it would only cost bandwidth.

Analytic anti-aliasing has limits too. It applies to what SilkGraphics draws, not to sprites or Pixi Graphics. GPU cost follows covered area, so a large blurred shape costs more than many small ones.

Fix a symptom#

Most of these fixes work with plain Graphics. The last column is the pixi-silk route.

You seeCauseFix in PixiJSWith pixi-silk
Jagged, stair-stepped edgesantialias is false by defaultapp.init({ antialias: true })SilkGraphics computes coverage itself
Circles look like polygons when zoomedGraphics cuts curves into segments when you drawraise bezierSmoothness, or redraw at the final scalecurves are evaluated per pixel
Thin lines flicker, look dotted or vanisha sub-pixel line covers a fraction of each pixel, and samples miss itkeep strokes at least one device pixel widehairlines fade instead of thinning. See strokes and dashes
Everything is a little blurry on retina screensthe canvas has fewer pixels than the screen, or was rounded at a fractional DPRresolution: devicePixelRatio, autoDensity: truecreateSilkApp maps canvas pixels 1:1. See resolution
Content behind a filter is blurryfilters render at resolution 1Filter.defaultOptions.resolution = 'inherit'createSilkApp sets it for you
Gradients show bands8-bit colour, with no dither in FillGradientnone built inhalf-float ramps, OKLab and dither. See gradients
Slow motion snaps from pixel to pixelroundPixels: truekeep roundPixels: falsethe default in createSilkApp

@pixi/graphics-smooth on PixiJS v8#

@pixi/graphics-smooth is the community plugin that made v7 Graphics smooth. Its SmoothGraphics class anti-aliases in the shader, without MSAA. Its 1.1 line requires PixiJS 7.2 or a newer v7, and as of September 2026 there is no v8 release on npm. On v8, SilkGraphics fills that role with a v8-style API.

// PixiJS v7 + @pixi/graphics-smooth
const g = new SmoothGraphics();
g.lineStyle({ width: 2, color: 0xffffff });
g.beginFill(0x1c1c1e);
g.drawRoundedRect(0, 0, 200, 80, 16);
g.endFill();
g.drawCircle(40, 40, 12);
// PixiJS v8 + pixi-silk
const g = new SilkGraphics();
g.roundRect(0, 0, 200, 80, 16).fill(0x1c1c1e).stroke({ width: 2, color: 0xffffff });
g.circle(40, 40, 12).stroke({ width: 2, color: 0xffffff });
v7 callSilkGraphics
beginFill(c, a) to endFill()shape().fill({ color: c, alpha: a })
lineStyle({ width, color, alpha })shape().stroke({ width, color, alpha })
drawRect, drawRoundedRect, drawCircle, drawEllipserect, roundRect, circle, ellipse
moveTo, lineTo, bezierCurveTothe same, then fill() or stroke()
arc(...)arc(...), which starts its own subpath, or arcSweep(...)
LINE_SCALE_MODE.NONEnot built in. Divide the width by the scale

PixiJS v8 changed the Graphics API itself. The PixiJS v8 migration guide covers the upgrade, and migrating from Graphics covers how v8 Graphics and SilkGraphics differ.

Which one to use#

For sprites and textures only, skip MSAA. The GPU filters textures already.

For a few large shapes and no zoom, antialias: true with resolution: devicePixelRatio is enough.

For UI, charts, icons, thin lines and vector content that zooms or rotates, use SilkGraphics and keep antialias: false. The same holds for content behind filters or in render textures.

The infinite zoom and sub-pixel motion experiments at the end of this page show the difference full screen.

References#

API reference: SilkGraphics, createSilkApp

Go deeper

Experiments on this page

Full-screen scenes. Each one also opens in Studio with its source.