PixiJS v8 自定义渲染完全指南:Shader、UniformGroup、Filter 与 Batcher 实战
【免费下载链接】pixijsThe HTML5 Creation Engine: Create beautiful digital content with the fastest, most flexible 2D WebGL renderer.项目地址: https://gitcode.com/gh_mirrors/pi/pixijs
本指南以 PixiJS v8 自定义渲染能力为主线,系统讲解如何通过Shader.from({ gl, gpu, resources })将 GLSL / WGSL 着色器绑定到场景对象,如何用带类型标注的UniformGroup管理 uniform、以独立资源方式传入纹理,以及如何基于Filter.from快速构建自定义滤镜、基于扩展机制注册自定义Batcher。读完本文,你将掌握 WebGL 与 WebGPU 双渲染器共用的着色器编写范式、UBO 模式的使用边界、滤镜的 GLSL ES 3.0 约定,以及常见踩坑点的规避方法。
本文对应的技能文档位于 skills/pixijs-custom-rendering/SKILL.md,配套的完整 uniform 类型表见 references/uniform-types.md;所有结论均可对照
src/rendering/与src/filters/下的源码验证。
快速上手:把第一个自定义着色器挂到 Mesh 上
自定义渲染的核心 API 是Shader.from。它接收一个 options 对象,其中gl声明 WebGL 着色器源码,resources声明 uniform 与纹理资源,然后与MeshGeometry、Mesh组合即可渲染:
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; });从源码层面看,Shader是渲染管线中连接「着色器」与「几何体」的枢纽(见 Shader.ts):WebGL 侧持有GlProgram,WebGPU 侧持有GpuProgram,两者通过同一个资源对象共享 uniform 数据。若只提供其中一方,compatibleRenderers位掩码会自动按0b01(WebGL)/0b10(WebGPU)设置,缺失的一侧渲染器将无法使用该着色器(Shader.ts)。
需要强调的是,v8 的Shader.from只接受 options 对象,v7 时代的位置参数构造Shader.from(vertex, fragment, uniforms)已被移除(详见下文「常见错误」)。
核心模式
双渲染器着色器(WebGL + WebGPU)
一份着色器源码同时支持两种渲染器,只需同时提供gl与gpu两个程序描述:
import { Shader, GlProgram, GpuProgram, UniformGroup } from "pixi.js"; const glVertex = `...`; // GLSL vertex(如需 WebGL2/GLSL ES 3.0,可自行编写 `#version 300 es`) const glFragment = `...`; // GLSL fragment const wgslSource = `...`; // WGSL 合并源码 const shader = Shader.from({ gl: { vertex: glVertex, fragment: glFragment }, gpu: { // entryPoint 名称可任取,但必须与 WGSL 源码中的 @vertex / @fragment 函数名一致。 // PixiJS 自带示例习惯使用 'mainVert' / 'mainFrag',使用 `main` 同样合法。 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;两者都传则自动获得双渲染器兼容。 resources中 UniformGroup 的键名必须与着色器源码中的 uniform/binding 名称一致——WebGPU 侧是硬性要求,WebGL 侧则相对宽松(见 Shader.ts)。- 仓库中可参考的完整双渲染器示例:mesh_custom_shader_geometry/index.ts(同时提供了
triangle.vert/triangle.frag/triangle.wgsl三份源码),以及 mesh_custom_color_attributes/index.ts。
关于 GLSL 版本的关键事实:GlProgram不会自动注入#version 300 es。它的预处理流水线依次执行stripVersion→ensurePrecision→addProgramDefines→setProgramName→insertVersion(见 GlProgram.ts):
- 若你在源码里自行写了
#version 300 es,PixiJS 会保留它并按 GLSL ES 3.0 处理; - 否则会注入 WebGL1 兼容宏(
#define in varying、#define texture texture2D),按 WebGL1 风格 GLSL 运行; - 无论哪种情况,
GlProgram都会注入默认精度(顶点highp、片元mediump,见 GlProgram.ts)与程序名。
因此编写 GLSL ES 3.0 时请遵循:用in/out替代attribute/varying,用texture()替代texture2D(),用out vec4替代gl_FragColor。另外,GlProgram.from会按「顶点 + 片元源码」建立程序缓存,复用相同源码不会重复编译(GlProgram.ts),这也是为什么官方建议尽可能复用程序对象。
纹理是资源,不是 uniform
纹理不能放进UniformGroup,而是作为独立的顶级资源传入:纹理的source(TextureSource)与style(TextureStyle)要分开传递:
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" }, }), }, }); // 运行时切换纹理 shader.resources.uTexture = otherTexture.source;资源是一个扁平的键值映射,键名必须匹配着色器源码中的 uniform/binding 名。渲染时修改shader.resources中的任意资源即可热更新。
便捷机制:resources中的普通对象会被自动包装为UniformGroup。这一逻辑在Shader构造器中实现——凡是没有source属性、也不是BindResource的值,都会走value = new UniformGroup(value)(Shader.ts)。因此下面两种写法等价:
const shader = Shader.from({ gl: { vertex: vertSrc, fragment: fragSrc }, resources: { myUniforms: { uTime: { value: 0, type: "f32" }, }, }, });UBO 模式(Uniform Buffer Objects)
UBO 模式把一组 uniform 打包进单个 GPU 缓冲区。WebGPU 只能通过 UBO 使用 uniform,因此想在 WebGPU 下跑自定义着色器就必须开启它;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 }, ); // isStatic 为 true 时必须手动调用 update() 触发上传 ubo.uniforms.uAlpha = 0.5; ubo.update();UBO 规则(源码注释见 UniformGroup.ts):
- 仅支持
f32与i32系类型(标量与向量),矩阵仅支持浮点(mat*<f32>);u32不在UniformGroup的类型表中,会直接抛错。 - 采样器/纹理不能放进 UBO(GPU 限制)。
- resources 中的键名必须与着色器中的 UBO 块名完全一致。
- 结构(字段名与顺序)必须与着色器布局完全一致,否则渲染结果错乱且不会报错。
- UBO 同步底层依赖
new Function动态生成同步函数。在禁止unsafe-eval的严格 CSP 环境下,需要在启动时一次性引入pixi.js/unsafe-eval以切换到回退同步路径;否则首次使用 UBO(进而 WebGPU)时会抛错。该入口导出generateUboSyncPolyfill等回退实现,见 src/unsafe-eval/index.ts 与 src/unsafe-eval/ubo/。
另外注意UniformGroup的默认选项为ubo: false、isStatic: false(UniformGroup.ts):非静态模式下数据每帧自动重传,无需手动update();isStatic: true则把上传时机完全交给你,适合每帧只更新一次的 UBO,以获得最大性能收益。
自定义滤镜:Filter.from
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, });Filter继承自Shader(Filter.ts),因此它天然具备 Shader 的全部资源能力。其默认配置在 Filter.ts 中定义:blendMode: 'normal'、resolution: 1、padding: 0、antialias: 'off'、blendRequired: false、clipToViewport: true。其中:
resolution可设为数字或'inherit',调低分辨率可显著提升滤镜性能(模糊类滤镜常用);padding为滤镜扩展的边界像素,模糊等会外溢的效果需要它防止裁切;antialias支持'on' | 'off' | 'inherit','inherit'会跟随渲染目标的抗锯齿设置。
滤镜可作用于任何继承自Container的对象(Sprite、Graphics 等)。它的底层流程是:打断当前批次 → 用getGlobalBounds测量目标 → 从纹理池取纹理 → 把目标渲染到该纹理 → 再以滤镜程序把纹理作为 quad 画回主帧缓冲(见 Filter.ts)。正因如此,对一个容器应用一次滤镜远比给大量对象各挂一个滤镜快得多。
滤镜着色器约定(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()
Filter构造时会自动为uTexture预留资源槽位(addResource('uTexture', 0, 1),见 Filter.ts),所以片元着色器里声明uniform sampler2D uTexture;即可取到滤镜输入。
采样滤镜背后的渲染目标
当滤镜需要感知「背后已经画好的像素」(例如实现混合类效果)时,设置blendRequired: true,然后在片元着色器中采样uBackTexture——滤镜系统会先把目标区域的像素拷贝进该 uniform 再运行滤镜:
const blendFilter = Filter.from({ gl: { fragment: blendFragSrc }, resources: { uniforms: { uAmount: { value: 0.5, type: "f32" } } }, blendRequired: true, });从实现看,开启后Filter会额外执行addResource('uBackTexture', 0, 3)(Filter.ts),而关闭时该资源槽不存在。只在确实需要时开启blendRequired——它每帧都会强制增加一次额外的 GPU 拷贝(对应源码注释 "otherwise its an extra gpu copy you don't need!")。
运行时更新 uniform
// 通过 resources 访问 UniformGroup shader.resources.myUniforms.uniforms.uTime = performance.now() / 1000; // 对于 isStatic 的 UBO,修改值后要手动调用 update() shader.resources.myUniforms.update();注意UniformGroup.update()的实现是递增_dirtyId以标记数据待上传(UniformGroup.ts),渲染器据此决定是否重传 GPU 缓冲区。
Uniform 类型参考
UniformGroup中每个 uniform 都必须以{ value, type }形式声明,type字符串直接对应 WebGPU 类型。完整类型表见 references/uniform-types.md,以下为速查:
| PixiJS 类型 | WGSL 等价 | GLSL 等价 | JS 值 |
|---|---|---|---|
f32 | f32 | float | number |
i32 | i32 | int | number |
vec2<f32> | vec2<f32> | vec2 | Float32Array(2)或[x, y] |
vec3<f32> | vec3<f32> | vec3 | Float32Array(3) |
vec4<f32> | vec4<f32> | vec4 | Float32Array(4) |
vec2<i32> | vec2<i32> | ivec2 | Int32Array(2) |
vec3<i32> | vec3<i32> | ivec3 | Int32Array(3) |
vec4<i32> | vec4<i32> | ivec4 | Int32Array(4) |
mat2x2<f32> | mat2x2<f32> | mat2 | Float32Array(4) |
mat3x3<f32> | mat3x3<f32> | mat3 | Float32Array(9)或Matrix |
mat4x4<f32> | mat4x4<f32> | mat4 | Float32Array(16) |
mat3x2<f32> | mat3x2<f32> | mat3x2 | Float32Array(6) |
mat4x2<f32> | mat4x2<f32> | mat4x2 | Float32Array(8) |
mat2x3<f32> | mat2x3<f32> | mat2x3 | Float32Array(6) |
mat4x3<f32> | mat4x3<f32> | mat4x3 | Float32Array(12) |
mat2x4<f32> | mat2x4<f32> | mat2x4 | Float32Array(8) |
mat3x4<f32> | mat3x4<f32> | mat3x4 | Float32Array(12) |
底层支持类型白名单定义在 types.ts,UniformGroup构造时会校验每个 type,未命中白名单会抛出Uniform type ... is not supported错误(UniformGroup.ts)。
数组 uniform:不要用array<...>语法写进 type 字段,改用size属性:
import { UniformGroup } from "pixi.js"; // 10 个 vec4 组成的数组 const uniforms = new UniformGroup({ uColors: { value: new Float32Array(40), type: "vec4<f32>", size: 10 }, });若在 type 中写array<vec4<f32>, 10>,构造器会直接抛错,并提示改用type: 'vec4<f32>', size: 10(UniformGroup.ts)。
常见用法示例:
// 标量 const uniforms = new UniformGroup({ uTime: { value: 0, type: "f32" }, uIndex: { value: 0, type: "i32" }, }); uniforms.uniforms.uTime = 1.5; uniforms.uniforms.uIndex = 3; // 向量 const uniforms = new UniformGroup({ uPosition: { value: new Float32Array([100, 200]), type: "vec2<f32>" }, uDirection: { value: new Float32Array([0, 1, 0]), type: "vec3<f32>" }, uColor: { value: new Float32Array([1, 0, 0, 1]), type: "vec4<f32>" }, uGridSize: { value: new Int32Array([16, 16]), type: "vec2<i32>" }, }); // 矩阵 import { Matrix } from "pixi.js"; const uniforms = new UniformGroup({ uTransform: { value: new Matrix(), type: "mat3x3<f32>" }, uProjection: { value: new Float32Array(16), type: "mat4x4<f32>" }, uRotation: { value: new Float32Array(4), type: "mat2x2<f32>" }, });注意:PixiJS 的 2D 仿射变换矩阵Matrix对应mat3x3<f32>;3D 投影矩阵请使用裸Float32Array(16)+mat4x4<f32>。另外,即便声明了value之外不传值,UniformGroup也会按类型自动补默认值(getDefaultUniformValue,见 UniformGroup.ts)。
自定义 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; // 每个顶点的 float 数 public geometry: Geometry; public shader: Shader; constructor(options: BatcherOptions) { super(options); // 初始化 geometry 和 shader } public packAttributes( element: BatchableMeshElement, float32View: Float32Array, uint32View: Uint32Array, index: number, textureId: number, ): void { // 把 mesh 顶点属性打包进合批缓冲区 } public packQuadAttributes( element: BatchableQuadElement, float32View: Float32Array, uint32View: Uint32Array, index: number, textureId: number, ): void { // 把 quad 顶点属性打包进合批缓冲区 } } extensions.add(MyBatcher);要素说明:
- 扩展通过静态
extension属性声明type: [ExtensionType.Batcher]与唯一name; vertexSize决定每个顶点的浮点数量,与你的顶点格式匹配;- 元素通过
batcherName引用该合批器; - 要实现
BatchableElement接口,需提供batcherName、texture、blendMode、indexSize、attributeSize、topology、packAsQuad等字段。
仓库中可直接运行的自定义着色器/滤镜示例还包括 filters_custom-shader_glsl/index.ts、mesh_multipass_shader_effects/index.ts 与 text_filters_cartoon/CartoonTextFilter.ts,可作为扩展阅读。
常见错误
[严重] 沿用 v7 的位置参数构造 Shader
错误写法:
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的 options 对象,位置参数 API 已移除。
[严重] UniformGroup 缺少类型标注
错误写法:
new UniformGroup({ uTime: 1 });正确写法:
new UniformGroup({ uTime: { value: 1, type: "f32" } });每个 uniform 都必须提供显式的{ value, type }对。省略 type 会在运行时抛出Uniform type undefined is not supported(该校验位于 UniformGroup.ts)。
[高] UBO 使用了不支持的类型或结构不匹配
UBO 模式只支持f32/i32系类型(标量与向量),u32不在支持列表会抛错;矩阵仅限浮点(mat*<f32>);采样器不能放入 UBO。此外,UBO 的块名、字段名与顺序必须与着色器声明完全一致,否则渲染结果会错乱且不会报错。
[高] 把纹理放进 UniformGroup
错误写法:
new UniformGroup({ uTexture: { value: texture, type: "f32" }, });正确写法:纹理作为顶级资源传入source与style,uniform 单独放一组:
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)。
关联技能与 API 参考
- 相关技能文档:pixijs-filters(内置滤镜)、pixijs-scene-mesh(自定义几何体)、pixijs-performance(合批优化)、pixijs-migration-v8(从 v7 迁移着色器 API)。
- 核心 API:
Shader、GlProgram、GpuProgram、UniformGroup、Filter、Batcher、BatcherPipe,源码入口分别为 Shader.ts、GlProgram.ts、GpuProgram.ts、UniformGroup.ts、Filter.ts 与 Batcher.ts。
【免费下载链接】pixijsThe HTML5 Creation Engine: Create beautiful digital content with the fastest, most flexible 2D WebGL renderer.项目地址: https://gitcode.com/gh_mirrors/pi/pixijs
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考