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 リファレンス。