0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

p5.jsでフレームバッファを扱う際のカメラに関する注意

Posted at

はじめに

 p5.jsにフレームバッファが実装されました。誰も使っていません。それはいいんですが、カメラに関して注意すべきポイントがあるので触れておきます。AdventCalenderでもフレームバッファの記事が出たようですが、この辺に関して全く言及していなかったので、補足の意味も込められています。
 ざっくりいうと、カメラの向きが逆になっています。というか、射影行列の例の-1がフレームバッファの場合除去されているようです。なんだってぇ!?

フレームバッファのカメラ

 p5.Framebuffer
 フレームバッファにはカメラを作る関数が付随しています。
 createCamera()
これを使ってカメラを作ってフレームバッファ内で使うことを考えます。

let fbo;
let cam;

function setup() {
  createCanvas(400, 400, WEBGL);
  fbo = createFramebuffer();
  cam = fbo.createCamera();
  //cam = createCamera();
  noStroke();

  const gl = this._renderer.GL;
  //gl.enable(gl.CULL_FACE);
  //gl.cullFace(gl.BACK);
}

function draw() {
  fbo.begin();
  setCamera(cam);
  background(128);
  lights();
  fill(255);
  translate(0,80,0);
  rotateX(PI/3);
  torus(80,40,24);
  fbo.end();
  clear();
  image(fbo.color,-width/2,-height/2);
}

fbocamera0.png

 このように下方向にトーラスが描画されます。なお、fboの外ではデフォルトのカメラが使われるので、内部でのトランスフォームはリセットされ、普通にimage関数を用いることができます。
 次に、このfboのカメラを使って通常描画をします。

let fbo;
let cam;

function setup() {
  createCanvas(400, 400, WEBGL);
  fbo = createFramebuffer();
  cam = fbo.createCamera();
  //cam = createCamera();
  noStroke();

  const gl = this._renderer.GL;
  //gl.enable(gl.CULL_FACE);
  //gl.cullFace(gl.BACK);
}

function draw() {
  //fbo.begin();
  setCamera(cam);
  background(128);
  lights();
  fill(255);
  translate(0,80,0);
  rotateX(PI/3);
  torus(80,40,24);
  //fbo.end();
  //clear();
  //image(fbo.color,-width/2,-height/2);
}

fbocamera1.png

逆方向に描画されてしまいました。fbo用のカメラを使ったせいで罰が当たったのでしょうか。それはいいとして、普通のカメラではないことが分かりました。
 ちなみに、カリングの向きも逆になっています。p5は射影行列をいじることでy軸を逆向きにしているのですが、それがどうやら修正されているようです。ゆえに、通常はフロントカリングで消えない仕組みになっているのですが、この状態では、フロントカリングをすると消えてしまいます:

let fbo;
let cam;

function setup() {
  createCanvas(400, 400, WEBGL);
  fbo = createFramebuffer();
  cam = fbo.createCamera();
  //cam = createCamera();
  noStroke();

  const gl = this._renderer.GL;
  gl.enable(gl.CULL_FACE);
  gl.cullFace(gl.FRONT);
}

function draw() {
  //fbo.begin();
  setCamera(cam);
  background(128);
  lights();
  fill(255);
  translate(0,80,0);
  rotateX(PI/3);
  torus(80,40,24);
  //fbo.end();
  //clear();
  //image(fbo.color,-width/2,-height/2);
}

fbocamera2.png

fboのカメラではなく通常のカメラを用いれば、消えませんが、やはりy軸は下向きになります。

let fbo;
let cam;

function setup() {
  createCanvas(400, 400, WEBGL);
  fbo = createFramebuffer();
  //cam = fbo.createCamera();
  cam = createCamera();
  noStroke();

  const gl = this._renderer.GL;
  gl.enable(gl.CULL_FACE);
  gl.cullFace(gl.FRONT);
}

function draw() {
  //fbo.begin();
  setCamera(cam);
  background(128);
  lights();
  fill(255);
  translate(0,80,0);
  rotateX(PI/3);
  torus(80,40,24);
  //fbo.end();
  //clear();
  //image(fbo.color,-width/2,-height/2);
}

fbocamera3.png

つまりfbo用のカメラはカリングが逆向きになっているようです。そして外で使うとy軸が逆向きになります。ところで、内部で使ってもカリングは逆向きになっているのか?それを確かめるには、最初のようにimageでfboの結果を焼くことにしたうえで、フロントカリングをしてみればいいです。

let fbo;
let cam;

function setup() {
  createCanvas(400, 400, WEBGL);
  fbo = createFramebuffer();
  cam = fbo.createCamera();
  //cam = createCamera();
  noStroke();

  const gl = this._renderer.GL;
  gl.enable(gl.CULL_FACE);
  gl.cullFace(gl.FRONT);
}

function draw() {
  fbo.begin();
  setCamera(cam);
  background(128);
  lights();
  fill(255);
  translate(0,80,0);
  rotateX(PI/3);
  torus(80,40,24);
  fbo.end();
  clear();
  image(fbo.color,-width/2,-height/2);
}

fbocamera4.png

消えました!どうやらカリングが逆になっているようです。じゃあバックカリングなら消えないのか?ええ。しかし画像は消えてしまいます:

let fbo;
let cam;

function setup() {
  createCanvas(400, 400, WEBGL);
  fbo = createFramebuffer();
  cam = fbo.createCamera();
  //cam = createCamera();
  noStroke();

  const gl = this._renderer.GL;
  gl.enable(gl.CULL_FACE);
  gl.cullFace(gl.BACK);
}

function draw() {
  fbo.begin();
  setCamera(cam);
  background(128);
  lights();
  fill(255);
  translate(0,80,0);
  rotateX(PI/3);
  torus(80,40,24);
  fbo.end();
  clear();
  image(fbo.color,-width/2,-height/2);
}

fbocamera5.png

なぜか?先ほども述べた通り、外ではデフォルトカメラが機能します。デフォルトカメラはバック描画なので、バックカリングをすると消えてしまうわけです。fbo上にはちゃんとトーラスが焼かれています。それを確かめるにはy軸に関して180°回してみればわかります。

let fbo;
let cam;

function setup() {
  createCanvas(400, 400, WEBGL);
  fbo = createFramebuffer();
  cam = fbo.createCamera();
  //cam = createCamera();
  noStroke();

  const gl = this._renderer.GL;
  gl.enable(gl.CULL_FACE);
  gl.cullFace(gl.BACK);
}

function draw() {
  fbo.begin();
  setCamera(cam);
  background(128);
  lights();
  fill(255);
  translate(0,80,0);
  rotateX(PI/3);
  torus(80,40,24);
  fbo.end();
  clear();

  rotateY(PI);
  image(fbo.color,-width/2,-height/2);
}

fbocamera6.png

 ほらね。

 そういうわけで、中と外で異なるカメラを用いているがために、このような事態が発生します。なので、カリングを利用する際には、いちいちカリングを切り替える必要があります。
 orbitControl()の挙動についても触れておきます。次のようなコードを書くと、カメラと一緒に画像が動いてしまいます。

let fbo;
let cam;

function setup() {
  createCanvas(400, 400, WEBGL);
  fbo = createFramebuffer();
  cam = fbo.createCamera();
  //cam = createCamera();
  noStroke();

  const gl = this._renderer.GL;
  //gl.enable(gl.CULL_FACE);
  //gl.cullFace(gl.FRONT);
  //const defaultCam = createCamera();
}

function draw() {
  fbo.begin();
  setCamera(cam);
  orbitControl();
  background(128);
  lights();
  fill(255);
  translate(0,80,0);
  rotateX(PI/3);
  torus(80,40,24);
  fbo.end();
  clear();

  image(fbo.color,-width/2,-height/2);
}

fbocamera7.png

どうやらfboのカメラを生成するタイミングで、描画に使うカメラも同じカメラになってしまうらしい...です?そこら辺の仕様は詳しくないので分かりませんが、内部でカメラをいじると外側にも影響が出てしまうあたり、どうやらそのようです。カリングの方向は逆なのにorbitControl()でどっちも動いてしまうのは、おそらく射影行列の方はいじられていないというのが理由のようですが、詳しくはハッキングしてみないと何とも言えないです(やりませんが)。これを回避するにはいろんな方法がありますが、手っ取り早いのはデフォルトのカメラをダミーで生成することです。これで外ではデフォルトのカメラが使われるようになるようです。

let fbo;
let cam;

function setup() {
  createCanvas(400, 400, WEBGL);
  fbo = createFramebuffer();
  cam = fbo.createCamera();
  //cam = createCamera();
  noStroke();

  const gl = this._renderer.GL;
  //gl.enable(gl.CULL_FACE);
  //gl.cullFace(gl.FRONT);
  const defaultCam = createCamera();
}

function draw() {
  fbo.begin();
  setCamera(cam);
  orbitControl();
  background(128);
  lights();
  fill(255);
  translate(0,80,0);
  rotateX(PI/3);
  torus(80,40,24);
  fbo.end();
  clear();

  image(fbo.color,-width/2,-height/2);
}

fbocamera8.png

setupの最後の行でカメラを生成しています。これでimageの際に適用されるカメラが動かないようにできるようです。setupで生成したカメラがそのまま外での描画用として紐付けられ、fboのカメラと別扱いになるようです。
 この状態でフロントカリングするとどうなるのか?もちろんですが、トーラスは消えます(裏面は描画されるので動かせば現れます)。先ほども述べた通り、image()はバック描画なので消えませんが、トーラスはフロント描画だからです。

let fbo;
let cam;

function setup() {
  createCanvas(400, 400, WEBGL);
  fbo = createFramebuffer();
  cam = fbo.createCamera();
  //cam = createCamera();
  noStroke();

  const gl = this._renderer.GL;
  gl.enable(gl.CULL_FACE);
  gl.cullFace(gl.FRONT);
  const defaultCam = createCamera();
}

function draw() {
  fbo.begin();
  setCamera(cam);
  orbitControl();
  background(128);
  lights();
  fill(255);
  translate(0,80,0);
  rotateX(PI/3);
  torus(80,40,24);
  fbo.end();
  clear();

  image(fbo.color,-width/2,-height/2);
}

fbocamera9.png

おわりに

 なぜ中と外で違うカメラになっているかというと、テクスチャが絡む特殊事情によるものらしくて、正直よくわかんないです。実装した人が、その方が便利だろうと踏んだので、そういう仕様になったのでしょう。フレームバッファのサンプルを見ると分かりますが、ほぼすべてフレームバッファ内で描画した結果を外に出す形となっています。そういう使い方しか想定されていないわけです。カリングはどうでもいいということですね。

 余談ですが、自分の使ってる自作ライブラリではこんなめんどうなことはしていなくて、普通に外と中で共用しています。描画時にフェッチの際に$y$成分の上下を逆にすればいいだけの話ですから。

 以上です。

 ここまでお読みいただいてありがとうございました。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?