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の知識までもが要求されるところにあるといえるでしょう。

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


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);

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

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

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

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

・・・

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

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

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

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

プログラミング言語Dart

プログラミング言語Dart

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

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

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

・・・

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

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

A Perfect World

twitter@negipoさんが


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

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

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

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

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

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

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

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

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


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

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

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

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

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

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には日帰りで参加しましたが、よりにもよって大雪だったので、帰りは危うく遭難するところでした・・・。

Yo! Chrome App

なんとなく検索してたらYeomanChromeアプリ用のジェネレーターがあったので、試しに作成中のIRCクライアントを乗っけてみたらなかなかいい感じです。

特にソースコードを編集したらChromeアプリが自動リロードされるのがいい。これまではいちいち chrome://extensions で該当のアプリをリロードする必要があって、面倒くさくて仕方なかったので。

まぁYeoman使ってディレクトリ構成を生成した後は、既存のコードを持ってきてコピペしただけなので、ジェネレーターとしてはまともに使ってないけど、それもまたよし。

https://github.com/yeoman/generator-chromeapp

https://github-camo.global.ssl.fastly.net/49dd4e0c45a7a6cefa416177249797b7a286be1e/687474703a2f2f692e696d6775722e636f6d2f766837756f34582e676966#.gif

正直近頃のJavaScript開発に全然ついていってなくて、Yeoman使うの自体始めてだったわけだけど、Yeomanにはbowerだのgruntだのmochaだのいろいろ付いて来て勉強なるなる。フルスタックのフレームワークはこうやって最新のツールやら環境を強制してくれるのがなんだかんだ言ってありがたいよなと、久しぶりにRails使いはじめの頃を思い出したりしましたとさ。

Chromeウェブアプリで作ったIRCクライアントを公開してみた

以前、ChromeウェブアプリでIRCクライアント作ってみた後、当時はChromeアプリはウェブストアに公開できなかったのでそのまま放置してたんですが、気が付くと公開できるようになってたのと、いろいろ立て込んでたのが落ち着いたので、ちょっと修正してウェブストア上で公開してみました。

https://chrome.google.com/webstore/detail/chrocha-irc-client-for-ch/eoajgjnmcikichkdpkjkpgoejkglinbd

https://raw.github.com/technohippy/irc-client-for-chrome/master/images/doc/chrocha_main_en.png

この流れ?なのでAngular製か、はたまたDart製か、とか思う人もいるかもしれませんが

「質問だ・・・AngularJSで作るか?Dartで作るか?あててみな」
「ひ・・・ひと思いにAngularJSで・・・やってくれ」
「NO!NO!NO!NO!NO!」
「D・・・Dart?」
「NO!NO!NO!NO!NO!」
「り・・・りょうほーですかあああ〜」
「NO!NO!NO!NO!NO!」
「もしかしてPure JavaScriptですかーッ!?」
「YES!YES!YES!"OH MY GOD"」


まぁそんなわけで今のところChromeアプリであること以外とりたてて特徴もなし。でも作り始めた時点ではAngularJSはCSPに引っかかって使えなかったし、Dartも1.0前で仕様がブレブレで選択肢になかったんスよ。

多分まだまだいろいろ不具合もあるんだろうけど、とりあえず普通に使えてるので、よろしければみなさんもどうぞ。