2011年12月3日土曜日

WebGL Advent calender 3日目

それでは3日目、WebGL Advent Calender書くよー!

1日目edvakfさんが基本的なWebGLの考え方について、2日目yomotsuさんがThree.jsでWebGLについて書かれています。
どちらもソースコードや図を交えての丁寧な解説なので是非読んでみてください!

で、私の記事はThree D Libraryについて。
内容はWebGL経験者向けになるのでedvakfさんの記事を読んでから読むといいと思います!

さて、WebGLを直接書くと「手続きが多いなー面倒だなー」と思われた方も多いと思います。
一方でThree.jsは手続きが少なく簡単ですが「俺はシェーダをガンガン書きたいんだよ!」という人には向いていないかもしれません。

そこで今回紹介するライブラリはThree D Libraryです(略してTDL。おや?だれかきたようだ)。
http://code.google.com/p/threedlibrary/

使いやすさよりも速さを追求したライブラリで、拙作のこれもTDLを使用しています。

行列演算の速さ
まずご覧頂きたいのが行列演算のベンチマークです。
http://stepheneb.github.com/webgl-matrix-benchmarks/matrix_benchmark.html

Google Chromeでベンチマークを取ってみた結果がこれです。


TDLには一時変数の使用を抑えて高速に演算するTDLFastと、一時変数をメソッド内で生成するTDLMathの2種類が存在します。このTDLFastはClosure Libraryに次いでAverageがいいですね。
これだけでもTDLを使う価値があるかと思います。

というわけでこのTDLの簡単な特徴をサンプルソースを読みながら解説していきたいと思います。

シェーダコンパイルの簡単さ
まずはこの部分。
function createProgramFromTags(vertexTagId, fragmentTagId) {
  return tdl.programs.loadProgram(
      document.getElementById(vertexTagId).text,
      document.getElementById(fragmentTagId).text);
}

これはタグからシェーダプログラムを生成するメソッドです。
よくある<script>タグの中にシェーダソースを記述し、それをコンパイルするってやつです。
ではこのloadProgramメソッドのなかで何が行われているのかを見てみましょう。

/**
 * Loads a program.
 * @param {string} vertexShader The vertex shader source.
 * @param {string} fragmentShader The fragment shader source.
 * @return {tdl.programs.Program} The created program.
 */
tdl.programs.loadProgram = function(vertexShader, fragmentShader) {
  var id = vertexShader + fragmentShader;
  tdl.programs.init_();
  var program = gl.tdl.programs.programDB[id];
  if (program) {
    return program;
  } 
  try {
    program = new tdl.programs.Program(vertexShader, fragmentShader);
  } catch (e) {
    tdl.error(e);
    return null;
  }

  gl.tdl.programs.programDB[id] = program;
  return program;
};

シェーダのコンパイルやリンクも全てこのメソッドの中で行なってしまいます。
コンパイル済みのシェーダはソースをキーにしてデータベースに登録します。
これで同一のプログラムを作成してしまうって現象は発生しませんね、安心。

モデルの作成
では描画するモデルはどのようにして作成するのでしょうか?
こんどは次のメソッドに注目してください。
/**
 * Sets up Planet.
 */
function setupSphere() {
  var textures = {
    diffuseSampler: tdl.textures.loadTexture('assets/sometexture.png')};
  var program = createProgramFromTags(
      'sphereVertexShader',
      'sphereFragmentShader');
  var arrays = tdl.primitives.createSphere(0.4, 10, 12);
 
  return new tdl.models.Model(program, arrays, textures);
}
さきほどのcreateProgramFromTagsでタグからソースをコンパイルしています。
その後に描画するModelを作成しています。

TDLにおいてはこのModel単位でレンダリングを行います。
Modelを作成するときにprogram, arrays, texturesを引数として渡しています。

programはこのモデルをレンダリングするために使用するシェーダ。
arraysは描画する対象。createSphereメソッドでつくった球体を指定しています(確か複数指定可)。
最後のtexturesですが、これがまた面白い。
diffuseSamplerというキーに対してテクスチャを設定しています。
このdiffuseSamplerというキーは、じつはシェーダ内の変数に対応しています。

uniform sampler2D diffuseSampler; ← この部分

つまりレンダリング時にdiffuseSamplerには自動的にassets/sometexture.pngが割り当てられるわけです、はい。

いよいよレンダリング
それではModelをレンダリングするにはどうすればいいのでしょうか?
今度見てもらいたいのはこのへんです。
// Sphere uniforms.
  var sphereConst = {
    viewInverse: viewInverse,
    lightWorldPos: lightWorldPos,
    specular: one4,
    shininess: 50,
    specularFactor: 0.2};

  var spherePer = {
    lightColor: new Float32Array([0,0,0,1]),
    world: world,
    worldViewProjection: worldViewProjection,
    worldInverse: worldInverse,
    worldInverseTranspose: worldInverseTranspose};

はい、これuniformとしてシェーダにわたす変数ですね。
キーがシェーダ内のuniformの変数名に対応してます。

uniform mat4 viewInverse; ← こんなかんじ

TDLだとuniformをわたすタイミングは2回あります。
それを次のソースで確認してみましょう。

sphere.drawPrep(sphereConst);
    var across = 6;
    var lightColor = spherePer.lightColor;
    var half = (across - 1) * 0.5;
    for (var xx = 0; xx < across; ++xx) {
      for (var yy = 0; yy < across; ++yy) {
        for (var zz = 0; zz < across; ++zz) {
          lightColor[0] = xx / across;
          lightColor[1] = yy / across;
          lightColor[2] = zz / across;
          var scale = (xx + yy + zz) % 4 / 4 + 0.5;
          fast.matrix4.scaling(m4t0, [scale, scale, scale]);
          fast.matrix4.translation(m4t1, [xx - half, yy - half, zz - half]);
          fast.matrix4.mul(world, m4t0, m4t1);
          fast.matrix4.mul(worldViewProjection, world, viewProjection);
          fast.matrix4.inverse(worldInverse, world);
          fast.matrix4.transpose(worldInverseTranspose, worldInverse);
          sphere.draw(spherePer);
        }
      }
    }

はい、コレがサンプルのレンダリング部分ですね。
fast.matrix4.*っていうのが行列演算メソッドです(次回辺りここの部分を解説したいなー)。

このサンプルは球体を複数描画するというサンプルなのですが球体を複数描画する場合でもライトは常に1つですよね?
そこでライトに当たるuniformをdrawPrepメソッドで渡し、最後にdrawメソッドで球の色や大きさなどそれぞれのuniformを渡しています。
drawPrepを一度呼び出しておけばあとは何度drawを読んでもOKです。

あとこの周辺の処理を見て欲しいのですがほとんどWebGLのAPIをそのまま呼び出していることに気がつくはずです。
TDLの最大の特徴はWebGLの手続きを少なくして呼び出せるところにあるのです。

まとめ
TDLはWebGLの手続きの多さを少しだけ楽にしてくれます。
特にuniformを与える部分が便利。TDLを使用すれば行列なのか、ベクトルなのかちゃんと型をみて型にあったメソッドを呼び出してくれます。
シェーダをガンガン書きたいけどいわゆる「お決まりの手続きは省略したい」という方はつかってみてはいかがでしょうか?

それではWebGL Advent Calenderの成功を祈願しています。
良い年末を!

0 件のコメント:

コメントを投稿