hagino3000's blog

平成アーカイブス (更新停止)

OpenGLなにそれうまいの?? という人のためのWebGLの始め方

追記:例題の頂点シェーダーで何をしているか説明を追加しました
追記:動作環境の所修正しました、IE9では動作しません。

皆さんはじめまして、荒川智則です。この記事はJavaScript Advent Calendarの21日目です。

この記事では、Google I/OFirefox Developers Conferenceで華麗なデモがバリバリ出てくるにもかかわらず、実際に使っている人が異常なまでに少ないWebGLについて書きます。対象読者はWebGLに興味があるor手を出してみたけどクソ難しそうだし既に諦めそう、という人です。

WebGLの概要

WebGLOpenGL ES 2.0のグラフィックAPICanvas要素上で使える様にした物です。OpenGL ESはOpenGLの組み込み機器向けのサブセットで、iPhoneandroid端末にも搭載されています。OpenGL ESなにそれ??という人は、少ないリソースで動作させるのに最適化されている3DグラフィックスAPIの仕様だと覚えておけば良いでしょう。

OpenGL ES 2.0の特徴として、シェーダーがプログラムで書ける(プログラマブルシェーダー)、というのもあります。シェーダーの説明はまた後で。

動作環境

自分の環境で動作するかは次のデモページを開いてみるとわかります。

Webブラウザ

Chrome*1、Firefox4b、IE9WebKitのnightly buildで動作します。私はChrome 9で動作させています。

ハードウェア

OpenGL ES 2.0対応のGPUである必要があります。2008 late modelのmacbookでも動作しているので、最近のマシンなら動作するはず。Windowsマシンの事情はよく知らないので割愛します。

Hello Triangle

環境は整ったらWebGLの最初の一歩、三角形の描画をしてみます。Hello Worldでは無く、Hello Triangleな理由は、WebGLで描画できるものは三角形のみだからです。*2
Leaning WebGLというブログのLesson 1が参考になるので見てみましょう。

ざっと目を通すだけで、三角形と四角形を一つづつ表示するだけなのに半端無い量の解説がなされているのがわかります。実際のhtmlソースを見てみてもJavaScriptが100行を越えています。さらに行列計算ライブラリをscriptタグで読み込んでいるので、全て時前で書いたら100行どころではありません。さらに <script type="x-shader/x-fragment"> といった見慣れない物まで出てくる始末。数行のJavaScriptで四角や円が描画できたCanvasに比べると、WebGLはこの時点で習得を諦めたくなります。しかし、ここではWebGLが難しいのではなく*3Canvasが簡単すぎるのだと考えましょう。

次は、上記チュートリアルを初めて見た時にひっかかりそうな点をいくつか説明します。

WebGLの描画処理の流れ

まずWebGLの描画処理の流れを把握しましょう。単純化して書くと左の図になります。頂点データを頂点シェーダーとフラグメントシェーダーを介してフレームバッファに叩き込む作業が最低限必要になります。頂点データ、頂点シェーダ、フラグメントシェーダは自分で作る必要があります

頂点データ

3角形でいえば各頂点の3次元の座標(x,y,z)になります。

頂点シェーダー

各頂点毎の処理、例えば頂点座標の変換を行ないます。Leaning WebGL Lesson1のコードでは次の箇所が該当します。

<script id="shader-vs" type="x-shader/x-vertex">
  attribute vec3 aVertexPosition; // 頂点座標
  
  uniform mat4 uMVMatrix; // パラメータ
  uniform mat4 uPMatrix;  // パラメータ

  void main(void) {
    // 次の処理に渡される出力値
    gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);
  }
</script>

WebGLではシェーダーを記述するのにOpenGL ES シェーダー記述言語というのを使います。ここではuPMatrixとuMVMatrixの分だけ頂点を移動させています。コードを読むとmvTranslateに渡した値がuMVMatrixまで渡っています。つまりuMVMatrixは描画開始位置に相当します。頂点シェーダに値を渡しているのは次のコードです。

  // 描画開始位置へ移動(ちょっと左 and 奥の方)
  mvTranslate([-1.5, 0.0, -7.0]);

  gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexPositionBuffer);
  // 頂点シェーダに頂点座標データをセット
  gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, triangleVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
  // 頂点シェーダにパラメータをセット(setMatrixUniformsで)
  setMatrixUniforms();
  // 描画
  gl.drawArrays(gl.TRIANGLES, 0, triangleVertexPositionBuffer.numItems);

この例ではscriptタグからシェーダーのコードを読みこんでいますが、最初からJavaScriptで文字列として持っておいても使えます。

プリミティブの構築とラスタライズ

頂点シェーダーから受けとった頂点データを元にプリミティブ(OpenGLで扱える図形)を構築します。ラスタライズ処理ではプリミティブから平面(ピクセルの集まり)を生成します。

フラグメントシェーダー

OpenGL以外ではピクセルシェーダーと呼ばれる物です。ピクセル毎の処理をします。次のサイトを見ていただくとフラグメントシェーダーの効果がわかりやすいでしょう。

うねうね動いているのはフラグメントシェーダーに時間をパラメータとして渡して、操作に使っているためです。

Leaning WebGL Lesson1のコードでは次の箇所が該当します。このシェーダーにパラメータはありません。単純に各ピクセルを白で塗りつぶしています。

<script id="shader-fs" type="x-shader/x-fragment">
  #ifdef GL_ES
  precision highp float;
  #endif

  void main(void) {
    gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
  }
</script>

シェーダーのコードをWebGLで使う為には、コンパイルしてプログラムオブジェクトにアタッチします。この処理はLesson1のコードでは getShader, initShaders という関数で行なっています。必ず書く事になる処理なのですぐに覚えられるでしょう。余談ですが、シェーダーが書ける様になってもメシは食えないとの意見もありますので、あまりシェーダーを書く事に拘らない方がいいかもしれません。

バッファへの書きこみ

フラグメントシェーダーから出力されたピクセルデータをバックバッファに書き込みます。書き込みが完了したら、フロントバッファ(表示しているコマ)と差し替えます。これで1フレームの処理になります。

この辺りが理解できれば、あとは複雑な頂点データを作ったり、細かいテクニックを使いこなしていく段階でしょう。三角形を描画するだけで100行を越えるコードが必要になるといっても、ここに出てきた9割のコードはお決まりの物です。一度覚えてしまえばなんという事はありません。OpenGL ES 2.0について、私はOpenGL ES 2.0 プログラミングガイドを読んでだいたい理解できました。

最初はLeaning WebGLのLessonを何個か進めてみてから、自分で何か作ってみると良いでしょう。

行列演算ライブラリって?

すばらしい資料があるのでこちらをどうぞ!!

canvasとの組み合わせ

2Dプログラミングや文字の描画は、圧倒的にcanvasの方が簡単です。私はcanvas要素を二つ重ねあわせて、一つをWebGL用にして使っています。さらにCSSアニメーションを使って楽をしたい時もありますが、DOM操作はcanvas及びWebGLの描画をブロックしてしまうので注意が必要です。

物理演算

JavaScriptで使える3Dの物理演算ライブラリ。まだ使った事は無いのですが……。

テクスチャの罠

テクスチャの使い方を覚えたら、自分の好きな画像を回したくなるのが世の常。しかし、WebGLのテクスチャに使える画像サイズはそれぞれの辺が2の乗数(64px, 128px, 256px...)に限定されます。これはOpenGL ESの制限みたいです。Twitterのアイコンを無差別に取ってきてテクスチャとして貼るプログラムを書くとほぼ失敗します*4

デバッグ

OpenGL ESのコマンドを実行した時のOpenGL ESのエラーは、ブラウザのコンソールには出力されません。別途デバッガが必須になるので2つ紹介します。

WebGL-Inspector

こんな感じ、頂点バッファの情報やメモリ使用量も見れる、便利!!*5

エラーを表示するだけの奴

Leaning WebGLのサンプルや人の書いたコードををいじる時に仕込んでおくと、突然動かなくなった時に助かります。

リンク

Learning WebGL
http://learningwebgl.com/blog/
hack_webgl | Google グループ
https://groups.google.com/group/hack_webgl?hl=ja
Main Page - WebGL Public Wiki
http://khronos.org/webgl/wiki/Main_Page


地震がきたのでとりあえずこの時点でこの記事はまとめます。次はKinectと絡ませて使うといった内容で書く予定です。突っ込みがあったらコメ欄でもブコメにでも書いていただけると助かります。

*1:Chrome8は起動オプションが必要、Chrome9はデフォルトで有効

*2:四角形は三角形を組み合わせて作ります。円は細い二等辺三角形を沢山並べて作ります。

*3:C++で書いても全く同じコードになりますし

*4:というか自分がこの現象にはまった

*5:なぜか自分の環境ではJSのシンタックスエラーが出るので、修正して使ってます。