ずっと君のターン

2014-04-01 春の愛の嵐

WebGLを簡単に使うためのzero.jsを作成しました

| 01:39 |  WebGLを簡単に使うためのzero.jsを作成しましたを含むブックマーク

WebGLの登場で我らがウェブ業界にも徐々に3Dの波が押し寄せており、スプライトを利用した2Dゲームがいつのまにか3Dに駆逐されたように、3Dのできないウェブプログラマが駆逐されるのも時間の問題のようにさえ感じられます。

しかしいざWebGLを始めようと思っても、たとえば画面にドットを1つ表示しようと思っただけで

<html>
<head>
  <script src="CanvasMatrix.js"></script>
  <script>
  function main() {
    var domElement = document.createElement('canvas');
    domElement.width = 500;
    domElement.height = 500;
    document.body.appendChild(domElement);

    var gl = domElement.getContext('webgl') || 
      domElement.getContext('experimental-webgl');
    if (!gl) throw 'WebGL is not supported.';

    var positions = [
      -0.5,  0.5, 0.0,
       0.5,  0.5, 0.0,
      -0.5, -0.5, 0.0,
       0.5, -0.5, 0.0
    ];
    var indices = [
      0, 1, 2,
      2, 1, 3
    ];
    var numIndices = indices.length;

    var vbuffers = [positions, positions];
    for (var i = 0; i < vbuffers.length; i++) {
      var data = vbuffers[i];
      var vbuffer = gl.createBuffer();
      gl.bindBuffer(gl.ARRAY_BUFFER, vbuffer);
      gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), gl.STATIC_DRAW);
      vbuffers[i] = vbuffer;
    }
    gl.bindBuffer(gl.ARRAY_BUFFER, null);

    var ibuffer = gl.createBuffer();
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ibuffer);
    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Int16Array(indices), gl.STATIC_DRAW);
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);

    var vshader = gl.createShader(gl.VERTEX_SHADER);
    gl.shaderSource(vshader, 
      "#ifdef GL_ES\n" +
      "precision highp float;\n" +
      "#endif\n" +
      "uniform mat4 mvpMatrix;\n" +
      "uniform mat4 normalMatrix;\n" +
      "uniform vec4 lightVec;\n" +
      "uniform vec4 lightColor;\n" +
      "uniform vec4 materialColor;\n" +
      "attribute vec3 position;\n" +
      "attribute vec3 normal;\n" +
      "varying vec4 color;\n" +
      "void main() {\n" +
      "  float light = clamp(dot(vec3(0.0, 0.0, 1.0), lightVec.xyz), 0.0, 1.0) * 0.8 + 0.2;\n" +
      "  color       = min(min(materialColor, lightColor), vec4(light, light, light, 1.0));\n" +
      "  gl_Position = mvpMatrix * vec4(position, 1.0);\n" +
      "}"
    );
    gl.compileShader(vshader);
    if (!gl.getShaderParameter(vshader, gl.COMPILE_STATUS)) {
      throw gl.getShaderInfoLog(vshader);
    }

    var fshader = gl.createShader(gl.FRAGMENT_SHADER);
    gl.shaderSource(fshader, 
      "#ifdef GL_ES\n" +
      "precision highp float;\n" +
      "#endif\n" +
      "varying vec4 color;\n" +
      "void main() {\n" +
      "  gl_FragColor = color;\n" +
      "}"
    );
    gl.compileShader(fshader);
    if (!gl.getShaderParameter(fshader, gl.COMPILE_STATUS)) {
      throw gl.getShaderInfoLog(fshader);
    }

    var program = gl.createProgram();
    gl.attachShader(program, vshader);
    gl.attachShader(program, fshader);

    gl.bindAttribLocation(program, 0, 'position');
    gl.bindAttribLocation(program, 1, 'normal');

    gl.linkProgram(program);
    if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
      throw gl.getProgramInfoLog(program);
    }

    var uniformVars = [
      gl.getUniformLocation(program, 'mvpMatrix'),
      gl.getUniformLocation(program, 'normalMatrix'),
      gl.getUniformLocation(program, 'lightVec'),
      gl.getUniformLocation(program, 'lightColor'),
      gl.getUniformLocation(program, 'materialColor')
    ];

    gl.clearColor(0, 0, 0, 1);
    gl.clearDepth(1000);
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

    gl.enable(gl.DEPTH_TEST);
    gl.useProgram(program);

    var lightVec = [0.0, 0.0, 1.0, 0.0];
    var lightColor = [1.0, 1.0, 1.0, 1.0];

    var modelMatrix = new CanvasMatrix4();

    var mvpMatrix = new CanvasMatrix4(modelMatrix);
    mvpMatrix.translate(0, 0, -500);
    mvpMatrix.perspective(30, 1.0, 0.1, 1000);

    var normalMatrix = new CanvasMatrix4(modelMatrix);
    normalMatrix.invert();
    normalMatrix.transpose();

    var materialColor = [1.0, 1.0, 1.0, 1.0];

    var values = [mvpMatrix, normalMatrix, lightVec, lightColor, materialColor];
    for (var i = 0; i < values.length; i++) {
      var value = values[i];
      if (value instanceof CanvasMatrix4) {
        gl.uniformMatrix4fv(uniformVars[i], false, value.getAsWebGLFloatArray());
      }
      else {
        gl.uniform4fv(uniformVars[i], new Float32Array(value));
      }
    }

    var strides = [3, 3];
    for (var i = 0; i < strides.length; i++) {
      var stride = strides[i];
      gl.enableVertexAttribArray(i);
      gl.bindBuffer(gl.ARRAY_BUFFER, vbuffers[i]);
      gl.vertexAttribPointer(i, stride, gl.FLOAT, false, 0, 0);
    }

    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ibuffer);

    gl.drawElements(gl.TRIANGLES, numIndices, gl.UNSIGNED_SHORT, 0);
    gl.flush();
  }
  document.addEventListener('DOMContentLoaded', main, false);
  </script>
</head>
<body>
</body>
</html>

信じがたいことにこのようなコードが要求されます*1

特にC言語様のGLSLコードを文字列として渡さないと画面に何も表示されず、そのGLSLのコードにJS側から値を与えるには「GLSLコード内で何番目に宣言された変数か」という情報が必要だったりするところなど、まるで何か悪い夢を見ているようですらあります。

WebGLの難しさはこのようなWebGL自体の複雑さに加えて、さらに3Dの知識までもが要求されるところにあるといえるでしょう。

f:id:technohippy:20140401013318p:image:w300

さて、このように複雑過ぎる問題に直面した時に我々プログラマが取るべき手段とはなんでしょうか?そうです。分割統治です。そこで私はこの問題から3Dという問題を除き、まずは「WebGL自体の複雑さ」だけを解決するためのライブラリを開発しました。

f:id:technohippy:20140401013507p:image:w400

http://technohippy.github.io/zero.js/

それがこのzero.jsです。zero.jsは名前の通りWebGLを使用して原点、すなわちゼロ次元を描画するためのライブラリです。このライブラリを使用すれば煩わしい3Dについて考えることなく、WebGLを利用することに集中できます。例えば最初に上げたコードと同様の表示を次のように非常にシンプルに記述することができます。

function main() {
  var scene = new ZERO.Scene();

  var geometry = new ZERO.PointGeometry();
  var material = new ZERO.MeshBasicMaterial({color: 0xffffff});
  var mesh = new ZERO.Mesh(geometry, material);
  scene.add(mesh);
  
  var width = 500;
  var height = 500;
  var fov = 30;
  var aspect = width / height;
  var near = 0.1;
  var far = 1000;
  var camera = new ZERO.PerspectiveCamera(fov, aspect, near, far);
  camera.position.set(500);

  var directionalLight = new ZERO.DirectionalLight(0xffffff);
  directionalLight.position.set(1.0);
  scene.add(directionalLight);

  var renderer = new ZERO.WebGLRenderer();
  renderer.setSize(width, height);
  document.getElementById('demo').appendChild(renderer.domElement);

  renderer.render(scene, camera);
}
document.addEventListener('DOMContentLoaded', main, false);

f:id:technohippy:20140401013318p:image:w300

これ以上なくシンプルなコードで原点が描画できていることが分かるでしょう。

もちろんここでライブラリに存在する唯一のジオメトリであるZERO.PointGeometryは原点であるため一切の座標が指定できません。これは光速が観測者によらず一定であることを考えると分かりやすいのではないでしょうか。

またカメラ(視点)についても指定できるのは基本的に原点からの距離だけです。ただし原点はゼロ次元であるため当然"大きさ"という概念が存在せず、カメラの位置に関わらず視角つまり表示上のサイズは一定になります。

このようにzero.jsを使用すれば座標をほぼ意識せずにWebGLが利用できることが分かっていただけるでしょう。zero.jsが皆さんがWebGLを使い始める零歩目になることを願っています。

・・・

なお、姉妹品として1次元を描画するためのone.jsも合わせて作成しました。zero.jsに習熟した方の次の一歩としてよろしければこちらもご利用ください。

http://technohippy.github.io/one.js/

2014-03-19 急に春めいてDart

大人のプログラミング言語Dart

21:28 | 大人のプログラミング言語Dartを含むブックマーク

Dartの本を翻訳しました。今月28日発売です。

こんな記事を書いたり、こんな記事を勝手訳したりしておきながら、しれっとDart本の翻訳に参加し、あまつさえ訳者まえがきでみなさんにDartをオススメする。この変り身の早さ、このしたたかさこそが激動の現代を生き抜くのに最も必要とされているものと言えるでしょう。かのチャールズ・ダーウィンは言いました。

最も強い者が生き残るのではなく、最も賢い者が生き延びるでもない。唯一生き残るのは、変化できる者である

何一つ恥じ入るところなどない、これこそが正しい大人の姿です。この私の背中を見て人生に迷う若者たちがしなやかに強く生きる術を身につけてくれればと願ってやみません。

・・・

という照れ隠し的なエクスキューズはさておき。言語の本は一度書いてみたいなぁと思っていたので、翻訳書とはいえなかなか喜ばしいです。ものすごい大それた日本語タイトルがついていて初めて見た時は結構ビビリましたが(原題は「Dart in Action」)。

「プログラミング言語Dart」というタイトルではありますが、実際のところ本書は言語仕様を説明するような本ではなく、「Dartを使って何かを作りたい」という人のための本です。そこさえ勘違いしなければとてもいい本だと思うので、本屋で見かけたらまずは著者紹介だけでも目を通していただけると嬉しいです。

2014-03-12 もうすぐ春ですね

A Perfect World

21:27 | A Perfect Worldを含むブックマーク

twitterで@negipoさんが

f:id:technohippy:20140312131156p:image:w300

https://twitter.com/negipo/status/439211041302392832

とか面白いことを言ってたので、なんとなく画像をRGBの各チャンネルごとにサイズの違うモザイクにして、それぞれのドットを円形にした上で合成したらおもしろい感じになるかなーとやってみたら

f:id:technohippy:20140312131500p:image:w200

意外と面白くもなんともなく。あ、元画像はみんなの大好きなレナさんです。

で、この画像のモザイクのサイズの大小関係はいちおう比視感度を考慮してにしてたんですが、ふとこの順番変えてみたらどうなるんだろうかと思い立ちまして。

やってみたのが以下です。

f:id:technohippy:20140312131456p:image:w200

解像度は同じはずなのに明らかにボヤけた感じになりました。

これがなにか定性的な話なのか、たまたまレナさんがこうなるだけなのか分からないけど、なんか面白かったのでメモ。

2014-02-22 いまさらDart Flight School

Dart Flight School Tokyo

| 21:24 |  Dart Flight School Tokyoを含むブックマーク

Dart言語のバージョンが1.0を迎え、これを機に本格的に盛り上げていきたいということで2月に世界中でDart Flight Schoolというイベントが開かれています。今ちょっと公式サイトで数えてみたらイベントの数が119もあって、もうDartは世界を獲ったと言っても過言じゃない感じじゃないですか。

https://www.dartlang.org/events/2014/flight-school/

そんな世界を席巻しているDartのイベントがTokyo GTUGさん主催、Google Japanさんで開催されたので、AngularDartについてちょっと話をしてきました。話をしたというか、コードラボですが、資料はこちら。

http://goo.gl/aXyt2e

ということで、私としては京都と同じことを東京でもやったので、ブログエントリもコピペしてみました。

f:id:technohippy:20140312212204p:image

壇上からの写真は公開していいかよく分からなかったので公開しても問題なさそうな安生さんのご尊顔。

東京はコードラボ以外にセッションがいくつかありましたが、なかでもDartVMの話が白眉。

http://www.slideshare.net/nothingcosmos/2014-dart-flight-school-in-tokyo

Dartの資料読んでてモヤッとしてたところが、あーあれそういう意味だったのかー、みたいなaha!がとてもたくさん。とある書籍でイベントループ周りの説明読んでてもなんかピンと来ないなーと思ってたんだけど、おかげさまで分かった気になれました。すごく急だったけど発表をお願いしてよかった。

あとはLTでDartからSIMD使う話があったり、Scala.jsの話があったり、なかなか多彩で楽しい勉強会でした。

2014-02-08 八甲田山的Dart Flight School Kyoto

Dart Flight School Kyoto

| 14:46 |  Dart Flight School Kyotoを含むブックマーク

Dart言語のバージョンが1.0を迎え、これを機に本格的に盛り上げていきたいということで2月に世界中でDart Flight Schoolというイベントが開かれています。今ちょっと公式サイトで数えてみたらイベントの数が119もあって、もうDartは世界を獲ったと言っても過言じゃない感じじゃないですか。

https://www.dartlang.org/events/2014/flight-school/

そんな世界を席巻しているDartのイベントがKyoto GTUGさん主催、京都リサーチパークさんで開催されたので、AngularDartについてちょっと話をしてきました。話をしたというか、コードラボですが、資料はこちら。

http://goo.gl/aXyt2e

公式チュートリアルはすでに終わらせてる人がいるかもしれないので資料は自作しましたが、コードラボ中にちょこちょこ不具合の指摘を受けて"TODO"とか追加してあります。すいません。

正直Dart単体だときっと便利な言語だけどいまいち面白みに欠ける感じですが、AngularDartはよく出来ててなかなか面白いです。AngularDartがRubyにおけるRailsみたいなポジションになってDart自体が盛り上がったりする可能性も完全にゼロではないと感じているので、少しでも気になる人は触ってみるといいんじゃないでしょうか。22日にはDart Flight School Tokyoも開催されるので、よろしければぜひ。

https://docs.google.com/forms/d/15RQ9U9i_TiSlGmRnpcgvnvOYaCv-w6ugGJAB2Bw9oEw/viewform

ちなみにFlight School Kyotoには日帰りで参加しましたが、よりにもよって大雪だったので、帰りは危うく遭難するところでした・・・。

f:id:technohippy:20140209001539j:image