2013年3月21日木曜日

[WebGL][JavaScript][HTML] 第3回 WebGLで遊んでみる(3点の描画)

第2回では点の描画を通してシェーダやプログラムの使い方をやった。
しかし点の座標もシェーダに書いているため、JavaScriptから座標を制御して、
点の位置を変えたり移動したりすることができない。
そこで今回はJavaScriptから点の座標をシェーダに渡し、3つの点を描画してみる。

描画結果(画像)


3つの点が描画されている。

HTML部分。
//link・・・http://mio-koduki.blogspot.jp/2013/03/webgljavascripthtml-3-webgl3.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をHTMLで用意しておく。

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

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

//頂点シェーダ
var vertexShaderSource='attribute vec4 a_Position;\
\
void main()\
{\
    gl_Position=a_Position;\
    gl_PointSize=10.0;\
}';

//フラグメントシェーダ
var fragmentShaderSource='void main()\
{\
    gl_FragColor=vec4(0.0,0.0,1.0,1.0);\
}';

var main=function()
{
    //canvas要素を取得する
    var canvas=document.getElementById('canvas');
    //WebGLのコンテキスト取得
    gl=getContext(canvas);
    //nullだったらreturnしてmain関数を抜ける
    if(gl===null)
    {
        return;
    }
    //頂点シェーダを作成
    var vertexShader=createShader(gl.VERTEX_SHADER,vertexShaderSource);
    //nullだったらreturnしてmain関数を抜ける
    if(vertexShader===null)
    {
        return;
    }
    //フラグメントシェーダを作成
    var fragmentShader=createShader(gl.FRAGMENT_SHADER,fragmentShaderSource);
    //nullだったらreturnしてmain関数を抜ける
    if(fragmentShader===null)
    {
        return;
    }
    //プログラムを作成
    var program=createProgram(vertexShader,fragmentShader);
    //nullだったらreturnしてmain関数を抜ける
    if(program===null)
    {
        return;
    }
    //頂点の配列
    var vertex=
    [
        //x,y
        0.0,0.5
        //x,y
        ,-0.5,-0.5
        //x,y
        ,0.5,-0.5
    ];
    //バッファを作成
    var buffer=createBuffer(vertex);
    //nullだったらreturnしてmain関数を抜ける
    if(buffer===null)
    {
        return;
    }
    //attribute変数取得
    var position=getAttribute(program,'a_Position');
    //nullだったらreturnしてmain関数を抜ける
    if(position===null)
    {
        return;
    }
    //attribute変数にバッファをひもづけて有効にする
    bindAttribute(position,buffer,2);
    //RGBAの順番にカラーバッファをクリアする色を指定する
    gl.clearColor(0.5,0.5,0.5,1.0);
    //カラーバッファをクリアする
    gl.clear(gl.COLOR_BUFFER_BIT);
    //clearメソッドのエラーチェック
    checkGLError('clear');
    //使用するプログラムをWebGLに設定する
    gl.useProgram(program);
    //useProgramメソッドのエラーチェック
    checkGLError('useProgram');
    //描画する
    gl.drawArrays(gl.POINTS,0,3);
    //drawArraysメソッドのエラーチェック
    checkGLError('drawArrays');
    //attribute変数とバッファのひもづけを無効にする
    unbindAttribute(position);
    //バッファ削除
    deleteBuffer(buffer);
    //プログラム削除
    deleteProgram(program,vertexShader,fragmentShader);
    //頂点シェーダ削除
    deleteShader(vertexShader);
    //フラグメントシェーダ削除
    deleteShader(fragmentShader);
};
//ページロード時に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);
        }
    }
};

var getContext=function(canvas)
{
    //WebGLのコンテキストを入れる変数
    var gl=null;
    //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で関数を抜ける
    if(!gl)
    {
        console.log('no gl');
        return null;
    }
    return gl;
};

var createShader=function(type,source)
{
    //渡されたtype引数のシェーダを作成
    var shader=gl.createShader(type);
    //createShaderメソッドのエラーチェック
    checkGLError('createShader');
    //作成できたかチェック
    if(shader===null)
    {
        console.log('no shader');
        return null;
    }
    //シェーダとソースコードをひもづける
    gl.shaderSource(shader,source);
    //shaderSourceメソッドのエラーチェック
    checkGLError('shaderSource');
    //ソースコードをコンパイルする
    gl.compileShader(shader);
    //compileShaderメソッドのエラーチェック
    checkGLError('compileShader');
    //コンパイル状態を取得
    var result=gl.getShaderParameter(shader,gl.COMPILE_STATUS);
    //getShaderParameterメソッドのエラーチェック
    checkGLError('getShaderParameter');
    //コンパイルが成功しているかどうか
    if(result===false)
    {
        console.log('failed compile');
        //シェーダのログを取得
        var log=gl.getShaderInfoLog(shader);
        //getShaderInfoLogメソッドのエラーチェック
        checkGLError('getShaderInfoLog');
        if(log===null)
        {
            console.log('no log');
        }
        else
        {
            console.log(log);
        }
        return null;
    }
    //シェーダを返す
    return shader;
};

var createProgram=function(vertexShader,fragmentShader)
{
    //プログラム作成
    var program=gl.createProgram();
    //作成できたかチェック
    if(program===null)
    {
        console.log('no program');
        return null;
    }
    //頂点シェーダをプログラムにひもづける
    gl.attachShader(program,vertexShader);
    //attachShaderメソッドのエラーチェック
    checkGLError('attachShader');
    //フラグメントシェーダをプログラムにひもづける
    gl.attachShader(program,fragmentShader);
    //attachShaderメソッドのエラーチェック
    checkGLError('attachShader');
    //頂点シェーダとフラグメントシェーダにリンク処理を行う
    gl.linkProgram(program);
    //linkProgramメソッドのエラーチェック
    checkGLError('linkProgram');
    //リンクの状態を取得
    var result=gl.getProgramParameter(program,gl.LINK_STATUS);
    //getProgramParameterメソッドのエラーチェック
    checkGLError('getProgramParameter');
    //リンクが成功しているかどうか
    if(result===false)
    {
        console.log('failed link');
        //プログラムのログを取得
        var log=gl.getProgramInfoLog(program);
        //getProgramInfoLogメソッドのエラーチェック
        checkGLError('getProgramInfoLog');
        if(log===null)
        {
            console.log('no log');
        }
        else
        {
            console.log(log);
        }
        return null;
    }
    //プログラムを返す
    return program;
};

var deleteProgram=function(program,vertexShader,fragmentShader)
{
    //頂点シェーダとプログラムのひもづけを切る
    gl.detachShader(program,vertexShader);
    //detachShaderメソッドのエラーチェック
    checkGLError('detachShader');
    //フラグメントシェーダとプログラムのひもづけを切る
    gl.detachShader(program,fragmentShader);
    //detachShaderメソッドのエラーチェック
    checkGLError('detachShader');
    //プログラムを削除する
    gl.deleteProgram(program);
    //deleteProgramメソッドのエラーチェック
    checkGLError('deleteProgram');
};

var deleteShader=function(shader)
{
    //シェーダを削除する
    gl.deleteShader(shader);
    //deleteShaderメソッドのエラーチェック
    checkGLError('deleteShader');
};

var createBuffer=function(value)
{
    //バッファ作成
    var buffer=gl.createBuffer();
    //createBufferメソッドのエラーチェック
    checkGLError('createBuffer');
    //作成できたかチェック
    if(buffer===null)
    {
        console.log('no buffer');
        return;
    }
    //バッファをバインドする
    bindBuffer(buffer);
    //バッファに値を書き込む
    gl.bufferData(gl.ARRAY_BUFFER,new Float32Array(value),gl.STATIC_DRAW);
    //bufferDataメソッドのエラーチェック
    checkGLError('bufferData');
    //バッファをアンバインドする
    unbindBuffer();
    //バッファを返す
    return buffer;
};

var bindBuffer=function(buffer)
{
    //バッファをバインドする
    gl.bindBuffer(gl.ARRAY_BUFFER,buffer);
    //bindBufferメソッドのエラーチェック
    checkGLError('bindBuffer');
};

var unbindBuffer=function()
{
    //バッファをアンバインドする
    gl.bindBuffer(gl.ARRAY_BUFFER,null);
    //bindBufferメソッドのエラーチェック
    checkGLError('bindBuffer');
};

var deleteBuffer=function(buffer)
{
    //バッファを削除する
    gl.deleteBuffer(buffer);
    //deleteBufferメソッドのエラーチェック
    checkGLError('deleteBuffer');
};

var getAttribute=function(program,name)
{
    //プログラムからattribute変数を取得する
    var location=gl.getAttribLocation(program,name);
    //getAttribLocationメソッドのエラーチェック
    checkGLError('getAttribLocation');
    //取得できたかチェック
    if(location===-1)
    {
        console.log('no attribute');
        return;
    }
    //attribute変数を返す
    return location;
};

var bindAttribute=function(location,buffer,size)
{
    //バッファをバインドする
    bindBuffer(buffer);
    //attribute変数にバッファをひもづける
    gl.vertexAttribPointer(location,size,gl.FLOAT,false,0,0);
    //vertexAttribPointerメソッドのエラーチェック
    checkGLError('vertexAttribPointer');
    //バッファをアンバインドする
    unbindBuffer();
    //バッファのひもづけを有効にする
    gl.enableVertexAttribArray(location);
    //enableVertexAttribArrayメソッドのエラーチェック
    checkGLError('enableVertexAttribArray');
};

var unbindAttribute=function(location)
{
    //バッファのひもづけを無効にする
    gl.disableVertexAttribArray(location);
    //disableVertexAttribArrayメソッドのエラーチェック
    checkGLError('disableVertexAttribArray');
};

シェーダなどは第2回で説明しているので、もし不安があれば見なおしてみてほしい。
では、第2回からの変更点を見ていこう。


まず、8行目~15行目に書かれている頂点シェーダのソースコード。
//頂点シェーダ
var vertexShaderSource='attribute vec4 a_Position;\
\
void main()\
{\
    gl_Position=a_Position;\
    gl_PointSize=10.0;\
}';
ソースコードの一番最初にattribute vec4 a_Position;というのがある。
これは、a_Positionという名前でvec4型のattribute変数を定義している。
attribute変数というのは頂点ごとに値が変わる場合に使用出来る変数で、
今回はまさに頂点の座標の値を渡すためattribute変数を使う。
そしてmain関数の中をみてみると、gl_Positionにa_Positionを代入している。
なんとこれだけで、頂点ごとに異なる座標を指定する頂点シェーダが出来上がる。


フラグメントシェーダは変化なしなので、JavaScript側のmain関数内の変更点を見よう。55行目~64行目。
//頂点の配列
var vertex=
[
    //x,y
    0.0,0.5
    //x,y
    ,-0.5,-0.5
    //x,y
    ,0.5,-0.5
];
配列にたくさんの値を入れて初期化しているがこれは頂点座標の値で、
最初の行の0.0が1つ目の頂点のx座標、0.5がy座標、
次の行の-0.5が2つ目の頂点のx座標、-0.5がy座標、
3行目の0.5が3つ目の頂点のx座標、-0.5がy座標という風に並べて代入している。


65行目~66行目にcreateBufferという関数が使われている。
//バッファを作成
var buffer=createBuffer(vertex);
引数に先ほどの頂点座標を渡している。


中で何をしているかを見るために273行目~295行目を見てみる。
var createBuffer=function(value)
{
    /*
    省略
    */
};
具体的に何をしているかを少しずつ見ていこう。


まず275行目~278行目。
//バッファ作成
var buffer=gl.createBuffer();
//createBufferメソッドのエラーチェック
checkGLError('createBuffer');
createBufferメソッドを使ってバッファを作成している。
実はバッファなしでも頂点座標を渡すことが出来る。
ではなぜバッファが必要かというと、
キャラクタのポリゴンなどを描画しようと思うと何万もの頂点座標が必要になったりする。
描画するたびにJavaScriptからWebGLにコピーして渡すのは非常に非効率で低速なので、
WebGLのバッファに予め頂点座標の値を渡しておき、
バッファを描画の時に使うようにすることによって高速な描画が可能になる。


次に279行目~284行目。
//作成できたかチェック
if(buffer===null)
{
    console.log('no buffer');
    return null;
}
バッファが作成できなかった場合は、
コンソールにメッセージを出してnullを返している。


285行目~286行目。
//バッファをバインドする
bindBuffer(buffer);
bindBufferという関数を呼び出しバッファを渡している。


bindBufferは297行目~303行目に定義されている。
var bindBuffer=function(buffer)
{
    //バッファをバインドする
    gl.bindBuffer(gl.ARRAY_BUFFER,buffer);
    //bindBufferメソッドのエラーチェック
    checkGLError('bindBuffer');
};
bindBufferメソッドを呼び出し、ARRAY_BUFFER定数と引数で受け取ったバッファを渡している。
これは、WebGLのARRAY_BUFFERに渡されたバッファをひもづけるという効果がある。


287行目~290行目に戻り更に見ていこう。
//バッファに値を書き込む
gl.bufferData(gl.ARRAY_BUFFER,new Float32Array(value),gl.STATIC_DRAW);
//bufferDataメソッドのエラーチェック
checkGLError('bufferData');
bufferDataメソッドを呼び出している。
引数をみてみると先ほど登場したARRAY_BUFFERが再び登場している。
また第2引数をよくみてみると、
createBuffer関数で受け取った値を引数としてFloat32Arrayというクラスのインスタンスを作っている。
これはJavaScriptの型付き配列と呼ばれるものだ。
JavaScriptは型が柔軟な言語だがWebGLは型が厳密なので、そのままの値を渡されると都合が悪い。
そのため、何の型の値なのかを明示的に伝えるべく型付き配列で渡すことになっている。
第3引数はこのデータの使われ方をヒントとしてWebGLに教えるためのものだ。
基本的にSTATIC_DRAW定数で問題ないと思う。
このメソッドを呼ぶことにより値がARRAY_BUFFERに書き込まれ、
事前にARRAY_BUFFERとバッファをひもづけていたため、
結果バッファに値が書き込まれることになる。


291行目~292行目。
//バッファをアンバインドする
unbindBuffer();
unbindBufferという関数を呼び出している。


unbindBufferの定義は305行目~311行目にある。
var unbindBuffer=function()
{
    //バッファをアンバインドする
    gl.bindBuffer(gl.ARRAY_BUFFER,null);
    //bindBufferメソッドのエラーチェック
    checkGLError('bindBuffer');
};
bindBufferメソッドを呼び出し、ARRAY_BUFFER定数とnullを渡している。
ぱっとみbindBuffer関数と同じに見えるが、第2引数にnullを渡しているところが異なる。
nullを渡すことによってARRAY_BUFFERとバッファのひもづけを切る効果がある


最後に293行目~294行目。
//バッファを返す
return buffer;
値が書き込まれたバッファを返している。


JavaScriptのmain関数に戻り67行目~71行目。
//nullだったらreturnしてmain関数を抜ける
if(buffer===null)
{
    return;
}
バッファが作成できなかった場合は、
returnしてmain関数を抜けている。


72行目~73行目でgetAttributeという関数が呼ばれている。
//attribute変数取得
var position=getAttribute(program,'a_Position');
引数としてプログラムとattribute変数の名前を渡している。


321行目~335行目にある定義を見てみよう。
var getAttribute=function(program,name)
{
    /*
    省略
    */
};
では中で何をしているかを見ていこう。


323行目~326行目。
//プログラムからattribute変数を取得する
var location=gl.getAttribLocation(program,name);
//getAttribLocationメソッドのエラーチェック
checkGLError('getAttribLocation');
getAttribLocationメソッドにプログラムとattribute変数の名前を渡すと、
渡された名前のattribute変数(厳密にはattribute変数が格納されている場所)が取得できる。
ただし取得できなかった時には-1が返ってくるようになっている。


327行目~332行目で取得できたかチェックする。
//取得できたかチェック
if(location===-1)
{
    console.log('no attribute');
    return;
}
attribute変数が取得できなかった場合は、
コンソールにメッセージを出してnullを返している。


最後に333行目~334行目。
//attribute変数を返す
return location;
attribute変数を返している。


74行目~78行目に戻ろう。
//nullだったらreturnしてmain関数を抜ける
if(position===null)
{
    return;
}
attribute変数が取得できなかった場合は、
returnしてmain関数を抜けている。


次に79行目~80行目。
//attribute変数にバッファをひもづけて有効にする
bindAttribute(position,buffer,2);
第1引数にattribute変数、第2引数にバッファ、第3引数に頂点あたりの値の数(1~4)を渡している。
今回はx座標とy座標を頂点に渡すので頂点あたりの値の数は2である。


bindAttributeの定義は337行目~351行目にある。
var bindAttribute=function(location,buffer,size)
{
    /*
    省略
    */
};
では具体的に見ていこう。


339行目~340行目。
//バッファをバインドする
bindBuffer(buffer);
再びbindBuffer関数を呼び出している。


341行目~342行目で実際にattribute変数にバッファをひもづけている。
//attribute変数にバッファをひもづける
gl.vertexAttribPointer(location,size,gl.FLOAT,false,0,0);
//vertexAttribPointerメソッドのエラーチェック
checkGLError('vertexAttribPointer');
vertexAttribPointerメソッドで第1引数にattribute変数を、
第2引数に頂点あたりの値の数(1~4)を、
第3引数に値の型を、
第4引数に値が整数の時に正規化をするかどうかのフラグを、
第5引数に値のストライドを、
第6引数にバッファのどこから値が始まるかを渡す。
第5引数については第8回で説明する。


345行目~346行目。
//バッファをアンバインドする
unbindBuffer();
unbindBuffer関数もきちんと呼んでおく。


347行目~350行目で実際にひもづけが有効になる。
//バッファのひもづけを有効にする
gl.enableVertexAttribArray(location);
//enableVertexAttribArrayメソッドのエラーチェック
checkGLError('enableVertexAttribArray');
enableVertexAttribArrayメソッドにattribute変数を渡すことによって、
attribute変数にひもづけられたバッファが有効になる。


91行目~94行目でdrawArraysメソッドを呼び実際に描画する。
//描画する
gl.drawArrays(gl.POINTS,0,3);
//drawArraysメソッドのエラーチェック
checkGLError('drawArrays');
第2回で説明したとおりdrawArraysメソッドの第3引数は描画する頂点の数なので、
今回は3つの点を描画するので3を指定する。
以上で3つの点が描画される。


95行目~96行目で後片付けをしている。
//attribute変数とバッファのひもづけを無効にする
unbindAttribute(position);
attribute変数とバッファのひもづけを無効化しておく。


353行目~359行目にそのunbindAttributeの定義がある。
var unbindAttribute=function(location)
{
    //バッファのひもづけを無効にする
    gl.disableVertexAttribArray(location);
    //disableVertexAttribArrayメソッドのエラーチェック
    checkGLError('disableVertexAttribArray');
};
disableVertexAttribArrayメソッドにattribute変数を渡すことによって、
ひもづいているバッファを無効化することが出来る。


次に97行目~98行目。
//バッファ削除
deleteBuffer(buffer);
バッファも使い終わったら削除してしまう。


deleteBufferが定義されてある313行目~319行目を見てみる。
var deleteBuffer=function(buffer)
{
    //バッファを削除する
    gl.deleteBuffer(buffer);
    //deleteBufferメソッドのエラーチェック
    checkGLError('deleteBuffer');
};
deleteBufferメソッドにバッファを渡すことによってそのバッファを削除することが出来る。


以上で説明は終わり。
今回はバッファとattribute変数の使い方を説明した。
これを使いこなすことによって自在に好きな座標に描画することが出来るようになる。
バッファの作成をまとめてみると。
  1. バッファを作成する。(createBuffer)
  2. バッファをバインドする。(bindBuffer)
  3. バッファに値を書き込む。(bufferData)
  4. バッファをアンバインドする。(bindBuffer)
要点はこれだけ。
同様にattribute変数もまとめてみると。
  1. attribute変数を取得する。(getAttribLocation)
  2. バッファをバインドする。(bindBuffer)
  3. attribute変数とバッファをひもづける。(vertexAttribPointer)
  4. バッファをアンバインドする。(bindBuffer)
  5. attribute変数とバッファのひもづけを有効にする。(enableVertexAttribArray)
相変わらずエラーチェックなどチェックが多く入るため、
複雑に感じるが実際はこれだけである。


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

2013年3月2日土曜日

[WebGL][JavaScript][HTML] 第2回 WebGLで遊んでみる(点の描画)

第1回は描画領域クリアまで説明した。
今回は最も簡単な図形・・・点の描画を説明していこうと思う。
また、今回からもっとも重要な要素の1つシェーダが登場する。

描画結果(画像)


真ん中に青い点が表示されている。
たったこれだけだが、シェーダが登場するため、
ソースコードは少し長い。

HTML部分。
//link・・・http://mio-koduki.blogspot.jp/2013/03/webgljavascripthtml-webgl2.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>

まぁ、第1回と同じなので特に解説は要らないであろう。
canvasを用意しておくだけである。

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

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

//頂点シェーダ
var vertexShaderSource='void main()\
{\
    gl_Position=vec4(0.0,0.0,0.0,1.0);\
    gl_PointSize=10.0;\
}';

//フラグメントシェーダ
var fragmentShaderSource='void main()\
{\
    gl_FragColor=vec4(0.0,0.0,1.0,1.0);\
}';

var main=function()
{
    //canvas要素を取得する
    var canvas=document.getElementById('canvas');
    //WebGLのコンテキスト取得
    gl=getContext(canvas);
    //nullだったらreturnしてmain関数を抜ける
    if(gl===null)
    {
        return;
    }
    //頂点シェーダを作成
    var vertexShader=createShader(gl.VERTEX_SHADER,vertexShaderSource);
    //nullだったらreturnしてmain関数を抜ける
    if(vertexShader===null)
    {
        return;
    }
    //フラグメントシェーダを作成
    var fragmentShader=createShader(gl.FRAGMENT_SHADER,fragmentShaderSource);
    //nullだったらreturnしてmain関数を抜ける
    if(fragmentShader===null)
    {
        return;
    }
    //プログラムを作成
    var program=createProgram(vertexShader,fragmentShader);
    //nullだったらreturnしてmain関数を抜ける
    if(program===null)
    {
        return;
    }
    //RGBAの順番にカラーバッファをクリアする色を指定する
    gl.clearColor(0.5,0.5,0.5,1.0);
    //カラーバッファをクリアする
    gl.clear(gl.COLOR_BUFFER_BIT);
    //clearメソッドのエラーチェック
    checkGLError('clear');
    //使用するプログラムをWebGLに設定する
    gl.useProgram(program);
    //useProgramメソッドのエラーチェック
    checkGLError('useProgram');
    //描画する
    gl.drawArrays(gl.POINTS,0,1);
    //drawArraysメソッドのエラーチェック
    checkGLError('drawArrays');
    //プログラム削除
    deleteProgram(program,vertexShader,fragmentShader);
    //頂点シェーダ削除
    deleteShader(vertexShader);
    //フラグメントシェーダ削除
    deleteShader(fragmentShader);
};
//ページロード時に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);
        }
    }
};

var getContext=function(canvas)
{
    //WebGLのコンテキストを入れる変数
    var gl=null;
    //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で関数を抜ける
    if(!gl)
    {
        console.log('no gl');
        return null;
    }
    return gl;
};

var createShader=function(type,source)
{
    //渡されたtype引数のシェーダを作成
    var shader=gl.createShader(type);
    //createShaderメソッドのエラーチェック
    checkGLError('createShader');
    //作成できたかチェック
    if(shader===null)
    {
        console.log('no shader');
        return null;
    }
    //シェーダとソースコードをひもづける
    gl.shaderSource(shader,source);
    //shaderSourceメソッドのエラーチェック
    checkGLError('shaderSource');
    //ソースコードをコンパイルする
    gl.compileShader(shader);
    //compileShaderメソッドのエラーチェック
    checkGLError('compileShader');
    //コンパイル状態を取得
    var result=gl.getShaderParameter(shader,gl.COMPILE_STATUS);
    //getShaderParameterメソッドのエラーチェック
    checkGLError('getShaderParameter');
    //コンパイルが成功しているかどうか
    if(result===false)
    {
        console.log('failed compile');
        //シェーダのログを取得
        var log=gl.getShaderInfoLog(shader);
        //getShaderInfoLogメソッドのエラーチェック
        checkGLError('getShaderInfoLog');
        if(log===null)
        {
            console.log('no log');
        }
        else
        {
            console.log(log);
        }
        return null;
    }
    //シェーダを返す
    return shader;
};

var createProgram=function(vertexShader,fragmentShader)
{
    //プログラム作成
    var program=gl.createProgram();
    //作成できたかチェック
    if(program===null)
    {
        console.log('no program');
        return null;
    }
    //頂点シェーダをプログラムにひもづける
    gl.attachShader(program,vertexShader);
    //attachShaderメソッドのエラーチェック
    checkGLError('attachShader');
    //フラグメントシェーダをプログラムにひもづける
    gl.attachShader(program,fragmentShader);
    //attachShaderメソッドのエラーチェック
    checkGLError('attachShader');
    //頂点シェーダとフラグメントシェーダにリンク処理を行う
    gl.linkProgram(program);
    //linkProgramメソッドのエラーチェック
    checkGLError('linkProgram');
    //リンクの状態を取得
    var result=gl.getProgramParameter(program,gl.LINK_STATUS);
    //getProgramParameterメソッドのエラーチェック
    checkGLError('getProgramParameter');
    //リンクが成功しているかどうか
    if(result===false)
    {
        console.log('failed link');
        //プログラムのログを取得
        var log=gl.getProgramInfoLog(program);
        //getProgramInfoLogメソッドのエラーチェック
        checkGLError('getProgramInfoLog');
        if(log===null)
        {
            console.log('no log');
        }
        else
        {
            console.log(log);
        }
        return null;
    }
    //プログラムを返す
    return program;
};

var deleteProgram=function(program,vertexShader,fragmentShader)
{
    //頂点シェーダとプログラムのひもづけを切る
    gl.detachShader(program,vertexShader);
    //detachShaderメソッドのエラーチェック
    checkGLError('detachShader');
    //フラグメントシェーダとプログラムのひもづけを切る
    gl.detachShader(program,fragmentShader);
    //detachShaderメソッドのエラーチェック
    checkGLError('detachShader');
    //プログラムを削除する
    gl.deleteProgram(program);
    //deleteProgramメソッドのエラーチェック
    checkGLError('deleteProgram');
};

var deleteShader=function(shader)
{
    //シェーダを削除する
    gl.deleteShader(shader);
    //deleteShaderメソッドのエラーチェック
    checkGLError('deleteShader');
};

では、第1回から変わったところを詳しく見ていこう。


まず、8行目~13行目になにやら怪しいソースコードのようなものが書かれている。
//頂点シェーダ
var vertexShaderSource='void main()\
{\
 gl_Position=vec4(0.0,0.0,0.0,1.0);\
 gl_PointSize=10.0;\
}';
これは頂点シェーダという、頂点座標ごとの処理を定義しているソースコードで、
今回は描画する点の座標についての処理を定義している。
また、シェーダはGLSLという言語を用いて書く必要がある。
わざわざ新しい言語覚えるのかぁ・・・と思う必要もなく、そこまで特殊な文法は存在しない。
基本文法はC言語と似ていて、JavaScriptがわかるならある程度読み解けるだろう。
わからないことがあれば必要に応じて調べて覚えていく程度でもなんとかなるだろう。
さて、実際ここではvertexShaderSource変数に頂点シェーダのソースコードを文字列として代入している。
ソースコードは最初にmain関数を定義している。
これはGLSLもCと同じくmain関数から始まるという決まりがあるからだ。
main関数内ではgl_Positionにvec4(0.0,0.0,0.0,1.0);を代入している。
簡単に説明すると、x座標0.0、y座標0.0、z座標0.0、wに1.0を指定したvec4型を作り、
それを表示座標に代入している。
x座標、y座標、z座標はなんとなくわかるが、
wはあんまりピンと来ない。
すぐに使うわけじゃないのでざっくりとした説明だけにするが、
行列計算の時にあると便利だからついてる。
今はそのぐらいの認識でもいいと思う。
vec4型とはfloat型が4つ入る型。
もうちょっと言えば4つのfloat型によって表されるベクトルの値が入る型である。
次に、gl_PointSizeに10.0を代入しているが、これは点の大きさを定義しているだけだ。
なお、文字列中の各行末にある「\(バックスラッシュ、環境によっては円マーク)」はJavaScriptで複数行文字列リテラルを扱うために必要な物だ。


15行目~19行目も同様にフラグメントシェーダのソースコードがかかれている。
//フラグメントシェーダ
var fragmentShaderSource='void main()\
{\
 gl_FragColor=vec4(0.0,0.0,1.0,1.0);\
}';
頂点シェーダと同様にフラグメントシェーダもGLSLで書かれている。
頂点・・・はまぁ、なんとなくわかるが、フラグメントってなに?と思うだろうが、
ざっくりといえば頂点で結ばれた線の内側の一つ一つのピクセル(厳密にはピクセルではないが)ぐらいの認識でいいと思う。
つまり3つの頂点を定義し三角形を作ったとしたらその三角形の内部全てに
このフラグメントシェーダが適応される。
頂点シェーダと同様にフラグメントシェーダでもmain関数を定義している。
そして、その中でgl_FragColorにvec4(0.0,0.0,1.0,1.0)を代入している。
これは、このフラグメントの色をRGBAの順に指定したvec4型を作り代入している。
つまりは今回は青色でフラグメントを描画する。という指定だ。


次にJavaScript側のmain関数で行なっている処理を見ていこう。まず25行目~26行目。
//WebGLのコンテキスト取得
gl=getContext(canvas);
第1回にはなかったgetContextという関数にcanvas要素を渡し、
返り値をgl変数で受け取っている。


しかしよくよくgetContextが定義してある92行目~121行目を見てみると。
var getContext=function(canvas)
{
    //WebGLのコンテキストを入れる変数
    var gl=null;
    //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で関数を抜ける
    if(!gl)
    {
        console.log('no gl');
        return null;
    }
    return gl;
};
やってることは第1回と一緒でWebGLのコンテキストを取得しているだけである。
そしてそれを関数の返り値として返している。


そして27行目~31行目。
//nullだったらreturnしてmain関数を抜ける
if(gl===null)
{
    return;
}
WebGLのコンテキストが取得できなかった場合はreturnしてmain関数を終了している。


32行目~33行目にcreateShaderという関数が使われている。
//頂点シェーダを作成
var vertexShader=createShader(gl.VERTEX_SHADER,vertexShaderSource);
引数にWebGLの定数と上部で定義したvertexShaderSourceを渡している。


中で何をしているかは123行目~167行目に書いてある。
var createShader=function(type,source)
{
    /*
    省略
    */
};
具体的に何をしているかを少しずつ見ていこう。


まず125行目~128行目。
//渡されたtype引数のシェーダを作成
var shader=gl.createShader(type);
//createShaderメソッドのエラーチェック
checkGLError('createShader');
WebGLのコンテキストにあるcreateShaderメソッドに第1引数で渡ってきたtypeという変数を渡している。
上記でも少し触れたがシェーダは頂点シェーダとフラグメントシェーダがあるため、
どっちのシェーダを作るのかを指定するためにtypeを渡してもらう必要がある。
33行目ではgl.VERTEX_SHADERを渡しているので、頂点シェーダを作ることになる。
またその下の行では第1回説明したとおり返り値や例外でエラーをキャッチできないので、
エラーチェック用の関数を定義し呼んでいる。


次に129行目~134行目。
//作成できたかチェック
if(shader===null)
{
    console.log('no shader');
    return null;
}
シェーダが作成できなかった場合は、
コンソールにメッセージを出してnullを返している。


135行目~138行目。
//シェーダとソースコードをひもづける
gl.shaderSource(shader,source);
//shaderSourceメソッドのエラーチェック
checkGLError('shaderSource');
shaderSourceメソッドを使いではシェーダにソースコードをひもづけている。


そして139行目~164行目。
//ソースコードをコンパイルする
gl.compileShader(shader);
//compileShaderメソッドのエラーチェック
checkGLError('compileShader');
//コンパイル状態を取得
var result=gl.getShaderParameter(shader,gl.COMPILE_STATUS);
//getShaderParameterメソッドのエラーチェック
checkGLError('getShaderParameter');
//コンパイルが成功しているかどうか
if(result===false)
{
    console.log('failed compile');
    //シェーダのログを取得
    var log=gl.getShaderInfoLog(shader);
    //getShaderInfoLogメソッドのエラーチェック
    checkGLError('getShaderInfoLog');
    if(log===null)
    {
        console.log('no log');
    }
    else
    {
        console.log(log);
    }
    return null;
}
compileShaderメソッドを使いひもづけられているソースコードをコンパイルする。
コンパイルはC言語やJavaをやったことがある人にとっては馴染み深い言葉だが、
JavaScriptやPHPなどしかしたことない人にとっては初めて見る言葉かもしれない。
ざっくりといえばソースコードをコンピュータが理解しやすい形式(一般的にはバイナリコード)に変換することだ。
もしソースコードの文法に誤りがあったりした際にはコンパイルが失敗する。
そのため、getShaderParameterメソッドにシェーダとgl.COMPILE_STATUSを渡し、
コンパイルが成功したかどうかをチェックしている。
返り値がfalseのときはコンパイルに失敗しているため、
getShaderInfoLogメソッドを呼び、ログの取得を試みる。
もし、ログがあればコンソールにログを、なければ素直に簡単なメッセージを出し、return nullをしている。


最後に165行目~166行目。
//シェーダを返す
return shader;
すべての手順が成功した際にシェーダを返している。


再びJavaScriptのmain関数に戻り35行目~38行目。
//nullだったらreturnしてmain関数を抜ける
if(vertexShader===null)
{
    return;
}
シェーダが作成できなかった場合は、
returnしてmain関数を抜けている。


39行目~45行目で今度はフラグメントシェーダを作成する。
//フラグメントシェーダを作成
var fragmentShader=createShader(gl.FRAGMENT_SHADER,fragmentShaderSource);
//nullだったらreturnしてmain関数を抜ける
if(fragmentShader===null)
{
    return;
}
頂点シェーダの時と同じくcreateShader関数を呼んでいるが、
引数にgl.FRAGMENT_SHADERとフラグメントシェーダのソースコードを渡しているところが異なる。
そして、作成に失敗したときは同様にreturnでmain関数を抜けている。


そして46行目~47行目。
//プログラムを作成
var program=createProgram(vertexShader,fragmentShader);
今まで作った頂点シェーダとフラグメントシェーダを渡し、createProgramという関数を呼んでいる。


createProgram関数の定義は169行目~215行目にある。
var createProgram=function(vertexShader,fragmentShader)
{
    /*
    省略
    */
}
createProgram関数は頂点シェーダとフラグメントシェーダを受け取り、プログラムを返すようになってる。
プログラムという言葉が初めて出てきたが、
簡単にいえばシェーダを管理する入れ物のようなもので、
プログラムにシェーダをひもづけることによって初めてシェーダが使えるようになる。
ではcreateProgram関数がどうやってプログラムを作っているか見てみよう。


最初に171行目~172行目。
//プログラム作成
var program=gl.createProgram();
WebGLのコンテキストにあるcreateProgramメソッドを呼ぶことによりプログラムが作られる。


次に173行目~178行目。
//作成できたかチェック
if(program===null)
{
    console.log('no program');
    return null;
}
プログラムが作成できなかった場合は、
コンソールにメッセージを出してnullを返している。


179行目~186行目。
//頂点シェーダをプログラムにひもづける
gl.attachShader(program,vertexShader);
//attachShaderメソッドのエラーチェック
checkGLError('attachShader');
//フラグメントシェーダをプログラムにひもづける
gl.attachShader(program,fragmentShader);
//attachShaderメソッドのエラーチェック
checkGLError('attachShader');
attachShaderメソッドにプログラムとシェーダを渡すことによってその2つをひもづけることができる。
プログラムには必ず頂点シェーダとフラグメントシェーダをひもづけなければならないので、
それぞれに対してattachShaderメソッドを呼んであげる。


そして187行目~212行目。
//頂点シェーダとフラグメントシェーダにリンク処理を行う
gl.linkProgram(program);
//linkProgramメソッドのエラーチェック
checkGLError('linkProgram');
//リンクの状態を取得
var result=gl.getProgramParameter(program,gl.LINK_STATUS);
//getProgramParameterメソッドのエラーチェック
checkGLError('getProgramParameter');
//リンクが成功しているかどうか
if(result===false)
{
    console.log('failed link');
    //プログラムのログを取得
    var log=gl.getProgramInfoLog(program);
    //getProgramInfoLogメソッドのエラーチェック
    checkGLError('getProgramInfoLog');
    if(log===null)
    {
        console.log('no log');
    }
    else
    {
        console.log(log);
    }
    return null;
}
linkProgramメソッドを使うと頂点シェーダとフラグメントシェーダでリンクさせるのに必要なチェックが行われる。
今回は使用してないが、attribute変数やuniform変数、varying変数などを使用する際に、
頂点シェーダとフラグメントシェーダでの整合性や、使用上限を超えてないかなどがチェックされる。
attribute変数は第3回、uniform変数は第4回、varying変数は第5回に説明する。
リンクに成功したかどうかはgetProgramParameterメソッドにプログラムとgl.LINK_STATUSを渡す事によって、
調べることができる。
返り値がfalseのときはリンクに失敗しているため、
getProgramInfoLogメソッドを呼び、ログの取得を試みる。
もし、ログがあればコンソールにログを、なければ素直に簡単なメッセージを出し、return nullをしている。


最後に213行目~214行目。
//プログラムを返す
return program;
すべての手順が成功した際にプログラムを返している。


再びJavaScriptのmain関数に戻り48行目~52行目。
//nullだったらreturnしてmain関数を抜ける
if(program===null)
{
    return;
}
プログラムが作成できなかった場合は、
returnしてmain関数を抜けている。


53行目~58行目では第1回でもやったようにカラーバッファをクリアしている。
//RGBAの順番にカラーバッファをクリアする色を指定する
gl.clearColor(0.5,0.5,0.5,1.0);
//カラーバッファをクリアする
gl.clear(gl.COLOR_BUFFER_BIT);
//clearメソッドのエラーチェック
checkGLError('clear');
今回も灰色でクリアする。


59行目~62行目で作成したプログラムを使ってる。
//使用するプログラムをWebGLに設定する
gl.useProgram(program);
//useProgramメソッドのエラーチェック
checkGLError('useProgram');
useProgramメソッドを呼ぶことによってWebGLにこれから使用するプログラムを教えてあげる。


そして63行目~66行目によって実際に描画される。
//描画する
gl.drawArrays(gl.POINTS,0,1);
//drawArraysメソッドのエラーチェック
checkGLError('drawArrays');
drawArraysメソッドの第1引数にどういう方法で描画するかを指定する。
例えば線として描画や三角形として描画させるなどができるが、
今回は点を描画するのでgl.POINTSを指定している。
drawArraysと複数形になってるだけありJavaScriptから座標を渡してあげると、
このメソッドは複数の頂点を同時に描画できる。
そのため第2引数で何番目の頂点から描画するかを、
第3引数で何個描画するかを指定する。
今回はJavaScriptから頂点を渡さずに頂点シェーダに直接頂点を指定しているので、
第2引数には何を指定したとしても変わらない。
そして点を1個描画するので、第3引数は1になっている。
このメソッドを呼ぶことによって実際に画面に描画される。


さて、67行目~68行目。
//プログラム削除
deleteProgram(program,vertexShader,fragmentShader);
つかい終わったらいらないものは削除しておいたほうがいい。
deleteProgram関数にプログラムと頂点シェーダ、フラグメントシェーダを渡している。


deleteProgramが定義されてある217行目~231行目を見てみる。
var deleteProgram=function(program,vertexShader,fragmentShader)
{
    //頂点シェーダとプログラムのひもづけを切る
    gl.detachShader(program,vertexShader);
    //detachShaderメソッドのエラーチェック
    checkGLError('detachShader');
    //フラグメントシェーダとプログラムのひもづけを切る
    gl.detachShader(program,fragmentShader);
    //detachShaderメソッドのエラーチェック
    checkGLError('detachShader');
    //プログラムを削除する
    gl.deleteProgram(program);
    //deleteProgramメソッドのエラーチェック
    checkGLError('deleteProgram');
};
detacheShaderメソッドにプログラムとシェーダを渡すと、ひもづけを切ることができる。
もちろん頂点シェーダとフラグメントシェーダ両方を切らなければいけないので、
それぞれを引数に渡しdetachShaderメソッドを呼んでいる。
そして、deleteProgramメソッドにプログラムを渡すことでそのプログラムを削除している。


69行目~72行目では今度はシェーダを削除している。
//頂点シェーダ削除
deleteShader(vertexShader);
//フラグメントシェーダ削除
deleteShader(fragmentShader);
もちろん頂点シェーダもフラグメントシェーダも削除するので、それぞれを引数に渡して呼び出す。


定義は233行目~239行目にある。
var deleteShader=function(shader)
{
    //シェーダを削除する
    gl.deleteShader(shader);
    //deleteShaderメソッドのエラーチェック
    checkGLError('deleteShader');
};
やってることは単純でdeleteShaderメソッドにシェーダを渡してあげるだけである。


以上で説明は終わり。
とっても長かった・・・。
だが、WebGLを使う上でシェーダは避けては通れない。
シェーダを使いこなすことによって、よりリアルな3DCGキャラクターを表示させたり、
逆にアニメチックな(トゥーン処理)を行うことができる。
セピア調にしてみたりモノクロや、変形、回転、拡大縮小、魚眼レンズや彩度変更などなどなど。
上げればきりがないほど様々なことが可能になる。
ざっくりと今回のことをまとめよう。
  1. シェーダ(頂点シェーダとフラグメントシェーダ)を作る。
  2. プログラムをシェーダから作る。
  3. カラーバッファクリア。
  4. 描画。
  5. プログラム削除。
  6. シェーダ削除。
これだけといえばこれだけ。
また、シェーダの作成もまとめてみると。
  1. シェーダを作成する。(createShader)
  2. シェーダとソースをひもづける。(shaderSource)
  3. ソースをコンパイルする。(compileShader)
要点はこれだけ。
同様にプログラムの作成もまとめてみると。
  1. プログラムを作成する。(createProgram)
  2. プログラムとシェーダ(頂点シェーダとフラグメントシェーダ)をひもづける。(attachShader)
  3. リンクさせる。(linkProgram)
とほぼシェーダと同じ。
エラーチェックやコンパイルチェック、リンクチェックなどチェックが多く入るため、
長く複雑に感じるかもしれないが要点だけみてみると意外とシンプルである。


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