2013年2月17日日曜日

[WebGL][JavaScript][HTML] 第1回 WebGLで遊んでみる(描画領域のクリア)

最近OpenGLにハマってる。
やっぱり自分でアニメーションやエフェクト、3Dポリゴンなどをゴリゴリできるのは楽しい。
ブラウザでも実はWebGLというものがある。
まだ対応しているブラウザが多いわけではないがどんどん増えてきている。
また、CSSでもCSS shadersというものが規格されており、
これもWebGL・・・もっといえばOpenGLがベースとなっているのだ。(2013/02/17現在)

もっとも簡単なWebGL、それは描画領域のクリアである。

描画結果(画像)


描画領域をクリアすると上の画像のように一色で塗りつぶされる。
では、早速ソースコードを見ていこう。

まずはHTML部分から。
//link・・・http://mio-koduki.blogspot.jp/2013/02/webgljavascripthtml-webgl.html
<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="utf-8">
        <script src="webgl.js"></script>
    </head>
    <body>
        <canvas id="canvas" width="500px" height="500px" style="width:500px;height:500px;"></canvas>
    </body>
</html>

まず、ポイントはcanvas要素を作ること。
WebGLはこのcanvas要素に描画されることになる。
なので、このcanvasにidとサイズを設定してあげる。

次にJavaScript側。
//link・・・http://mio-koduki.blogspot.jp/2013/02/webgljavascripthtml-webgl.html
//デバッグ用フラグ
var DEBUG_FLAG=true;

//WebGLのコンテキスト
var gl=null;

var main=function()
{
    //canvas要素を取得する
    var canvas=document.getElementById('canvas');
    //getContextの引数を配列で用意
    var tryContext=
    [
        'webgl'
        ,'experimental-webgl'
        ,'webkit-3d'
        ,'moz-webgl'
    ];
    //順番にgetContextを実行しWebGLのコンテキストを取得を試みる
    for(var i in tryContext)
    {
        gl=canvas.getContext(tryContext[i]);
        //WebGLのコンテキスト取得に成功したらループを抜ける
        if(gl)
        {
            break;
        }
    }
    //WebGLのコンテキストを取得できなかったらコンソールにメッセージを出しreturnでmain関数を抜ける
    if(!gl)
    {
        console.log('no gl');
        return;
    }
    //RGBAの順番にカラーバッファをクリアする色を指定する
    gl.clearColor(0.5,0.5,0.5,1.0);
    //カラーバッファをクリアする
    gl.clear(gl.COLOR_BUFFER_BIT);
    //clearメソッドのエラーチェック
    checkGLError('clear');
};
//ページロード時にmain関数実行
window.onload=main;

var checkGLError=function(errorString)
{
    //デバッグ時のみチェック
    if(DEBUG_FLAG)
    {
        var noError=gl.NO_ERROR;
        var error=noError;
        //エラーを取得しNO_ERROR以外だったらコンソールにメッセージを出す
        while((error=gl.getError())!=noError)
        {
            console.log(error+" : "+errorString);
        }
    }
};

では、詳しく見ていこう。


まず、2行目~3行目にデバッグ用のフラグを用意している。
//デバッグ用フラグ
var DEBUG_FLAG=true;
WebGLに限らずプログラムを書くときにおいてこれは用意しておいたほうがいい。
特にWebGLと言うよりOpenGLは処理を高速化させるために、
本番ではエラーチェック処理を入れないということもしばしばある。
他にもデバッグの時だけしたい処理や逆にプロダクションだけでしたい処理なども出てきたりする。
用意して損はないだろう。


次に5行目~6行目にglという変数を用意している。
//WebGLのコンテキスト
var gl=null;
これはWebGLのコンテキストを代入するのに使う。
WebGLのコンテキストはWebGLの処理をするのに必ず必要な物なので、
グローバル変数として用意している。
なお、実際はあまりグローバル変数は好まれないので、
実際に自分で書くときはクラスのメンバ(JavaScript的にはプロパティ)などとして持たせるといいだろう。
今回はソース簡略化のためにグローバル変数にしている。


そして、8行目、次にちょっと飛んで44行目。
var main=function()
{
    /*
    省略
    */
};
//ページロード時にmain関数実行
window.onload=main;
main関数を定義し、ページがロードされた時に呼び出されるようにwindow.onloadに代入する。
こうすることによってDOMや画像が用意され終わった段階で、このmain関数を実行してくれるようになる。


次にmain関数の中を見ていこう。まず、10~11行目。
//canvas要素を取得する
var canvas=document.getElementById('canvas');
HTMLで用意しておいたcanvas要素を取ってくる。
id属性でcanvasと指定したものをとるというソースコードだが、
今回はcanvas要素自体に"canvas"というidを指定してるので、
結果的にcanvas要素が取れる。


さて、ようやくWebGLの第一歩、12行目~29行目。
//getContextの引数を配列で用意
var tryContext=
[
    'webgl'
    ,'experimental-webgl'
    ,'webkit-3d'
    ,'moz-webgl'
];
//順番にgetContextを実行しWebGLのコンテキストを取得を試みる
for(var i in tryContext)
{
    gl=canvas.getContext(tryContext[i]);
    //WebGLのコンテキスト取得に成功したらループを抜ける
    if(gl)
    {
        break;
    }
}
先ほど取得したcanvasのgetContextメソッドを呼びWebGLのコンテキストを取得するのだが、
これらはブラウザや実装状況によって引数の指定が異なる。
故に予め配列で引数の文字列を用意して、ループで回しWebGLのコンテキストが取得できたら、
ループを抜けるようにしている。


そして30行目~35行目。
//WebGLのコンテキストを取得できなかったらコンソールにメッセージを出しreturnでmain関数を抜ける
if(!gl)
{
    console.log('no gl');
    return;
}
WebGL未対応ブラウザだったりなにかしらのエラーやミスで、
WebGLのコンテキストが取得できなかった場合は素直にコンソールにメッセージを出し、
returnしてmain関数を終了している。


36行目~37行目にようやくWebGLのメソッドが登場!
//RGBAの順番にカラーバッファをクリアする色を指定する
gl.clearColor(0.5,0.5,0.5,1.0);
clearColorメソッドを呼び出し、
RGBAの順番にカラーバッファをクリア・・・つまり塗りつぶす色を指定する。
カラーバッファは描画領域のRGBA用バッファとして使われる。
つまりはカラーバッファをクリアすることによって描画領域もクリアできるのだ。
なお、色を指定するときはカラーコードでお馴染みの0~255ではなく、0.0~1.0である。


38行目~39行目。
//カラーバッファをクリアする
gl.clear(gl.COLOR_BUFFER_BIT);
clearメソッドを呼び出し、clearColorメソッドで指定した色を使って、
実際にカラーバッファをクリアする。


さて、40行目~41行目に何か関数がある。
//clearメソッドのエラーチェック
checkGLError('clear');
実はWebGLは・・・というかOpenGLは返り値や例外でエラーをキャッチするのではなく、
都度関数を呼んでエラーチェックを行う。
そのためそれらの処理を自前で関数として用意しまとめている。


それが46行目~59行目だ。
var checkGLError=function(errorString)
{
    //デバッグ時のみチェック
    if(DEBUG_FLAG)
    {
        var noError=gl.NO_ERROR;
        var error=noError;
        //エラーを取得しNO_ERROR以外だったらコンソールにメッセージを出す
        while((error=gl.getError())!=noError)
        {
            console.log(error+" : "+errorString);
        }
    }
};
引数は直前に呼び出したメソッド名。
関数の中で早速行なっているのはデバッグ用のフラグがtrueかどうかのチェックだ。
その理由は少し下のコードにある。
getErrorメソッドを呼び出し、NO_ERRORかどうかを見ている。
もしNO_ERRORでなかったらエラーの回数だけループするわけだが、
その処理をエラーチェックが必要な関数の都度することになる。
多少なら無視出来るレベルだが、ゲームやアニメーションを行う際には、
この多少が積み重なり処理速度を低下させるうる。
故にデバッグの時だけエラーチェックをしておき本番では行わないようにする。
今回はNO_ERRORではないとき、つまりエラーのときはコンソールに、
この関数の引数として渡した直前のメソッド名を、メッセージに含めて出している。
これによって何のメソッドでエラーが起きたかわかる。


WebGLに対応しているブラウザで上記のソースコードを実行させると、
描画結果の画像のようにcavas内全体が灰色に塗りつぶされて表示される。
なぜならカラーバッファを灰色でクリアすることにより結果、描画領域も灰色でクリアされたからだ。
OpenGLひいてはWebGLではエラーの時真っ黒に表示される。
その場合はこのカラーバッファクリアも効かずに真っ黒になるので、
黒以外でクリアしておくとエラーが起きてるのかパラメータミスなのかの問題の切り分けがしやすい。
なので、デバッグのときは黒以外の色でクリアしておくといいかもしれない。


第2回 WebGLで遊んでみる(点の描画)へ