PluginBench
Skill
Pass
Audit score 90

pixijs-custom-rendering

pixijs/pixijs-skills

Write custom shaders, uniforms, filters, and batchers in PixiJS v8 with WebGL and WebGPU support.

What is pixijs-custom-rendering?

This skill covers building custom shaders using Shader.from(), managing typed uniforms in UniformGroup, creating custom filters, and implementing custom batchers in PixiJS v8. Use it when you need to write GLSL/WGSL programs, handle uniform buffers, or optimize rendering with custom batching logic.

  • Define dual-renderer shaders (WebGL + WebGPU) with Shader.from({ gl, gpu, resources })
  • Manage typed uniforms (f32, vec2, mat4x4) in UniformGroup with UBO mode support
  • Pass textures as resources separate from uniforms
  • Create custom filters via Filter.from() with fragment shader shortcuts
  • Sample the render target behind a filter using uBackTexture with blendRequired flag
  • Implement custom Batcher subclasses for specialized rendering via extensions

How to install pixijs-custom-rendering

npx skills add https://github.com/pixijs/pixijs-skills --skill pixijs-custom-rendering
Prerequisites
  • PixiJS v8 installed
  • Basic understanding of GLSL ES 3.0 or WGSL syntax
  • For strict CSP environments: import pixi.js/unsafe-eval at startup for UBO support
Claude Code
Cursor
Windsurf
Cline

How to use pixijs-custom-rendering

  1. 1.Define uniforms in a typed UniformGroup with value and type properties
  2. 2.Write vertex and fragment shaders in GLSL ES 3.0 (use in/out, texture(), finalColor)
  3. 3.Create a Shader via Shader.from() passing gl/gpu programs and resources
  4. 4.Attach the shader to a Mesh or use Filter.from() for post-processing
  5. 5.Update uniform values at runtime via shader.resources.myUniforms.uniforms.property
  6. 6.For static UBOs, call update() after changing values to sync to GPU
  7. 7.Register custom Batcher subclasses via extensions.add() with ExtensionType.Batcher

Use cases

Good for
  • Build a time-animated shader that updates uniforms each frame for visual effects
  • Create a custom post-process filter that blends the scene with a distortion effect
  • Implement a specialized batcher for rendering thousands of custom geometry objects efficiently
  • Write a WebGPU-compatible shader that works identically in WebGL fallback mode
  • Sample the framebuffer behind a filter to create blend or composite effects
Who it's for
  • Graphics programmers writing custom shaders and effects
  • Game developers optimizing rendering performance with custom batchers
  • PixiJS v8 users migrating from v7 shader APIs
  • WebGPU-first developers needing WebGL2 compatibility
  • Performance-critical applications requiring UBO-based uniform management

pixijs-custom-rendering FAQ

Do I need to write both GLSL and WGSL?

No. Provide only gl for WebGL-only, only gpu for WebGPU-only, or both for dual-renderer support. PixiJS auto-detects compatibleRenderers.

How do I use textures in shaders?

Pass texture.source and texture.source.style as separate resources, not as uniforms. Update them at runtime via shader.resources.uTexture = newTexture.source.

What is UBO mode and when should I use it?

UBO packs uniforms into a single GPU buffer. Required for WebGPU; optional for WebGL2+. Use it for performance with many uniforms. Call update() manually if isStatic: true.

How do I sample the framebuffer behind a filter?

Set blendRequired: true on the filter and sample uBackTexture in the fragment shader. PixiJS copies destination pixels into that uniform before running the filter.

Do I need #version 300 es in my GLSL?

Optional. GlProgram auto-injects it if absent and uses WebGL1 compat macros. If you write it yourself, PixiJS treats the shader as GLSL ES 3.0 and preserves it.

Full instructions (SKILL.md)

Source of truth, from pixijs/pixijs-skills.


name: pixijs-custom-rendering description: "Use this skill when writing custom shaders, uniforms, filters, or batchers in PixiJS v8. Covers Shader.from({gl, gpu, resources}), GlProgram/GpuProgram, UniformGroup with typed uniforms (f32, vec2, mat4x4), UBO mode, textures as resources, custom Filter via Filter.from, GLSL ES 3.0 conventions (in/out, finalColor, texture()), uBackTexture sampling, pixi.js/unsafe-eval for strict CSP, custom Batcher via extensions. Triggers on: Shader, GlProgram, GpuProgram, UniformGroup, Batcher, Filter, Filter.from, GLSL, WGSL, UBO, uniform, custom shader, finalColor, uBackTexture, blendRequired, unsafe-eval." license: MIT

Custom shaders bind GLSL and WGSL programs to scene objects via Shader.from({ gl, gpu, resources }). Uniforms live in typed UniformGroups, textures are passed as separate resources, and the same shader can target both WebGL and WebGPU.

Quick Start

const uniforms = new UniformGroup({
  uTime: { value: 0, type: "f32" },
});

const shader = Shader.from({
  gl: { vertex: vertexSrc, fragment: fragmentSrc },
  resources: { uniforms },
});

const geometry = new MeshGeometry({
  positions: new Float32Array([0, 0, 100, 0, 100, 100, 0, 100]),
  uvs: new Float32Array([0, 0, 1, 0, 1, 1, 0, 1]),
  indices: new Uint32Array([0, 1, 2, 0, 2, 3]),
});

const mesh = new Mesh({ geometry, shader });
app.stage.addChild(mesh);

app.ticker.add(() => {
  shader.resources.uniforms.uniforms.uTime = performance.now() / 1000;
});

Related skills: pixijs-filters (built-in filters), pixijs-scene-mesh (custom geometry), pixijs-performance (batch optimization), pixijs-migration-v8 (shader API migration from v7).

Core Patterns

Dual-renderer shader (WebGL + WebGPU)

import { Shader, GlProgram, GpuProgram, UniformGroup } from "pixi.js";

const glVertex = `...`; // GLSL vertex (write `#version 300 es` yourself if you want WebGL2/GLSL ES 3.0)
const glFragment = `...`; // GLSL fragment
const wgslSource = `...`; // WGSL combined

const shader = Shader.from({
  gl: { vertex: glVertex, fragment: glFragment },
  gpu: {
    // entryPoint names are arbitrary; they must match the @vertex / @fragment
    // function names in your WGSL source. PixiJS ships examples using
    // 'mainVert' / 'mainFrag' but `main` is equally valid.
    vertex: { entryPoint: "mainVert", source: wgslSource },
    fragment: { entryPoint: "mainFrag", source: wgslSource },
  },
  resources: {
    myUniforms: new UniformGroup({
      uColor: { value: new Float32Array([1, 0, 0, 1]), type: "vec4<f32>" },
      uMatrix: { value: new Float32Array(16), type: "mat4x4<f32>" },
    }),
  },
});

If only gl is provided, the shader works with WebGL only. If only gpu is provided, it works with WebGPU only. The compatibleRenderers bitmask is set automatically.

GlProgram does not auto-inject #version 300 es. If you write #version 300 es yourself, PixiJS preserves it and treats the shader as GLSL ES 3.0; otherwise it injects WebGL1 compat macros (#define in varying, #define texture texture2D) and runs the shader as WebGL1-style GLSL. GlProgram always injects a default precision (highp vertex, mediump fragment) and the program name. For GLSL ES 3.0, use in/out instead of attribute/varying, texture() instead of texture2D(), and an out vec4 instead of gl_FragColor.

Textures as resources

Textures are resources, not uniforms. Pass the texture's source and style separately:

import { Shader, UniformGroup, Texture, Assets } from "pixi.js";

const texture = await Assets.load("myImage.png");

const shader = Shader.from({
  gl: { vertex: vertSrc, fragment: fragSrc },
  resources: {
    uTexture: texture.source,
    uSampler: texture.source.style,
    myUniforms: new UniformGroup({
      uAlpha: { value: 1.0, type: "f32" },
    }),
  },
});

// Swap texture at runtime
shader.resources.uTexture = otherTexture.source;

Resources are a flat key-value map. The key must match the uniform/binding name in the shader source.

Resources can also be plain objects (auto-wrapped into UniformGroup):

const shader = Shader.from({
  gl: { vertex: vertSrc, fragment: fragSrc },
  resources: {
    myUniforms: {
      uTime: { value: 0, type: "f32" },
    },
  },
});

UBO mode (Uniform Buffer Objects)

UBO mode packs uniforms into a single GPU buffer. Required for WebGPU; optional (WebGL2+) for WebGL.

import { UniformGroup } from "pixi.js";

const ubo = new UniformGroup(
  {
    uProjection: { value: new Float32Array(16), type: "mat4x4<f32>" },
    uAlpha: { value: 1.0, type: "f32" },
  },
  { ubo: true, isStatic: true },
);

// Must call update() manually when isStatic is true
ubo.uniforms.uAlpha = 0.5;
ubo.update();

UBO rules:

  • Only f32 and i32 based types are supported (no u32). Matrices are float-only.
  • Samplers/textures cannot go in a UBO.
  • The UniformGroup name in resources must exactly match the UBO block name in the shader.
  • Structure and order must exactly match the shader layout.
  • UBO sync uses new Function under the hood. In strict-CSP environments (no unsafe-eval), import pixi.js/unsafe-eval once at startup to swap in the fallback sync path; without it, UBO-backed shaders (and therefore WebGPU) will throw on first use.

Custom filter

Filter.from({ gl, resources }) is the shorthand. Pass only a fragment shader; PixiJS supplies a default vertex shader that handles output frame positioning.

import { Filter } from "pixi.js";

const filter = Filter.from({
  gl: {
    fragment: `
            in vec2 vTextureCoord;
            out vec4 finalColor;
            uniform sampler2D uTexture;
            uniform float uStrength;

            void main(void) {
                vec4 color = texture(uTexture, vTextureCoord);
                finalColor = mix(color, vec4(1.0 - color.rgb, color.a), uStrength);
            }
        `,
  },
  resources: {
    filterUniforms: {
      uStrength: { value: 0.5, type: "f32" },
    },
  },
});

filter.resources.filterUniforms.uniforms.uStrength = 1.0;

For a custom vertex shader, use new Filter({ glProgram: new GlProgram({ vertex, fragment }), resources }).

Filter shader conventions (GLSL ES 3.0)

  • in vec2 vTextureCoord; instead of varying vec2 vTextureCoord;
  • out vec4 finalColor; instead of gl_FragColor
  • texture(uTexture, uv) instead of texture2D(uTexture, uv)
  • The default vertex shader exposes uInputSize, uOutputFrame, uOutputTexture and helpers filterVertexPosition() / filterTextureCoord()

Sampling the render target behind the filter

Set blendRequired: true and sample uBackTexture in the fragment shader. PixiJS copies the destination pixels into that uniform before running the filter:

const blendFilter = Filter.from({
  gl: { fragment: blendFragSrc },
  resources: { uniforms: { uAmount: { value: 0.5, type: "f32" } } },
  blendRequired: true,
});

Only enable blendRequired when you need it; it forces an extra GPU copy every frame.

Updating uniforms at runtime

// Access the UniformGroup via resources
shader.resources.myUniforms.uniforms.uTime = performance.now() / 1000;

// For isStatic UBOs, call update() after changing values
shader.resources.myUniforms.update();

Uniform type reference

See references/uniform-types.md for the complete table of supported types, their WGSL/GLSL equivalents, and value formats.

Custom Batcher (extension-based)

The Batcher abstract class enables custom batching for specialized rendering. Subclass it and register via extensions:

import { Batcher, extensions, ExtensionType } from "pixi.js";
import type {
  BatcherOptions,
  BatchableMeshElement,
  BatchableQuadElement,
  Geometry,
  Shader,
} from "pixi.js";

class MyBatcher extends Batcher {
  public static extension = {
    type: [ExtensionType.Batcher],
    name: "my-batcher",
  };

  public name = "my-batcher";
  protected vertexSize = 6; // floats per vertex
  public geometry: Geometry;
  public shader: Shader;

  constructor(options: BatcherOptions) {
    super(options);
    // Initialize geometry and shader
  }

  public packAttributes(
    element: BatchableMeshElement,
    float32View: Float32Array,
    uint32View: Uint32Array,
    index: number,
    textureId: number,
  ): void {
    // Pack mesh vertex attributes into the batch buffer
  }

  public packQuadAttributes(
    element: BatchableQuadElement,
    float32View: Float32Array,
    uint32View: Uint32Array,
    index: number,
    textureId: number,
  ): void {
    // Pack quad vertex attributes into the batch buffer
  }
}

extensions.add(MyBatcher);

Elements reference the batcher by batcherName. The BatchableElement interface requires: batcherName, texture, blendMode, indexSize, attributeSize, topology, and packAsQuad.

Common Mistakes

[CRITICAL] Old Shader.from(vertex, fragment, uniforms) constructor

Wrong:

const shader = Shader.from(vertex, fragment, { uTime: 1 });

Correct:

const shader = Shader.from({
  gl: { vertex, fragment },
  resources: {
    uniforms: new UniformGroup({
      uTime: { value: 1, type: "f32" },
    }),
  },
});

v8 requires an options object with gl/gpu programs and resources. The positional API was removed.

[CRITICAL] UniformGroup without type annotation

Wrong:

new UniformGroup({ uTime: 1 });

Correct:

new UniformGroup({ uTime: { value: 1, type: "f32" } });

Every uniform requires an explicit { value, type } pair. Omitting the type causes a runtime error: "Uniform type undefined is not supported."

[HIGH] UBO with unsupported types or wrong structure

UBO mode supports f32 and i32 based types (scalars and vectors). u32 is not in the supported UniformGroup type list and will throw. Matrices are float-only (mat*<f32>). Samplers cannot be placed in UBOs.

The struct name and field order must exactly match the shader's UBO declaration. Mismatches produce garbled rendering with no error.

[HIGH] Putting textures in UniformGroup

Wrong:

new UniformGroup({
  uTexture: { value: texture, type: "f32" },
});

Correct:

const shader = Shader.from({
  gl: { vertex, fragment },
  resources: {
    uTexture: texture.source,
    uSampler: texture.source.style,
    myUniforms: new UniformGroup({
      uAlpha: { value: 1.0, type: "f32" },
    }),
  },
});

Textures are resources, not uniforms. Pass texture.source (TextureSource) and texture.source.style (TextureStyle) as top-level resource entries.

API Reference

Related skills

More from pixijs/pixijs-skills and the wider catalog.

PIpixijs-environments logo

pixijs-environments

pixijs/pixijs-skills

Run PixiJS v8 outside the browser: Web Workers, OffscreenCanvas, Node/SSR, and CSP-restricted contexts.

1.9k installsAudited
PIpixijs-events logo

pixijs-events

pixijs/pixijs-skills

Handle pointer, mouse, touch, and wheel input in PixiJS v8 with federated events.

2.0k installsAudited
PIpixijs-filters logo

pixijs-filters

pixijs/pixijs-skills

Apply visual effects to PixiJS v8 containers using built-in and custom filters with GLSL/WGSL shaders.

2.0k installsAudited
PIpixijs-html-source logo

pixijs-html-source

pixijs/pixijs-skills

Use this skill when rendering live HTML/DOM elements (or frozen snapshots of them) as PixiJS v8 textures via the EXPERIMENTAL HTML-in-Canvas browser APIs. Covers the pixi.js/html-source side-effect import, feature-detection with canvas.requestPaint, HTMLSource for a live, repainting element kept interactive in the browser (autoLayout/autoUpdate/autoRequestPaint, requestPaint, isReady, the direct-child-of-canvas + layoutsubtree requirement), ElementImageSource for an immutable captureElementImage() snapshot (autoClose, ready immediately), using the source on a Sprite/Texture/Mesh, fallback-only auto-detection via Texture.from at priority -10, and destroy/cleanup. Triggers on: HTMLSource, ElementImageSource, pixi.js/html-source, requestPaint, captureElementImage, ElementImage, layoutsubtree, autoRequestPaint, autoUpdate, autoClose, HTML in canvas, render DOM to texture, HTMLSourceOptions, ElementImageSourceOptions, HTMLSourceCanvas, experimental.

773 installsAudited
PIpixijs-math logo

pixijs-math

pixijs/pixijs-skills

PixiJS v8 math primitives: points, matrices, shapes, hit testing, and coordinate transforms.

2.0k installsAudited
PIpixijs-migration-v8 logo

pixijs-migration-v8

pixijs/pixijs-skills

Migrate PixiJS v7 code to v8: async init, new Graphics API, single package, shader rework.

1.7k installsAudited