チュートリアル フレームバッファを使用したレイヤー描画

フレームバッファを使用したレイヤー描画

By Dave Pagurek, Adam Ferriss

スケッチの中で、画面に直接描画するのではなく、画像に描画したいことがあるかもしれません。これにより、その画像を3Dシェイプのテクスチャとして使用したり、複雑なレンダリング効果を実現したり、画像を効率的に複数回繰り返したりすることができます。

2Dモードでは、p5.Graphicsを使用してこれを実現できます。WebGLモードでも機能しますが、より高いパフォーマンスと柔軟性を得るためにp5.Framebufferを使用できます。

フレームバッファの特別な点は何か?

フレームバッファは、コンピューターのGPU(Graphics Processing Unit)上に存在します。GPUは、画像のピクセルを可能な限り高速に並列で描画することに特化したコンピューターの部分です。スケッチ用に書くJavaScriptは、コンピューターのCPU(Central Processing Unit)上で実行され、一度に1つのことしか行いません。フレームバッファはGPU上に存在するため、すでに大部分の計算を行っている場所で、CPUとGPU間で大量のデータを移動させることなく、フレームバッファへの書き込みと読み取りを迅速に行うことができます!

フレームバッファは、通常の画像やキャンバスよりも多くの情報を含むことができます。より高精度の浮動小数点数を使用して、色をより正確に保存し、丸め誤差による奇妙な視覚効果を回避できます。また、描画された内容の3D深度情報を保存することもでき、被写界深度や影などの視覚効果の作成に役立ちます。

遠くに消えていく球体の列を3つの方法で描画したもの。1つ目は焦点が合っていてカラー。2つ目は単色で、遠くに行くほど白くなる。3つ目はカラーで、遠くに行くほど球体がぼやけている。

遠くに消えていく球体の列を3つの方法で描画したもの。1つ目は焦点が合っていてカラー。2つ目は単色で、遠くに行くほど白くなる。3つ目はカラーで、遠くに行くほど球体がぼやけている。

フレームバッファの色

フレームバッファの深度

色+深度を使用した焦点ぼかしの最終画像

p5.Framebufferの使用

p5.Framebufferは、メインキャンバスと同様に描画できる表面です。メインキャンバスに描画することは、紙に描くようなものです。p5.Framebufferbegin()を呼び出すと、元の紙の上に新しい紙を置くようなもので、新しく描画されるものはすべてそこに集まります。p5.Framebufferend()を呼び出すと、その紙を再び取り除き、その後の描画は再びメインキャンバスに直接行われます。

createFramebuffer()関数を使用してp5.Framebufferを作成できます。オプションでオブジェクトをパラメータとして渡して、幅と高さを指定できます。デフォルトでは、p5.Framebufferはメインキャンバスと同じサイズです。色と深度情報の保存方法を制御するために、このオブジェクトに他のオプションを追加することもできます。これについては後で詳しく説明します。完全なリファレンスについては、createFramebuffer()のドキュメントをチェックしてください。

すでにp5.Graphicsオブジェクトへの描画に慣れているかもしれません。以下は、テクスチャとしてp5.Graphicsを使用するコードと、同等のp5.Framebufferコードの比較です:

p5.Graphicsの使用

let layer;
function setup() {
  createCanvas(200, 200, WEBGL);
  layer = createGraphics(200, 200);
  describe('各面に黄色の点がある回転する赤い立方体');
}
function draw() {
  let t = millis() * 0.001;
  layer.background('red');
  layer.noStroke();
  layer.fill('yellow');
  for (let i = 0; i < 30; i += 1) {
    layer.circle(
      map(noise(i*10, 0, t), 0, 1, 0, width),
      map(noise(i*10, 100, t), 0, 1, 0, height),
      20
    );
  }
 
  background(255);
  lights();
  noStroke();
  texture(layer);
  rotateX(t);
  rotateY(t);
  box(100);
}

p5.Framebufferの使用

let layer;
function setup() {
  createCanvas(200, 200, WEBGL);
  layer = createFramebuffer();
  describe('各面に黄色の点がある回転する赤い立方体');
}
function draw() {
  let t = millis() * 0.001;
  layer.begin();
  background('red');
  noStroke();
  fill('yellow');
  for (let i = 0; i < 30; i += 1) {
    circle(
      map(noise(i*10, 0, t), 0, 1, -width/2, width/2),
      map(noise(i*10, 100, t), 0, 1, -height/2, height/2),
      20
    );
  }
  layer.end();
 
  background(255);
  lights();
  noStroke();
  texture(layer);
  rotateX(t);
  rotateY(t);
  box(100);
}

結果

デフォルトでは、p5.Framebufferは画面に描画されません。draw()の終了時に表示されるのはメインキャンバスだけです。p5.Framebufferの内容を表示するには、メインキャンバスにスタンプする必要があります。これは通常、image(yourFramebuffer, x, y, width, height)を呼び出すことで行います。メインキャンバスと同様に、p5.Framebufferはクリアを要求したときにのみクリアされるので、以下の例のように、メインキャンバスに何度でもスタンプすることができます。

メインキャンバス

layer

let layer;
function setup() {
  createCanvas(windowWidth, windowHeight, WEBGL);
  layer = createFramebuffer();
}
function draw() {

最初は、描画されるものはすべてメインキャンバスに向かいます。右側で、現在描画されている表面を赤い枠線で示しています。

  // フレームバッファへの描画を開始
  layer.begin();

p5.Framebufferbegin()を呼び出すことで、これ以降に描画されるものはすべてメインキャンバスではなくそのレイヤーに向かいます。

  clear();
 
  lights();
  noStroke();
  rotateX(millis() * 0.001);
  rotateY(millis() * 0.001);
  box(min(width/2, height/2));

これはp5.Framebufferをクリアし、立方体を描画します。この後、メインキャンバスはまだ空白ですが、p5.Framebufferには立方体が入っています。

キャンバスを埋める立方体
  // フレームバッファへの描画を停止
  layer.end();

p5.Framebufferend()を呼び出すことで、これ以降に描画されるものはすべてメインキャンバスに向かいます。

キャンバスを埋める立方体
  // レイヤーをメインキャンバスに描画
  clear();
  translate(-width/2, -height/2);
  for (let x = 0; x < 4; x += 1) {
    for (let y = 0; y < 4; y += 1) {
      image(
        layer,
        x*width/4, y*height/4,
        width/4, height/4
      );
    }
  }
}

これはメインキャンバスに4x4のグリッドを描画し、各セルにlayer p5.Framebufferのコピーを描画します。

キャンバスを埋める4x4のグリッドで繰り返される立方体の画像
キャンバスを埋める立方体
試してみよう!

p5.Framebufferを蜂の巣パターンで何度も描画することで、ハエの視覚をシミュレートできますか?

フィードバックを使用したスケッチング

ビデオフィードバックは、ビデオの前のフレームを次のフレームの描画に使用する技術です。これを使用して、無限に続くように見える視覚効果を生成できます。この効果は、2枚の鏡の間に立っているときのように見えます。

スケッチでフィードバックを使用するには、通常、p5.Framebufferレイヤーに描画して現在のフレームの画像を持ち、次のフレームを描画するときに前のフレームの画像を使用して描画に追加します。

これを行う最速の方法は、前のフレーム用と次のフレーム用の2つのレイヤーを保持することです。draw()の開始時に2つのレイヤーを交換します:以前は次のフレームだったものが今は前のフレームになり、次のフレームをクリアして新しい画像を描画する自由があります。コンピューターグラフィックスでは、この技術を「ピンポン」と呼びます。

試してみよう!

多くの音楽プレーヤーソフトウェアは、フィードバックを使用してオーディオビジュアライゼーションを作成しています。音楽に反応するスケッチでフィードバックを試してみてください!

より高度なフィードバック効果を作成する際に役立つヒントをいくつか紹介します。

フィードバックでのフェード:FLOAT精度を使用する

p5.Framebufferは、画像をより高精度の浮動小数点数として保存できるため、フィードバックに特に適しています。これはcreateFramebuffer({ format: FLOAT })で指定できます。通常、色の赤、緑、青の値は0〜255の整数として保存されます。画像を描画するたびに、色はこの範囲に丸められます。フィードバックスケッチでは、フレームが何度も描画され再描画されることがあります。これにより多くの丸め誤差が蓄積され、色が完全にフェードアウトしないことがあります。float精度を使用すると、この問題は解消されます!

3Dでのフィードバック:深度を忘れずに

上の例では、image(prev, 0, 0)を使用して前のフレームを次のフレームに描画しました。これは画面上に長方形を描画し、3D空間を占めます。その後、カメラからより遠くに何かを描画しようとしても、長方形がそれを遮っているため表示されません。

push();
translate(0, 0, -500);
// カメラがz1単位離れていて、
// z2単位後ろに移動した場合、
// 移動していないのと同じサイズに見えるように、
// (z2 + z1) / z1 でスケーリングします
scale((500 + 800) / 800);
image(prev, 0, 0);
pop();

他のすべての描画物が長方形の前に来るようにする一つの方法は、長方形をより後ろに描画しつつ、同じサイズに見えるようにスケールアップすることです。

image(prev, 0, 0);
clearDepth();

もう一つの方法は、clearDepth()を呼び出して、すべてのものがどの深度に描画されたかのメモリをクリアすることです。

深度を使用したスケッチング

p5.Framebufferに描画する際、3D空間での幾何学的深度も記録でき、0から1の間の数値として保存されます。この情報は、p5.Framebufferdepthプロパティを使用してシェーダーに読み込むことができます。深度情報はデバッグにも使用でき、オブジェクトがどれだけ遠くにあるかを素早く把握できます。以下の例では、キャンバス上でクリックして押し続けると、深度値を視覚化できます。

深度情報は、カメラからの距離に基づいて画像を変更したい場合に役立ちます。これを使用する一般的な効果の一つは霧で、遠くに配置されたオブジェクトが霧の色で着色されます。

フレームバッファのcolordepthの両方のプロパティをシェーダーに渡すと、元の色はtexture2D(color, vTexCoord)を通じてvec4として読み取ることができます。同じ座標を使用して、そのピクセルの深度はtexture2D(depth, vTexCoord).rを使用して読み取ることができ、赤チャンネルのみを読み取って単一のfloatを取得します。

オブジェクトがカメラにどれだけ近いか遠いかで深度値が0または1になるかをカスタマイズしたい場合は、perspective()でnearとfarの値を指定してください。

深度ベースのぼかしシェーダーはより高度で、このチュートリアルの範囲を超えています。

試してみよう!

霧シェーダーを使用して雰囲気を出す、ムーディーで雨の降るシーンを作成できますか?

まとめ

WebGLモードでスケッチを行い、画像に描画する必要がある場合は、createFramebuffer()の使用を検討してください。これにより、スケッチの実行速度が向上し、可能な限り最高の視聴体験を提供できます。

フレームバッファが可能にする新しいテクニックがあなたにインスピレーションを与え、それらを使用してアートを創作することを願っています!

用語集

GPU

GPU(Graphics Processing Unit)は、多くの計算を並列で実行するのに特に適したハードウェアで、3Dグラフィックスに強力な性能を発揮します。

フレームバッファ

p5.Graphicsと同様に描画可能な表面ですが、WebGLのハードウェアアクセラレーションを活用するためにGPU上に存在します。

深度

カメラからの形状の距離を測定したもの。フレームバッファは、その画像の各ピクセルについてこの測定値を保存します。

ピンポン

コンピューターグラフィックスにおいて、前のフレームを参照しながら次のフレームを描画する必要がある場合のプログラミングパターン。これは、どのフレームバッファが前のフレームを指すかを毎フレーム切り替え、残りのフレームバッファに次のフレームを描画することで実現されます。