もうちょっとだけp5.jsのWebGLモードについて調べてみた


2017年12月27日
With
もうちょっとだけp5.jsのWebGLモードについて調べてみた はコメントを受け付けていません。

前回のエントリで、p5.js WebGLモードでのスケッチをいくつか書いてみたのですが、

・年の瀬にp5.jsのWebGLモードを復習してみた

何となく腑に落ちない部分(特にfill()の挙動とか)があったので、もうちょっとだけ調べてみました。で、「p5.js Web GL mode」でググってみたら、GitHubのWikiにこんなページが。

・Getting started with WebGL in p5 ・ processing/p5.js Wiki ・ GitHub
・WebGL Module Architecture ・ processing/p5.js Wiki ・ GitHub

……もうちょっと早く見つけたかった(笑)。で、流し読みしてみたところ

・WebGLモードでは、一部の2D描画用の関数も使える
・WebGLモードで2D描画関数を使う場合、原点は左上じゃなくて中央になる
・以前、p5.jsには「basicMaterial()」というライトに影響を受けないジオメトリ描画関数があったが「fill()」が同じ役割を果たすので省略された

みたいなことが書いてありました。正直、レンダラーやシェーダーの細かい部分には踏み込む勇気がないのですが、とりあえず

・p5.js WebGLモードでは、3Dと2Dのオブジェクトを混在できて、同じように動かせる

ことが判明したので、検証のためにスケッチを描いてみました。

●p5.js WebGLモードテスト(クリックすると別タブが開きます。Win版Chrome/Edgeで確認しています。FireFoxだと表示がちょっと変です)

起動すると、上の画像の一番左の状態でアニメーションが始まります。原点は画面中央。真ん中の球は「sphere()」で、3つある大小の立方体は「box()」で、その周囲を回っているペラペラの3角形は「triangle()」でそれぞれ描いています。

triangle()では、2D描画のときと同じように3点のx座標、y座標を順に指定しています。3Dだからといってz座標を追加するというわけではなさそうです。ちなみに、先ほどのWikiでは、WebGLモードでの2D描画の例として「triangle()」や「quad()」でz座標を指定しているコードがあるのですが、9つ(3×3)の引数を入れて試してみたところ6つ目までしか反映されないようでした(もしかしたら古いバージョン向けのコードなのかも)。

実際の描画ですが、「z=0のxy平面に対して平行に置かれたデフォルトのカメラで見た場合、各点がcanvasの指定座標にくる大きさの3角形」が描かれています(複雑w)。z座標を変えたい場合や傾きを変えたい場合にはtranslate()やrotate()を使う感じなのでしょうね。とりあえずrotateを使ってグルグル回してみました。このあたりは、p5.jsでのいつものお作法です。原点の位置がずれて、軸の本数が増えるので多少混乱はしますけれども(^^; 。

fill()は2D図形の混在時や開発時に使うとよさそう?

で、前のエントリで腑に落ちなかった「fill()」の挙動について、何となく使い方がイメージできてきました。

今回のスケッチでは、起動時にライティングが反映された通常の3Dシェーダーを使った表示、1回クリックすると「stroke」「noFill」を反映したワイヤーフレーム表示、さらにクリックすると「stroke」「fill」を指定した表示になります。以降は、クリックごとにワイヤーフレームとベタ塗り表示のトグルです。一度「noFill()」を使った後に、ライティングを再度反映させる方法は分かりませんでした。

※補遺(2017/12/30):「一度noFill()を使った後に、ライティングを再度反映させる方法」が、その後分かりました。一度「fill(0);」などと書いて、fillのジオメトリを復活させたあとで、改めてライトとマテリアルを双方とも指定し直せば良いようです。

●p5.js WebGLモードテスト修正版(クリックすると別タブが開きます。PC版Chromeでの閲覧を推奨)

修正版のコードも、一応最後に追加しておきます。

各描画を見比べてみると、それぞれに味があるので表現の意図に合った指定をするのが良さそうですね。

あと、絵を作っている段階では「stroke」「fill」を指定して作業を進め、仕上げの段階でマテリアルとライトを付加し、必要であれば「noStroke」を指定して「fill」の指定を消すといった処置をする……というプロセスもアリかと。

大変汚いコードですがご参考までに。

※修正前のコード

let frameFlag = false;

function setup() {
	createCanvas(600, 600, WEBGL);
	ambientLight(150, 150, 190);
	directionalLight(110, 110, 150, 255, -1, 1, 0);
	ambientMaterial(150, 150, 255, 200);
	strokeWeight(3);
	noStroke();
}

function draw() {
	background(20, 20, 45);
	drawObject();
}

function drawObject() {
	//draw 3d boxes and sphere
	push();
	rotateX(frameCount / 100);
	rotateY(frameCount / 100);
	rotateZ(frameCount / 100);
	box(300);
	pop();
	push();
	rotateX(-frameCount / 50);
	rotateY(frameCount / 100);
	rotateZ(frameCount / 100);
	box(200);
	pop();
	push();
	rotateX(frameCount / 50);
	rotateY(-frameCount / 100);
	rotateZ(-frameCount / 50);
	box(100);
	pop();
	push();
	rotateX(-frameCount / 100);
	rotateY(frameCount / 50);
	rotateZ(-frameCount / 100);
	sphere(30);
	pop();

	//draw 2d triangles
	push();
	rotateX(frameCount / 25);
	rotateY(frameCount / 100);
	rotateZ(-frameCount / 50);
	triangle(0, -300, -50, -220, 50, -220);
	pop();
	push();
	rotateX(-frameCount / 100);
	rotateY(frameCount / 25);
	rotateZ(frameCount / 50);
	triangle(0, 300, -50, 225, 50, 225);
	pop();
	push();
	rotateX(-frameCount / 50);
	rotateY(frameCount / 25);
	rotateZ(-frameCount / 100);
	triangle(-300, 0, -220, -50, -220, 50);
	pop();
	push();
	rotateX(frameCount / 100);
	rotateY(-frameCount / 50);
	rotateZ(frameCount / 25);
	triangle(300, 0, 220, -50, 220, 50);
	pop();
}

function mouseClicked() {
	if (!frameFlag) {
		stroke(220, 220, 255, 100);
		noFill();
	} else {
		clear();
		stroke(255);
		fill(150, 150, 255, 50);
	}
	frameFlag = !frameFlag;
}

※修正後(ライティング復帰版。修正個所は最後のmouseClicked内のみ)のコード

let frameFlag = false;

function setup() {
    createCanvas(600, 600, WEBGL);
    ambientLight(150, 150, 190);
    directionalLight(110, 110, 150, 255, -1, 1, 0);
    ambientMaterial(150, 150, 255, 200);
    strokeWeight(3);
    noStroke();
}

function draw() {
    background(20, 20, 45);
    drawObject();
}

function drawObject() {
    //draw 3d boxes and sphere
    push();
    rotateX(frameCount / 100);
    rotateY(frameCount / 100);
    rotateZ(frameCount / 100);
    box(300);
    pop();
    push();
    rotateX(-frameCount / 50);
    rotateY(frameCount / 100);
    rotateZ(frameCount / 100);
    box(200);
    pop();
    push();
    rotateX(frameCount / 50);
    rotateY(-frameCount / 100);
    rotateZ(-frameCount / 50);
    box(100);
    pop();
    push();
    rotateX(-frameCount / 100);
    rotateY(frameCount / 50);
    rotateZ(-frameCount / 100);
    sphere(30);
    pop();

    //draw 2d triangles
    push();
    rotateX(frameCount / 25);
    rotateY(frameCount / 100);
    rotateZ(-frameCount / 50);
    triangle(0, -300, -50, -220, 50, -220);
    pop();
    push();
    rotateX(-frameCount / 100);
    rotateY(frameCount / 25);
    rotateZ(frameCount / 50);
    triangle(0, 300, -50, 225, 50, 225);
    pop();
    push();
    rotateX(-frameCount / 50);
    rotateY(frameCount / 25);
    rotateZ(-frameCount / 100);
    triangle(-300, 0, -220, -50, -220, 50);
    pop();
    push();
    rotateX(frameCount / 100);
    rotateY(-frameCount / 50);
    rotateZ(frameCount / 25);
    triangle(300, 0, 220, -50, 220, 50);
    pop();
}

function mouseClicked() {
    if (!frameFlag) {
        stroke(220, 220, 255, 100);
        noFill();
    } else {
        clear();
        fill(0);
        noStroke();
        ambientLight(150, 150, 190);
        directionalLight(110, 110, 150, 255, -1, 1, 0);
        ambientMaterial(150, 150, 255, 200);
    }
    frameFlag = !frameFlag;
}