(2018/01/25追記:こちらのエントリで紹介しているコードは、p5.js 0.5.16向けのものになります。0.6.0以降では正しく動作しません)
前回はp5.jsのWebGLモードでオブジェクトに「グロー」的な効果を適用する方法として、
・方法1:サイズと透明度を変えたオブジェクトを複数重ね描き
・方法2:光彩の画像を用意しておいてオブジェクトと重ねて表示
の2つのやり方を試しました。ただし「方法2」については「カメラの位置は変わらず、オブジェクトの位置だけが変わるスケッチの場合にのみ」使える対応方法まで考えました。
前回紹介した方法2のスケッチで、カメラ位置を「orbitControl」を使って回すと、不都合が出てきます。光彩の画像は「plane」という厚さのない板状プリミティブのテクスチャとして表示しています。そのため、そのままカメラ位置が真横にまわると、次の画像のように光彩部分の形が変わったり、見えなくなったりしてしまうのです。
今回はオブジェクトの位置だけでなく「カメラの位置」も変化させたい場合に「方法2」を使ってグロー効果を適用するやり方です。
カメラとオブジェクト間の距離をとって配列を並び替える
基本的な方針としては
・カメラ位置を変える(見え方を変える)ために「orbitControl」や「rotateX, rotateY, rotateZ」といったp5.js作り付けの関数を使わない
・その上で、フレームごとにカメラの座標と各オブジェクトの座標との「距離」とり、それが遠い順にArray.sortで配列内の並び順を変える
・さらに、planeを描く時にカメラに対して平行になるようにrotateYで方向を変更する
とすれば良さそうな気がします。この方針に従って作ったスケッチが次のものになります。
●光彩画像によるグローを適用したスケッチでカメラ位置を動かす(クリックすると別タブが開きます)
「600×600×600の空間上にランダムに動くグロー付きsphereをバラまいて眺める」という、前回使ったものとほぼ同じスケッチですが、今回のスケッチには、カメラ位置を変更するためのスライドバーを2本付けました。上のバーで、カメラの位置を原点(注視点)を中央とする円周上で横方向に動かせます。下のバーでは、原点(注視点)からカメラまでの距離を変更できます。
カメラ位置の変化を分かりやすくするためにワイヤーフレームのboxも緑色で表示してみました。とりあえず、2本のバーをグリグリ動かして、カメラ位置が変わっても光彩画像によるグローがきちんと適用されていることを確認してみてください。…たまーに2つのオブジェクトの位置関係が微妙なときに透過をしくじることがありますが、ま、これは許容範囲ということで(笑)。
var sphereArr = []; var sphereMax = 80; var glowImg; var camX = 0; var camY = 0; var camZ = 350; var camRad = 1200; var camPos = 0; var camRotSlider; var camRadSlider; function setup() { glowImg = loadImage('./images/glow.png'); createCanvas(600, 600, WEBGL); perspective(radians(45), width / height, 0, 3000); noStroke(); for (var i = 0; i < sphereMax; i++) { sphereArr[i] = new PointObj(i); } setSlider(); } function draw() { background(0, 0, 30); camCordUpdate(); camera(camX, camY, camZ, 0, 0, 0, 0, 1, 0); drawBox(); sphereArr.forEach(spObj => { spObj.drawPlane(); spObj.drawSphere(); spObj.update(); }); sphereArr.sort(zOrder); sliderValue(); } function drawBox() { push(); stroke(150, 180, 0); strokeWeight(8); noFill(); box(600); fill(0); pop(); } function zOrder(obj1, obj2) { return obj2.distToCam - obj1.distToCam; } function camCordUpdate() { camX = cos(radians(camPos)) * camRad; camZ = sin(radians(camPos)) * camRad; camY = 0; } class PointObj { constructor(depth) { this.x = random(-300, 300); this.y = random(-300, 300); this.z = random(-300, 300); this.xmove = random(-5, 5); this.ymove = random(-5, 5); this.zmove = random(-5, 5); this.distToCam = 0; } drawPlane() { push(); translate(this.x, this.y, this.z); rotateY(radians(90 - camPos)); texture(glowImg); plane(50, 50); pop(); } drawSphere() { push(); translate(this.x, this.y, this.z); ambientLight(120); pointLight(255, 255, 255, 255, -600, -600, 300); ambientMaterial(255, 255, 255, 255); sphere(3); pop(); } update() { this.x = this.x + this.xmove; this.y = this.y + this.ymove; this.z = this.z + this.zmove; if (this.x > 300 || this.x < -300) { this.xmove = -this.xmove; } if (this.y > 300 || this.y < -300) { this.ymove = -this.ymove; } if (this.z > 300 || this.z < -300) { this.zmove = -this.zmove; } this.distToCam = dist(this.x, this.y, this.z, camX, camY, camZ); } } function setSlider() { camRotSlider = createSlider(0, 360, camPos); camRotSlider.position(5, height + 10); camRotSlider.size(600); camRadSlider = createSlider(1, 1200, camRad); camRadSlider.position(5, height + 40); camRadSlider.size(600); } function sliderValue() { camPos = camRotSlider.value(); camRad = camRadSlider.value(); }
各オブジェクトとカメラとの距離を取得するために、オブジェクトのコンストラクタに「distToCam」という変数を加え、updateの際に「dist()」を使って、オブジェクトの座標とカメラの座標から距離を算出し代入しています(88行目)。さらにArray.sortで並び替えの基準となる「zOrder」関数の中で、この「distToCam」の数値によって配列を並び替えています。これで描き順は調整できました。
さらにplaneを描く際に、67行目の「rotateY(radians(90 – camPos));」で、カメラの回転角度と逆方向に座標系を回し、カメラから見たplaneが正面を向くように調整しています。
これでどんな場合でも光彩画像を使ったグローが使える…かと思いきや、実はもうひとつ考慮しなければいけない要素が残っています。それは「カメラの注視点」です。
このスケッチでは注視点を「原点(0, 0, 0)」に固定しています。そのため、planeは「rotateY」を使って回転させればそれでOKなのですが、注視点が変更された場合には、さらに別の軸での回転を加えてやる必要が出てきます。たぶん、現状の回転に加えて、カメラ自身の回転方向のカウンターになるようにplaneの回転軸を増やしてやればいいはずなのですが、これについてはそのうち必要になったときに、実装してみようと思います。とりあえず、今回はここまでということで。
次回からは、ほったらかしにしていた「フラクタル」の続きをなんとかしようと思っています。
光彩画像によるグローを使ったスケッチでカメラを動かす はコメントを受け付けていません