読者です 読者をやめる 読者になる 読者になる

Processing中毒者の嘔吐物

ProcessingやらopenFrameworksやら色んなプログラミングについて吐きます

three.jsでジェネラティブアートっぽいことするのに必要な知識

three.js

タイトルの通り。

去年末くらいからWebGLというかthree.jsの勉強始めて、ある程度複雑な作品が作れるようになった。現在7つほどhttp://p5aholic.me/で作品を公開している。

点と線が繋がってごにょごにょするやつとか、近い点で面作ってごにょごにょするやつとか、そういうのをthree.jsで作るにはどんな知識が必要かというのを書いておく。本当はthree.jsの入門サイト作って体系的に解説したいところだけど、入門サイトはP5 Code Schoolで手一杯です。入門サイト運営するのくっそ面倒なんですわ。

大前提

JavaScriptはオブジェクトとか含めてしっかり理解していること。

three.jsの基礎知識

当たり前だけど、three.jsの基礎は押さえてないといけない。僕はLearning Three.js読んだ。英語だけどすごい読みやすいのでオススメ。

Learning Three.js ? the JavaScript 3D Library for WebGL - Second Edition

Learning Three.js ? the JavaScript 3D Library for WebGL - Second Edition

BufferGeometryとBufferAttribute

three.jsでジェネラティブアート作る上で一番重要だと思うのがBufferGeometryBufferAttribute。この2つは図形に用いる頂点や色、その他様々な情報をCPUではなくGPU側で管理するバッファリングという処理をやってくれる。three.jsの基本Geometry(BoxGeometryとかSphereGeometry)を使った場合は頂点データとかを毎フレームごとにGPUに送っている。BufferGeometryを使うと図形データの転送が最初の1回で済むのでフレームごとのCPUの処理がぐっと軽くなる。特に僕が作りたいようなジェネラティブアートはとにかく頂点の数が多い。僕の作品のWork1では点を600個用意して、600個の点は自分以外の599個の点との距離を計算して一定距離内なら線を引いている。ここで重要なのが、three.jsでこういう処理をする場合600 * 600 = 360000個の線をあらかじめ用意しておかなければいけないということ。Processingなら

// 全ての点に対して、他の全ての点との距離を計算し
// lengthLimit以下ならその2点間に線を引く
for (int i = 0; i < points.length; i++) {
  Point fromP = points[i];
  stroke(fromP.col);
  for (int j = i+1; j < points.length; j++) {
    Point toP = points[j];
    float dist = dist(fromP.x, fromP.y, fromP.z, toP.x, toP.y, toP.z);
    if (dist < lengthLimit) {
      line(fromP.x, fromP.y, fromP.z, toP.x, toP.y, toP.z);
    }
  }
}

ってな感じでできるんだけど、three.jsでは動的に描画するオブジェクトを増やしたり減らしたりするのが難しい。なので、描画する可能性のある図形をあらかじめ全部用意しておいて、実際に描画するときはそこから必要な図形を選び出して描画する感じになる。描画する図形の範囲を変えるにはBufferGeometryクラスのsetDrawRange()を使う。話を戻すと、360000個なんて数の頂点データを毎フレームGPUに送るなんて無理があるので、BufferGeometry使うしかない。

参考資料

僕が学習の過程で読んでよかった資料を紹介する。

wgld.org
これはとりあえず18回までは読んでおくべき。VBOやらIBOやらの話は直接BufferGeometryと関係があるので押さえておかないといけない。

あとはthree.js公式のDocumentationとExamplesを読む。
特にExamplesのこれhttp://threejs.org/examples/#webgl_buffergeometry_drawcallsはまさに点と線が繋がってごにょごにょしている。setDrawRange() を使った唯一のExampleなのでよく読み込んで理解すべし。

以上、雑な記事ですが参考になれば嬉しいです。