mcpField
P
pixijs-custom-rendering

pixijs-custom-rendering

github.com/pixijs开发3585.9k 次安装4 周前更新
代码仓库

关于 pixijs-custom-rendering

面向 PixiJS v8 开发者的自定义渲染技能,介绍着色器、UniformGroup、滤镜与 GPU 渲染模式。涵盖 WebGL 和 WebGPU 实践及常见配置问题。

主要功能

  1. 01编写 GLSL 与 WGSL 着色器
  2. 02配置 UniformGroup 与纹理资源
  3. 03创建自定义滤镜
  4. 04使用 UBO 管理 uniform
  5. 05自定义批处理与 GPU 功能

适用场景

  1. 01为场景对象编写自定义着色器
  2. 02实现片元着色器滤镜效果
  3. 03在 WebGL 与 WebGPU 间适配渲染
  4. 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,它会缓存结果,且仅在缓冲区或属性发生变化时重新计算。

API 参考

评论

支持 Markdown