关于 pixijs-custom-rendering
面向 PixiJS v8 开发者的自定义渲染技能,介绍着色器、UniformGroup、滤镜与 GPU 渲染模式。涵盖 WebGL 和 WebGPU 实践及常见配置问题。
主要功能
- 01编写 GLSL 与 WGSL 着色器
- 02配置 UniformGroup 与纹理资源
- 03创建自定义滤镜
- 04使用 UBO 管理 uniform
- 05自定义批处理与 GPU 功能
适用场景
- 01为场景对象编写自定义着色器
- 02实现片元着色器滤镜效果
- 03在 WebGL 与 WebGPU 间适配渲染
- 04调试 uniform、UBO 或纹理配置
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ pixijs/ pixijs-skills --skill pixijs-custom-rendering安装后在 Claude Code 中输入/pixijs-custom-rendering确认已加载。
附带文件references/advanced-gpu.mdreferences/uniform-types.md
详细介绍
自定义着色器通过 Shader.from({ gl, gpu, resources }) 将 GLSL 和 WGSL 程序绑定到场景对象。Uniform 存储在类型化的 UniformGroup 中,纹理作为单独的资源传入,同一个着色器可以同时用于 WebGL 和 WebGPU。
快速开始
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;
});
相关技能: pixijs-filters(内置滤镜)、pixijs-scene-mesh(自定义几何体)、pixijs-performance(批处理优化)、pixijs-migration-v8(从 v7 迁移着色器 API)。
核心模式
双渲染器着色器(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>" },
}),
},
});
如果只提供 gl,着色器只能用于 WebGL。如果只提供 gpu,着色器只能用于 WebGPU。compatibleRenderers 位掩码会自动设置。
GlProgram 不会自动注入 #version 300 es。如果你自行编写 #version 300 es,PixiJS 会保留它,并将着色器视为 GLSL ES 3.0;否则会注入 WebGL1 兼容宏(#define in varying、#define texture texture2D),并以 WebGL1 风格的 GLSL 运行着色器。GlProgram 始终会注入默认精度(顶点着色器为 highp,片段着色器为 mediump)以及程序名称。对于 GLSL ES 3.0 未提供默认精度的采样器类型(usampler2D、isampler2D、sampler2DArray、sampler3D 等),它也会声明 highp,因此你无需为这些类型单独指定精度;你自行编写的精度声明会保留。sampler2D 和 samplerCube 保持默认的 lowp。对于 GLSL ES 3.0,请使用 in/out 替代 attribute/varying,使用 texture() 替代 texture2D(),并使用 out vec4 替代 gl_FragColor。
将纹理作为资源
纹理是资源,而不是 Uniform。分别传入纹理的 source 和 style:
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;
资源是一个扁平的键值映射。键必须与着色器源码中的 Uniform/绑定名称一致。
资源也可以是普通对象(会自动包装为 UniformGroup):
const shader = Shader.from({
gl: { vertex: vertSrc, fragment: fragSrc },
resources: {
myUniforms: {
uTime: { value: 0, type: "f32" },
},
},
});
UBO 模式(Uniform Buffer Objects)
UBO 模式会将 Uniform 打包到单个 GPU 缓冲区中。WebGPU 必须使用此模式;WebGL 中则为可选(WebGL2+)。
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 规则:
- 仅支持基于
f32和i32的类型(不支持u32)。矩阵仅支持浮点类型。 - 采样器/纹理不能放入 UBO。
- 资源中的 UniformGroup 名称必须与着色器中的 UBO 块名称完全一致。
- 结构和顺序必须与着色器布局完全一致。
- UBO 同步底层使用
new Function。在严格 CSP 环境(不允许unsafe-eval)中,请在启动时导入一次pixi.js/unsafe-eval,以启用备用同步路径;否则,首次使用时,基于 UBO 的着色器(因此也包括 WebGPU)会抛出异常。
自定义滤镜
Filter.from({ gl, resources }) 是简写形式。只传入片段着色器即可;PixiJS 会提供默认顶点着色器来处理输出帧的位置。
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;
如需自定义顶点着色器,请使用 new Filter({ glProgram: new GlProgram({ vertex, fragment }), resources })。
滤镜着色器约定(GLSL ES 3.0)
- 使用
in vec2 vTextureCoord;替代varying vec2 vTextureCoord; - 使用
out vec4 finalColor;替代gl_FragColor - 使用
texture(uTexture, uv)替代texture2D(uTexture, uv) - 默认顶点着色器提供
uInputSize、uOutputFrame、uOutputTexture,以及辅助函数filterVertexPosition()/filterTextureCoord()
对滤镜后方的渲染目标进行采样
设置 blendRequired: true,并在片段着色器中采样 uBackTexture。PixiJS 会在运行滤镜前将目标像素复制到该 Uniform 中:
const blendFilter = Filter.from({
gl: { fragment: blendFragSrc },
resources: { uniforms: { uAmount: { value: 0.5, type: "f32" } } },
blendRequired: true,
});
仅在确实需要时启用 blendRequired;它会导致每帧额外执行一次 GPU 复制。
在运行时更新 Uniform
// 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();
高级 GPU 功能
有关以下内容的完整示例,请参阅 references/advanced-gpu.md:
- 部分缓冲区和纹理上传:
buffer.update(sizeInBytes, offsetInBytes)只上传已更改的字节范围;bufferImageSource.update(startTexel, endTexel)只上传数据纹理中的一个纹素范围。 - 顶点数和索引数、绕序、剔除:
geometry.vertexCount(替代已弃用的getSize())、用于绘制共享索引缓冲区前缀的geometry.indexCount、state.clockwiseFrontFace和cullMode。 - WGSL 覆盖常量(WebGPU):
Shader.from({ gpu, resources, overrides: { STEPS: 8 } });每组不同的常量都会编译自己的管线。 - 自定义绑定组布局(WebGPU):使用
generateGpuLayoutGroups(extractStructAndGroups(source))生成默认布局,编辑后作为gpuLayout传入。 - 使用
TextureView采样深度(WebGPU):将new TextureView(depth, { aspect: "depth-only" })作为资源绑定,同时将目标的深度附件设为depthReadOnly。 - 整数纹理:使用
rgba32uint等格式的BufferImageSource、usampler2D/texelFetch和texture_2d<u32>/textureLoad,并设置scaleMode: "nearest"。 - 3D 纹理和数组纹理:在
TextureSource/BufferImageSource上设置depth或arrayLayerCount,使用sampler3D/texture_3d<f32>或sampler2DArray/texture_2d_array<f32>进行采样;它们会整体上传,而renderer.render({ container, target: source, layer })会绘制到一个切片或图层。 - 存储纹理(WebGPU):在
TextureSource上设置storage: true,然后使用renderer.texture.getGpuSource(source).createView(),即可从自己的计算通道向其中写入数据。 - 渲染包(WebGPU):使用
encoder.beginBundle()/endBundle()录制一次绘制,然后在isBundleValid()返回有效时,通过executeBundle()重放。 - 池化临时纹理:使用
TexturePool.getOptimalTexture({ width, height, resolution, antialias })和returnTexture()。
Uniform 类型参考
有关支持类型、对应的 WGSL/GLSL 类型以及值格式的完整表格,请参阅 references/uniform-types.md。
自定义 Batcher(基于扩展)
抽象类 Batcher 支持针对特定渲染需求自定义批处理。将其子类化并通过扩展注册:
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);
元素通过 batcherName 引用 Batcher。BatchableElement 接口要求包含:batcherName、texture、blendMode、indexSize、attributeSize、topology 和 packAsQuad。
如果自定义 InstructionPipe 像 BatcherPipe 管理其批处理器那样,按 InstructionSet 缓存状态,则应实现 destroyInstructionSet(instructionSet)。所属渲染组销毁时,渲染器会调用此方法,以便同时释放缓存的 GPU 对象,避免泄漏。
public destroyInstructionSet(instructionSet: InstructionSet): void {
this._cache[instructionSet.uid]?.destroy();
delete this._cache[instructionSet.uid];
}
常见错误
[CRITICAL] 旧版 Shader.from(vertex, fragment, uniforms) 构造函数
错误:
const shader = Shader.from(vertex, fragment, { uTime: 1 });
正确:
const shader = Shader.from({
gl: { vertex, fragment },
resources: {
uniforms: new UniformGroup({
uTime: { value: 1, type: "f32" },
}),
},
});
v8 要求使用包含 gl/gpu 程序和 resources 的选项对象。位置参数 API 已被移除。
[CRITICAL] UniformGroup 未标注类型
错误:
new UniformGroup({ uTime: 1 });
正确:
new UniformGroup({ uTime: { value: 1, type: "f32" } });
每个 uniform 都必须显式提供 { value, type } 对。省略类型会导致运行时错误:"Uniform type undefined is not supported."
[HIGH] UBO 使用不支持的类型或结构错误
UBO 模式支持基于 f32 和 i32 的类型(标量和向量)。u32 不在受支持的 UniformGroup 类型列表中,使用它会抛出错误。矩阵仅支持浮点类型(mat*<f32>)。采样器不能放入 UBO。
结构体名称和字段顺序必须与着色器的 UBO 声明完全一致。不匹配会导致渲染结果乱码,且不会报错。
[HIGH] 将纹理放入 UniformGroup
错误:
new UniformGroup({
uTexture: { value: texture, type: "f32" },
});
正确:
const shader = Shader.from({
gl: { vertex, fragment },
resources: {
uTexture: texture.source,
uSampler: texture.source.style,
myUniforms: new UniformGroup({
uAlpha: { value: 1.0, type: "f32" },
}),
},
});
纹理是资源,不是 uniform。将 texture.source(TextureSource)和 texture.source.style(TextureStyle)作为顶层资源条目传入。
[HIGH] 将渲染包重放到不同的渲染目标
错误:
const bundle = encoder.endBundle();
// later, on every frame, regardless of what is bound
encoder.executeBundle(bundle);
正确:
if (!bundle || !encoder.isBundleValid(bundle)) {
bundle = record();
}
encoder.executeBundle(bundle);
渲染包会固化录制时所在渲染通道的附件和绕序,以及录制时使用的设备。在不同的目标下重放,要么导致整帧无法通过 WebGPU 验证,要么悄无声息地渲染出内外颠倒的画面;设备丢失后再重放则会被直接拒绝。
[MEDIUM] 读取 Geometry.getSize()
自 8.20.0 起,geometry.getSize() 已弃用,并会记录警告。请改用 geometry.vertexCount,它会缓存结果,且仅在缓冲区或属性发生变化时重新计算。