Les shaders personnalisés lient les programmes GLSL et WGSL aux objets de scène via Shader.from({ gl, gpu, resources }). Les uniforms vivent dans des UniformGroups typés, les textures sont passées comme ressources séparées, et le même shader peut cibler à la fois WebGL et WebGPU.
Démarrage rapide
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;
});
Skills associées : pixijs-filters (filtres intégrés), pixijs-scene-mesh (géométrie personnalisée), pixijs-performance (optimisation du batch), pixijs-migration-v8 (migration de l'API shader de v7).
Modèles fondamentaux
Shader dual-renderer (WebGL + WebGPU)
import { Shader, GlProgram, GpuProgram, UniformGroup } from "pixi.js";
const glVertex = `...`; // Vertex GLSL (écrivez `#version 300 es` vous-même si vous voulez WebGL2/GLSL ES 3.0)
const glFragment = `...`; // Fragment GLSL
const wgslSource = `...`; // WGSL combiné
const shader = Shader.from({
gl: { vertex: glVertex, fragment: glFragment },
gpu: {
// Les noms entryPoint sont arbitraires ; ils doivent correspondre aux noms de fonction
// @vertex / @fragment dans votre source WGSL. PixiJS fournit des exemples utilisant
// 'mainVert' / 'mainFrag' mais `main` est tout aussi valide.
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>" },
}),
},
});
Si seul gl est fourni, le shader fonctionne uniquement avec WebGL. Si seul gpu est fourni, il fonctionne uniquement avec WebGPU. Le masque compatibleRenderers est défini automatiquement.
GlProgram n'injecte pas automatiquement #version 300 es. Si vous écrivez #version 300 es vous-même, PixiJS le préserve et traite le shader comme GLSL ES 3.0 ; sinon il injecte des macros de compatibilité WebGL1 (#define in varying, #define texture texture2D) et exécute le shader en tant que GLSL de style WebGL1. GlProgram injecte toujours une précision par défaut (highp vertex, mediump fragment) et le nom du programme. Pour GLSL ES 3.0, utilisez in/out au lieu de attribute/varying, texture() au lieu de texture2D(), et un out vec4 au lieu de gl_FragColor.
Textures comme ressources
Les textures sont des ressources, pas des uniforms. Passez la source et le style de la texture séparément :
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" },
}),
},
});
// Remplacer la texture à l'exécution
shader.resources.uTexture = otherTexture.source;
Les ressources sont une carte clé-valeur plate. La clé doit correspondre au nom uniform/binding dans la source du shader.
Les ressources peuvent aussi être des objets simples (auto-encapsulés dans UniformGroup) :
const shader = Shader.from({
gl: { vertex: vertSrc, fragment: fragSrc },
resources: {
myUniforms: {
uTime: { value: 0, type: "f32" },
},
},
});
Mode UBO (Uniform Buffer Objects)
Le mode UBO empaquette les uniforms dans un seul buffer GPU. Requis pour WebGPU ; optionnel (WebGL2+) pour 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 },
);
// Vous devez appeler update() manuellement quand isStatic est true
ubo.uniforms.uAlpha = 0.5;
ubo.update();
Règles UBO :
- Seuls les types basés sur
f32eti32sont supportés (pas deu32). Les matrices sont float uniquement. - Les samplers/textures ne peuvent pas aller dans un UBO.
- Le nom UniformGroup dans les ressources doit correspondre exactement au nom du bloc UBO dans le shader.
- La structure et l'ordre doivent correspondre exactement à la disposition du shader.
- La synchronisation UBO utilise
new Functionen coulisse. Dans les environnements strict-CSP (pas deunsafe-eval), importezpixi.js/unsafe-evalune fois au démarrage pour basculer vers le chemin de synchronisation par fallback ; sans cela, les shaders soutenus par UBO (et donc WebGPU) lanceront une erreur à la première utilisation.
Filtre personnalisé
Filter.from({ gl, resources }) est le raccourci. Passez uniquement un fragment shader ; PixiJS fournit un vertex shader par défaut qui gère le positionnement du cadre de sortie.
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;
Pour un vertex shader personnalisé, utilisez new Filter({ glProgram: new GlProgram({ vertex, fragment }), resources }).
Conventions du shader filtre (GLSL ES 3.0)
in vec2 vTextureCoord;au lieu devarying vec2 vTextureCoord;out vec4 finalColor;au lieu degl_FragColortexture(uTexture, uv)au lieu detexture2D(uTexture, uv)- Le vertex shader par défaut expose
uInputSize,uOutputFrame,uOutputTextureet les helpersfilterVertexPosition()/filterTextureCoord()
Échantillonner la cible de rendu derrière le filtre
Définissez blendRequired: true et échantillonnez uBackTexture dans le fragment shader. PixiJS copie les pixels de destination dans cet uniform avant d'exécuter le filtre :
const blendFilter = Filter.from({
gl: { fragment: blendFragSrc },
resources: { uniforms: { uAmount: { value: 0.5, type: "f32" } } },
blendRequired: true,
});
N'activez blendRequired que lorsque vous en avez besoin ; cela force une copie GPU supplémentaire chaque frame.
Mettre à jour les uniforms à l'exécution
// Accédez au UniformGroup via resources
shader.resources.myUniforms.uniforms.uTime = performance.now() / 1000;
// Pour les UBOs isStatic, appelez update() après modification des valeurs
shader.resources.myUniforms.update();
Référence des types d'uniform
Consultez references/uniform-types.md pour le tableau complet des types supportés, leurs équivalents WGSL/GLSL, et les formats de valeur.
Custom Batcher (basé sur extensions)
La classe abstraite Batcher active le batching personnalisé pour le rendu spécialisé. Créez-en une sous-classe et enregistrez via des 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 par vertex
public geometry: Geometry;
public shader: Shader;
constructor(options: BatcherOptions) {
super(options);
// Initialisez la géométrie et le shader
}
public packAttributes(
element: BatchableMeshElement,
float32View: Float32Array,
uint32View: Uint32Array,
index: number,
textureId: number,
): void {
// Empaquetez les attributs vertex du mesh dans le buffer batch
}
public packQuadAttributes(
element: BatchableQuadElement,
float32View: Float32Array,
uint32View: Uint32Array,
index: number,
textureId: number,
): void {
// Empaquetez les attributs vertex du quad dans le buffer batch
}
}
extensions.add(MyBatcher);
Les éléments référencent le batcher par batcherName. L'interface BatchableElement nécessite : batcherName, texture, blendMode, indexSize, attributeSize, topology, et packAsQuad.
Erreurs courantes
[CRITICAL] Ancien constructeur Shader.from(vertex, fragment, uniforms)
Faux :
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 nécessite un objet options avec les programmes gl/gpu et resources. L'API positionnelle a été supprimée.
[CRITICAL] UniformGroup sans annotation de type
Faux :
new UniformGroup({ uTime: 1 });
Correct :
new UniformGroup({ uTime: { value: 1, type: "f32" } });
Chaque uniform nécessite une paire explicite { value, type }. Omettre le type cause une erreur à l'exécution : « Uniform type undefined is not supported. »
[HIGH] UBO avec types non supportés ou structure incorrecte
Le mode UBO supporte les types basés sur f32 et i32 (scalaires et vecteurs). u32 n'est pas dans la liste des types supportés de UniformGroup et lèvera une erreur. Les matrices sont float uniquement (mat*<f32>). Les samplers ne peuvent pas être placés dans les UBOs.
Le nom de la struct et l'ordre des champs doivent correspondre exactement à la déclaration UBO du shader. Les désaccords produisent un rendu garbled sans erreur.
[HIGH] Mettre des textures dans UniformGroup
Faux :
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" },
}),
},
});
Les textures sont des ressources, pas des uniforms. Passez texture.source (TextureSource) et texture.source.style (TextureStyle) comme entrées de ressource de haut niveau.