Skip to content

pixi-post-processing

**Scope**: pixi-filters v6+ for PixiJS v8, filter chain composition, bloom, vignette, color grading, chromatic aberration, performance budgets **Version range**: pixi.js ^8.5.0, pixi-filters v6+ (required for v8)

From plugin
vexjoy-agent
413198 skills198 agents10 commands86 hooks
Install
$ npx -y skills add notque/vexjoy-agent --agent claude-code

How it fires

How this agent gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.

Context preview

The summary Claude sees to decide when to auto-load this agent.

**Scope**: pixi-filters v6+ for PixiJS v8, filter chain composition, bloom, vignette, color grading, chromatic aberration, performance budgets **Version range**: pixi.js ^8.5.0, pixi-filters v6+ (required for v8)

Agent definition

pixi-post-processing.md

PixiJS v8 Post-Processing Reference

> **Scope**: pixi-filters v6+ for PixiJS v8, filter chain composition, bloom, vignette, color grading, chromatic aberration, performance budgets > **Version range**: pixi.js ^8.5.0, pixi-filters v6+ (required for v8)

---

Installation

npm install pixi-filters
# Verify v6+: npm ls pixi-filters

Individual `@pixi/filter-*` packages are NOT maintained for v8. Import from `pixi-filters`.

---

Import Patterns

// pixi-filters v6+ (external)
import { AdvancedBloomFilter, VignetteFilter, CRTFilter, GodrayFilter } from 'pixi-filters';

// Built-in PixiJS v8 (no separate package)
import { BlurFilter, ColorMatrixFilter, DisplacementFilter, NoiseFilter, AlphaFilter } from 'pixi.js';

---

Filter Chain Composition

Filters execute in array order. Each receives previous output as input.

const bloom = new AdvancedBloomFilter({ threshold: 0.5, bloomScale: 0.8, brightness: 1.0, blur: 8, quality: 4 });
const vignette = new VignetteFilter({ alpha: 0.6, size: 0.5 });
sceneContainer.filters = [bloom, vignette];

Chain order: (1) Displacement/shake early, (2) Bloom, (3) Color grading, (4) Vignette, (5) Chromatic aberration last.

---

AdvancedBloomFilter

const combatBloom = new AdvancedBloomFilter({
  threshold: 0.5, bloomScale: 0.8, brightness: 1.0, blur: 8, quality: 4,
  resolution: window.devicePixelRatio,
});

Threshold guide: 0.5-0.7 subtle, 0.3-0.5 moderate, 0.1-0.3 heavy (finisher only). Quality: 2 fast/mobile, 4 default, 8 finisher freeze-frame only.

---

VignetteFilter

const vignette = new VignetteFilter({ alpha: 0.6, size: 0.5, resolution: window.devicePixelRatio });

// Animate during finisher
function triggerFinisherVignette(vignette: VignetteFilter): void {
  let elapsed = 0;
  const ticker = app.ticker.add((t) => {
    elapsed += t.deltaMS;
    vignette.alpha = Math.min(0.95, elapsed / 300);
    if (elapsed > 1500) { vignette.alpha = 0.6; app.ticker.remove(ticker); }
  });
}

Replaces CSS `::after` vignette overlay on `CombatArena.tsx` — PixiJS version can animate reactively.

---

ColorMatrixFilter (Color Grading)

const colorGrade = new ColorMatrixFilter();

// Arena warm tone
colorGrade.saturate(0.2, false);
colorGrade.warmth(0.1, false);

// Submission blue tint
colorGrade.reset();
colorGrade.tint(0x4080ff, false);
colorGrade.saturate(-0.1, false);

// Finisher high contrast
colorGrade.reset();
colorGrade.brightness(1.1, false);
colorGrade.contrast(0.15, false);
colorGrade.saturate(0.3, false);

Methods: `saturate`, `brightness`, `contrast`, `hue`, `tint`, `greyscale`, `sepia`, `technicolor`, `night`.

---

Chromatic Aberration (Custom Filter)

import { Filter, GlProgram } from 'pixi.js';

const FRAG_SRC = `
  #version 300 es
  precision mediump float;
  in vec2 vTextureCoord;
  out vec4 fragColor;
  uniform sampler2D uTexture;
  uniform float uAmount;

  void main(void) {
    float r = texture(uTexture, vTextureCoord + vec2(uAmount, 0.0)).r;
    float g = texture(uTexture, vTextureCoord).g;
    float b = texture(uTexture, vTextureCoord - vec2(uAmount, 0.0)).b;
    fragColor = vec4(r, g, b, texture(uTexture, vTextureCoord).a);
  }
`;

export class ChromaticAberrationFilter extends Filter {
  constructor(amount = 0.003) {
    super({
      glProgram: GlProgram.from({ fragment: FRAG_SRC }),
      resources: { caUniforms: { uAmount: { value: amount, type: 'f32' } } },
    });
    this.resolution = window.devicePixelRatio;
  }
  set amount(value: number) { this.resources.caUniforms.uniforms.uAmount = value; }
}

// On hit: spike and decay over 200ms
function triggerHitAberration(ca: ChromaticAberrationFilter): void {
  ca.amount = 0.008;
  let elapsed = 0;
  const ticker = app.ticker.add((t) => {
    elapsed += t.deltaMS;
    ca.amount = Math.max(0.001, 0.008 - (elapsed / 200) * 0.007);
    if (elapsed >= 200) app.ticker.remove(ticker);
  });
}

---

Full Combat Filter Stack

const isMobile = /iPhone|iPad|Android/i.test(navigator.userAgent);

export function useCombatFilters(containerRef: React.RefObject<Container | null>) {
  const app = useApp();
  useEffect(() => {
    if (!containerRef.current) return;
    const resolution = app.renderer.resolution;

    if (isMobile) {
      const vignette = new VignetteFilter({ alpha: 0.5, size: 0.5 });
      vignette.resolution = resolution;
      containerRef.current.filters = [vignette];
      return () => { vignette.destroy(); if (containerRef.current) containerRef.current.filters = []; };
    }

    const bloom = new AdvancedBloomFilter({ threshold: 0.5, bloomScale: 0.8, brightness: 1.0, blur: 8, quality: 4 });
    bloom.resolution = resolution;
    const vignette = new VignetteFilter({ alpha: 0.6, size: 0.5 });
    vignette.resolution = resolution;
    const colorGrade = new ColorMatrixFilter();
    colorGrade.saturate(0.2, false); colorGrade.warmth(0.1, false);
    const ca = new ChromaticAberrationFilter(0.001);

    containerRef.current.filters = [bloom, colorGrade, vignette, ca];
    return () => { [bloom, vignette, colorGrade, ca].forEach(f => f.destroy()); if (containerRef.current) containerRef.current.filters = []; };
  }, [app.renderer.resolution]);
}

---

Performance Budget

| Filter | GPU cost | Mobile? | |--------|----------|---------| | VignetteFilter | Very low | Yes | | ColorMatrixFilter | Very low | Yes | | AdvancedBloomFilter quality:2 | Low | Marginal | | AdvancedBloomFilter quality:4 | Medium | No | | AdvancedBloomFilter quality:8 | High | No (finisher only) | | ChromaticAberrationFilter | Very low | Yes | | NormalMapFilter | Medium | No |

Desktop: 4 filter passes max on root container. Mobile: 2 passes max. Recommended: `[VignetteFilter, ColorMatrixFilter]`.

Each pass redraws entire canvas texture. 1920x1080 at retina (2x) with 5 passes = 41.5M pixels/frame at 60fps — mobile GPU stall threshold.

---

Patterns to Fix

Read more
Ships withvexjoy-agent

Essays and writing behind this toolkit live at vexjoy.com. AI agents skip steps. "Looks correct" replaces running tests. "Trivial change" replaces verification.

Get the whole plugin, auto-invoked