Attach visual effects by assigning one filter (or an array for chaining) to container.filters. Built-in filters cover blur, color matrix, displacement, alpha, and noise; custom filters wrap a GLSL/WGSL fragment shader via Filter.from(...).
Quick Start
tsconst sprite = new Sprite(await Assets.load("hero.png")); app.stage.addChild(sprite); const blur = new BlurFilter({ strength: 4, quality: 4 }); const colorMatrix = new ColorMatrixFilter(); colorMatrix.brightness(1.2, false); sprite.filters = [blur, colorMatrix]; const container = new Container(); container.filters = [new BlurFilter({ strength: 2 })]; container.filterArea = new Rectangle(0, 0, 800, 600); app.stage.addChild(container);
Related skills: pixijs-custom-rendering (shader internals, uniform types), pixijs-blend-modes (composing with filters), pixijs-performance (filter tuning, filterArea).
Core Patterns
Built-in filters
tsimport { AlphaFilter, BlurFilter, ColorMatrixFilter, DisplacementFilter, NoiseFilter, Assets, Sprite, } from "pixi.js"; // Alpha (uniform transparency without per-child layering) const alpha = new AlphaFilter({ alpha: 0.5 }); // Blur — strength/quality are uniform; strengthX/strengthY split axes; // kernelSize must be odd (5, 7, 9, ... 15); repeatEdgePixels avoids transparent edges const blur = new BlurFilter({ strength: 4, quality: 4, kernelSize: 5, repeatEdgePixels: false, }); // Color matrix — brightness is one of many presets. Others: tint, hue, // contrast, saturate, desaturate, greyscale/grayscale, blackAndWhite, // negative, sepia, technicolor, polaroid, kodachrome, browni, vintage, // colorTone, night, predator, lsd, reset. Direct access via // `colorMatrix.matrix` (20-element array) and `colorMatrix.alpha` (blend // between original and transformed). const colorMatrix = new ColorMatrixFilter(); colorMatrix.brightness(1.5, false); colorMatrix.contrast(0.5, true); // multiply stacks on top of existing matrix colorMatrix.alpha = 0.7; // blend at 70% strength // Displacement — scale is a number or PointData const displacementTexture = await Assets.load("displacement_map.png"); const displacementSprite = new Sprite(displacementTexture); const displacement = new DisplacementFilter({ sprite: displacementSprite, scale: { x: 20, y: 10 }, }); // Noise — seed is an arbitrary number that determines the noise pattern; same seed reproduces the same pattern const noise = new NoiseFilter({ noise: 0.5, seed: Math.random() }); sprite.filters = [blur, colorMatrix];
Custom filter with Filter.from()
The simplest way to create a custom filter. Only a fragment shader is needed; PixiJS provides a default vertex shader.
tsimport { Filter } from "pixi.js"; const filter = Filter.from({ gl: { fragment: ` in vec2 vTextureCoord; out vec4 finalColor; uniform sampler2D uTexture; uniform float uTime; void main() { vec2 uv = vTextureCoord; uv.x += sin(uv.y * 10.0 + uTime) * 0.02; finalColor = texture(uTexture, uv); } `, }, resources: { timeUniforms: { uTime: { value: 0, type: "f32" }, }, }, }); sprite.filters = filter; app.ticker.add((ticker) => { filter.resources.timeUniforms.uniforms.uTime += 0.04 * ticker.deltaTime; });
For more control, construct GlProgram/GpuProgram objects directly:
tsimport { Filter, GlProgram } from "pixi.js"; const glProgram = GlProgram.from({ fragment: fragmentSrc, vertex: vertexSrc }); const filter = new Filter({ glProgram, resources: { timeUniforms: { uTime: { value: 0, type: "f32" }, }, }, });
Key points:
- Use
out vec4 finalColorin fragment shaders, notgl_FragColor(GLSL ES 3.0). - Use
texture()to sample, nottexture2D. glProgramfor WebGL,gpuProgramfor WebGPU. Omitting one skips that renderer.- Textures go in
resources, not uniforms. The filter system auto-providesuTexture(the input). - Access uniform values via
filter.resources.{groupName}.uniforms.{name}.
Filter options
tsimport { Filter, GlProgram, Rectangle } from "pixi.js"; const filter = new Filter({ glProgram: GlProgram.from({ fragment }), resources: {}, resolution: 0.5, // default 1. Lower = faster, blurrier. 'inherit' matches the render target resolution padding: 10, // default 0. Extra pixels for effects that extend bounds antialias: "off", // default 'off'. 'on' | 'off' | 'inherit' blendMode: "normal", // default 'normal' blendRequired: false, // default false. true if shader samples uBackTexture clipToViewport: true, // default true }); // Optimization: set known bounds to avoid per-frame measurement container.filterArea = new Rectangle(0, 0, 800, 600); // Toggle without rebuilding the filter array filter.enabled = false; // Share one filter instance across many display objects sprite1.filters = [filter]; sprite2.filters = [filter];
Community filters (pixi-filters)
tsimport { AdjustmentFilter } from "pixi-filters/adjustment"; import { GlowFilter } from "pixi-filters/glow"; sprite.filters = [ new AdjustmentFilter({ brightness: 1.2, contrast: 1.1 }), new GlowFilter({ distance: 15, outerStrength: 2 }), ];
For v8, community filters use pixi-filters/{name} imports, not the old @pixi/filter-* packages.
Advanced blend modes
Advanced blend modes (color-burn, overlay, hard-light, etc.) are powered by the filter system and must be imported before use. They also require useBackBuffer: true on WebGL; see the pixijs-blend-modes skill for the full list.
tsimport "pixi.js/advanced-blend-modes"; await app.init({ useBackBuffer: true }); sprite.blendMode = "color-burn";
Advanced blend modes are filter-based, so they inherit Filter.defaultOptions, whose resolution defaults to 1. On high-DPI render targets this can make a blend mode look clipped, scaled, or only partially applied. Set Filter.defaultOptions.resolution = 'inherit' before creating the affected objects to render at the render target resolution, at higher memory and runtime cost:
tsimport { Filter } from "pixi.js"; import "pixi.js/advanced-blend-modes"; Filter.defaultOptions.resolution = "inherit"; sprite.blendMode = "overlay";
Common Mistakes
[CRITICAL] Using old Filter constructor (vertex, fragment, uniforms)
Wrong:
tsimport { Filter } from "pixi.js"; const filter = new Filter(vertex, fragment, { uTime: 0 });
Correct:
tsimport { Filter, GlProgram } from "pixi.js"; const filter = new Filter({ glProgram: GlProgram.from({ fragment, vertex }), resources: { timeUniforms: { uTime: { value: 0, type: "f32" } }, }, });
v8 uses an options object. Shaders must be wrapped in GlProgram.from() or GpuProgram.from(). Uniforms are grouped in resources with explicit types. Textures are resources, not uniforms.
[HIGH] Using @pixi/filter-* packages for v8
Wrong:
tsimport { AdjustmentFilter } from "@pixi/filter-adjustment";
Correct:
tsimport { AdjustmentFilter } from "pixi-filters/adjustment";
@pixi/filter-* packages are v7 only. For v8, the community filters package restructured to pixi-filters/{name}.
[HIGH] Using too many filters without containerizing
Each filter application requires a framebuffer switch, bounds measurement, and render-to-texture pass. One filter on a parent container is much cheaper than the same filter on each child.
Wrong:
tsfor (const child of container.children) { child.filters = [new BlurFilter({ strength: 4 })]; }
Correct:
tscontainer.filters = [new BlurFilter({ strength: 4 })];
[HIGH] Using a blendRequired filter without useBackBuffer on WebGL
Custom filters and most advanced community filters that set blendRequired: true sample the back buffer. On WebGL that only works if the renderer was initialized with useBackBuffer: true; otherwise PixiJS logs a warning and the filter silently falls back:
tsawait app.init({ useBackBuffer: true });
WebGPU enables the back buffer unconditionally, so this only affects WebGL.
[MEDIUM] Not setting filterArea for known-size containers
Without filterArea, PixiJS measures the container bounds every frame via getGlobalBounds(), which recursively walks all children. For containers with known dimensions, set filterArea to avoid this cost:
tsimport { Rectangle } from "pixi.js"; container.filterArea = new Rectangle(0, 0, 800, 600); container.filters = [someFilter];


