本文へスキップ

spryte でポストプロセス ​

画面エフェクトのほとんどは、2 つの機能があれば作れます。レンダーターゲット(オフスクリーン画像に描く)とカスタムシェーダー(ピクセルの出方を変える)です。spryte はどちらも、イミディエイトモードらしいスタイルで備えています。

レンダーターゲット ​

ts
const scene = draw.createTarget(320, 180); // ピクセル

function frame() {
  draw.begin("#000", 320, 180, scale);

  draw.into(scene, () => {
    // 内側では単位はターゲットのピクセル。変形、クリップ、アルファはリセットされた状態から始まる
    drawWorld(draw);
  }, "#102030");                            // クリア色。省略すると透明、null なら中身を残す

  draw.sprite(scene, 0, 0, { origin: [0, 0] }); // 普通の画像と同じように描ける
  draw.end();
}
  • into() は、関数が例外を投げた場合でも、終わったあとに外側の変形、クリップ、アルファ、描画先を元に戻します。入れ子にもできるので、ターゲットを別のターゲットに描くこともできます。
  • ブレンドモードと有効なシェーダーはターゲットの中に引き継がれ、関数が終わったときの状態のまま残ります。begin() は毎フレーム両方をリセットします。
  • クリア色に null を渡すと、すでにある内容の上に描きます。残像やモーションブラーはこうして積み重ねます。
  • ターゲットを自分自身に描こうとすると例外になります。GPU の上限(MAX_TEXTURE_SIZE)を超える大きさのターゲットも同じです。サイズは整数ピクセルです。
  • 不要になったターゲットは draw.disposeTarget(t) で解放しましょう。GPU コンテキストが失われたあと、ターゲットは空の状態で戻ってきます。

カスタムシェーダー ​

シェーダーはフラグメントエフェクトです。ピクセルの色を返す effect() を書きます。source(uv) は描いている画像をサンプリングします。

ts
const crt = draw.createShader(`
  uniform float time;
  vec4 effect(vec4 color, vec2 uv) {
    float scan = 0.85 + 0.15 * sin(uv.y * 900.0);
    vec2 wobble = vec2(sin(uv.y * 30.0 + time * 4.0) * 0.0015, 0.0);
    return source(uv + wobble) * color * vec4(vec3(scan), 1.0);
  }
`);

crt.set("time", t);
draw.withShader(crt, () => draw.sprite(scene, 0, 0, { origin: [0, 0] }));
  • color はその描画の色合いとアルファで、**乗算済み(premultiplied)**です。返す色も乗算済み(rgb にアルファを掛けたもの)にしてください。source(uv) * color ならそのまま正しく動きます。
  • uniform は float かベクトルです(set("glow", [1, 0.8, 0.3]))。set() はそれ以降の描画にだけ効くので、同じスコープ内の 2 つの描画の間で uniform を変えられます。
  • シェーダーは初めて使うときにコンパイルされます。タイプミスがあるとドライバーのメッセージ付きで例外が投げられ、行番号はあなたのソースの行を指します。コンパイルに失敗したシェーダーは、レンダラーの状態を一切変えません。
  • シェーダーは WebGL2 専用です。Canvas 2D フォールバックでは、withShader の中の描画はエフェクトなしで行われ、コンソールに警告が 1 回出ます。

組み合わせる:グロー ​

明るいものだけを小さなターゲットに描き、それを拡大して加算ブレンドで上に重ねます。

ts
const glow = draw.createTarget(160, 90); // 半分の解像度:安くて、自然にやわらかくなる

draw.into(glow, () => {
  draw.scale(0.5);
  draw.sprites(spark, sparks, live, { tint: "#ffd36b" });
});

drawWorld(draw);
draw.blend("add");
draw.sprite(glow, 0, 0, { origin: [0, 0], scaleX: 2, scaleY: 2, smooth: true });
draw.blend("normal");

拡大時に smooth: true を付けると、半分の解像度のグローがタダでぼけます。もっと強くぼかしたいなら、ターゲットをブラーシェーダーに通して描きましょう。

実際の GPU で確認済み ​

spryte のブラウザテストでは、シーンを直接描いた場合とターゲット経由で描いた場合を、全ピクセル比較しています(どちらのバックエンドでも一致します)。ほかにも、何もしないシェーダーが何も変えないこと、uniform が順番どおりに適用されること、コンパイルエラーがあなたの行を指すことも確かめています。これらは spryte 0.1 で入った新機能なので、おかしな挙動に出会ったらぜひ issue を立ててください。

あわせてどうぞ:spryte を単体で使う、API リファレンス。