mcpField
P
pixijs-custom-rendering

pixijs-custom-rendering

github.com/pixijs開発3585.9k 回インストール4 週間前に更新
リポジトリ

pixijs-custom-rendering について

PixiJS v8でカスタムレンダリングを行う開発者向けのスキルです。シェーダー、UniformGroup、フィルター、WebGLとWebGPUの実装パターンや設定上の注意点を扱います。

主な機能

  1. 01GLSLとWGSLのシェーダー作成
  2. 02UniformGroupとテクスチャリソースの設定
  3. 03カスタムフィルターの作成
  4. 04UBOによるuniform管理
  5. 05カスタムバッチ処理とGPU機能

活用シーン

  1. 01シーンオブジェクト用のシェーダー作成
  2. 02フラグメントシェーダーによるフィルター実装
  3. 03WebGLとWebGPUへのレンダリング対応
  4. 04uniform、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 のプログラムをシーンオブジェクトにバインドします。ユニフォームは型付きの 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 では、attribute/varying の代わりに in/out を、texture2D() の代わりに texture() を、gl_FragColor の代わりに out vec4 を使用してください。

リソースとしてのテクスチャ

テクスチャはユニフォームではなく、リソースです。テクスチャの 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;

リソースはフラットなキーと値のマップです。キーはシェーダーソース内のユニフォーム名/バインディング名と一致する必要があります。

リソースにはプレーンオブジェクトも指定できます(自動的に UniformGroup にラップされます)。

const shader = Shader.from({
  gl: { vertex: vertSrc, fragment: fragSrc },
  resources: {
    myUniforms: {
      uTime: { value: 0, type: "f32" },
    },
  },
});
UBO モード(ユニフォームバッファーオブジェクト)

UBO モードでは、ユニフォームを単一の 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)
  • varying vec2 vTextureCoord; の代わりに in vec2 vTextureCoord; を使用します。
  • gl_FragColor の代わりに out vec4 finalColor; を使用します。
  • texture2D(uTexture, uv) の代わりに texture(uTexture, uv) を使用します。
  • デフォルトの頂点シェーダーでは、uInputSize、uOutputFrame、uOutputTexture と、ヘルパー関数 filterVertexPosition()/filterTextureCoord() を利用できます。
フィルターの背後にあるレンダーターゲットのサンプリング

blendRequired: true を設定し、フラグメントシェーダーで uBackTexture をサンプリングします。PixiJS はフィルターを実行する前に、描画先のピクセルをこのユニフォームにコピーします。

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

必要な場合にのみ blendRequired を有効にしてください。有効にすると、毎フレーム追加の GPU コピーが発生します。

実行時のユニフォーム更新
// 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 }) は1つのスライスまたはレイヤーに描画します。
  • ストレージテクスチャ(WebGPU):TextureSource に storage: true を指定し、renderer.texture.getGpuSource(source).createView() を使って独自のコンピュートパスから書き込みます。
  • レンダーバンドル(WebGPU):encoder.beginBundle()/endBundle() で一度だけ描画を記録し、isBundleValid() が有効な間、executeBundle() で再実行します。
  • プールされたスクラッチテクスチャ:TexturePool.getOptimalTexture({ width, height, resolution, antialias }) と returnTexture()。
ユニフォーム型のリファレンス

サポートされている型、対応する WGSL/GLSL 型、値の形式をまとめた表については、references/uniform-types.md を参照してください。

カスタムバッチャー(拡張機能ベース)

抽象クラス 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 でバッチャーを参照します。BatchableElement インターフェースには、batcherName、texture、blendMode、indexSize、attributeSize、topology、packAsQuad が必要です。

InstructionSet ごとに状態をキャッシュするカスタム InstructionPipe(BatcherPipe がバッチャーを保持する方法と同様)は、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" } });

すべてのユニフォームには、明示的な { 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" },
    }),
  },
});

テクスチャはユニフォームではなく、リソースです。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() の読み取り

geometry.getSize() は 8.20.0 以降非推奨で、警告が出力されます。キャッシュされ、バッファーまたは属性の変更時にのみ再計算される geometry.vertexCount を使用してください。

API リファレンス

コメント