p5.jsのFramebufferを使ってみる

2024/12/14に公開

最初に

こんにちは。独楽回しeddyです。 Processing Advent Calendar 2024の14日目を担当させていただきます。
今回のテーマはフレームバッファです。

Processing Advent Calendar 2024 : https://adventar.org/calendars/9929

フレームバッファについて

フレームバッファは画面で表示する描画の内容を一時的に保持するためのものです。
これを利用することで描画した結果に対して後からシェーダーを使って加工をしたり、画像として利用することで背景として表示したり、立方体などの3D物体にマッピングしたりなど様々な表現が出来るようになります。

p5.jsのp5.Graphicsについて

p5.jsで描画結果を一時的に保持する方法としてはp5.Graphicsがあります。
p5.Graphicsは所謂createGraphicsを使った方法で、これを使って別で描画結果を作っておいてから画像の形で描画をする...みたいな事ができます。

// createGraphicsを使う書き方
let graphics;  // createGraphicsで生成した描画領域を格納する変数
function setup(){
// 一度だけ呼ばれる
    createCanvas(600, 600);  // 600×600 の画面を作る
    background(0);  // 黒背景
    
    graphics = createGraphics(width, height);  // 600×600 の描画領域を作る
}

function draw(){
// 何度も呼ばれる(1秒につき30 ~ 60回)
    background(0);  // 黒背景
    
    // メモリ上に確保した描画領域に対して描きたい内容を反映する
    graphics.clear();        // 描画内容を初期化する
    graphics.background(0);  // 黒背景
    graphics.fill(255);      // 白色に塗りつぶす
    graphics.noStroke();     // 線は書かない
    graphics.ellipse(width/2, height/2, 100, 100)  // 中央に100×100の円を描く
    
    // 描画領域を実際に表示する
    imageMode(CENTER);  // 画像配置の基準を中央する
    image(graphics, width/2, height/2);  // 描画領域を実際に表示する
}

p5.Graphicsは2D、WebGLの両方のモードで使用が可能で、これをシェーダー側に送って加工した結果を出力するという方法もあります。(以前にそのやり方については記事にしております。もし良ければ)

この方法はフレームバッファを簡単に扱えるような作りになっており、難しい設定があまりなくて使いやすいのが利点です。また、WebGLを使った操作も可能ではあります。ただし、内部の作り的にはCanvas2D側に寄ったものとなっているためシェーダーを使った加工(ポストエフェクト)とかにはパフォーマンス的に不向きとの事でした。(チュートリアル記事より)

p5.Framebufferについて

p5.jsは日々アップデートが行われており、v1.7.0以降にp5.Framebufferというものが追加されました。こちらが今回の主題となります。
p5.Framebufferはその名の通りフレームバッファを作るためのものとなります。先ほど説明したp5.Graphicsと出来ること自体は似ていますが、こちらは内部の作り的にはWebGLに特化しております。そのためシェーダーを使って複雑なことをやりたい場合にはより向いている方法と言えます。

使い方

下記にp5.Framebufferを使った描画結果を出す上でのシンプルな例を記述します。

// createFramebufferを使う書き方
let frameBuffer;   // createFramebufferで生成した描画領域を格納する変数

function setup() {
    createCanvas(800, 800, WEBGL);  // createFramebufferを使う場合はWEBGLモードでなければ動作しない
    background(0);

    frameBuffer = createFramebuffer();  // 引数なしで自動でメインのCanvasのサイズで作ってくれる。(createCanvasで800×800で指定しているので800×800になる)

}

function draw() {
    background(0);

    frameBuffer.begin();  // フレームバッファへの書き込み開始(以降endまで)
    clear();
    noFill();
    stroke(255);
    for(let i = 1; i <= 20; i++)
    {
        push();
        rotateX(i*0.04+frameCount*0.01);
        rotateY(i*0.08+frameCount*0.02);
        let boxSize = 300 - i*10;
        box(boxSize, boxSize, boxSize);
        pop();
    }
    frameBuffer.end();  // フレームバッファへの書き込み終了

    imageMode(CENTER);
    image(frameBuffer, 0, 0);  // フレームバッファの内容を画像として描画する
}

注意点としては、createFramebufferを使うにはメインとなるCanvasの方でWEBGLモードを指定しなければならないところです。そうしないとエラーになって動作しないです。
また、WEBGLモードの指定により座標系もいつもとは異なりますのでそこも注意が必要です。(通常だと画面左上が原点になりますが、WEBGLモードだと画面中央が原点になります)
フレームバッファへの書き込むには、生成したFramebufferでbegin()end()の間に描画したい内容の処理を挟みます。
その後単純のその描画結果を表示したい場合はp5.Graphicsと同様にimageを呼ぶことでできます。

単純な使い方ならこれでも十分ですが、createFramebufferは引数の指定が色々出来ます。(参考)使いこなせればより複雑なことが出来る一方、WebGLの基礎部分の知識もより必要になります。

活用例と解説

今回の一例として、p5.Framebufferで描画したものにシェーダーでの加工を試みてみます。

使い方の説明の方で描画した内容について、左半分の色を反転するシェーダーをかけた結果になります。
コードは下記になります。

let frameBuffer;
let filterShader;

// シェーダーコード
let fs = `
#ifdef GL_ES
precision mediump float;
#endif

varying vec2 vTexCoord;
uniform sampler2D tex0;  // フィルタリング対象の描画内容

void main(){
    vec2 uv = vTexCoord;
    vec4 texColor = texture2D(tex0, uv);
    vec4 color = vec4(mix(texColor.rgb, (1.0 - texColor.rgb), step(0.5, uv.x)), 1.0);  // 右半分は反転する
    gl_FragColor = color;
}
`;

function setup() {
	createCanvas(800, 800, WEBGL);
	background(0);

	frameBuffer = createFramebuffer();
	filterShader = createFilterShader(fs); // フィルタリングシェーダー
}

function draw() {
	background(0);

	frameBuffer.begin();
	clear();
	noFill();
	stroke(255);
	for(let i = 1; i <= 20; i++)
	{
		push();
		rotateX(i*0.04+frameCount*0.01);
		rotateY(i*0.08+frameCount*0.02);
		let boxSize = 300 - i*10;
		box(boxSize, boxSize, boxSize);
		pop();
	}
	frameBuffer.end();

	imageMode(CENTER);
	image(frameBuffer, 0, 0);

	filter(filterShader);  // フィルタリングシェーダーをかける
}

createFilterShaderfilterで使うためのシェーダーオブジェクトを作成します。imageでフレームバッファの内容を描画した後にfilterを使うことでFramebufferに描画した内容に対してシェーダーをかけることができます。

まとめ

今回はp5.jsでのフレームバッファを扱うための方法について記載させていただきました。
自分自身p5.Framebufferが実装されたことに気づいたのが最近で、これをきっかけに少し知り始めました。
p5.FramebufferはよりWebGLに近い内容で、使いこなせれば今までより複雑な表現が出来たりと利点も多そうです。
ただし使いこなすにはよりWebGL周りの知識も必要になりそうですし、簡単な内容であればp5.Graphicsで十分事足りる場合もありそうですのでその辺りは使い分けていきたいです。

今回の解説はあくまで導入部分で簡単な例でのものになりましたが、多少なりとも参考になれば幸いです。
ここまで読んでくださりありがとうございました!

参照

Discussion