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で遊んでみる(点の描画)へ

0 件のコメント:

コメントを投稿