VertexArrayObject
今回のサンプルの実行結果
拡張機能からの昇格を果たした VAO
前回は、GLSL ES 3.0 で初めて利用することができるようになった、flat 補間モードについて解説しました。
flat 補間モードでは、頂点シェーダからフラグメントシェーダへと値が遷移する際の、データの受け渡し方が変化します。色や、あるいはテクスチャ座標といったようなデータは、これまでの WebGL 1.0 の場合は暗黙の前提として smooth 補間モードで処理されていましたが、そこに干渉できるようになったというのは、なかなか面白いトピックだったのではないでしょうか。
なんでもかんでも使えるテクニックというわけではないと思いますが、状況に応じて、上手に活用していきましょう。
さて今回は VertexArrayObject です。略して VAO と表記されることもあります。
この VAO は WebGL 1.0 の頃は拡張機能として有効化した場合にのみ利用することができました。WebGL 2.0 では、デフォルトで VAO を使うことができるようになっています。また、OpenGL の比較的新しいバージョンにおいては、VAO はむしろ必須となっている機能のひとつです。WebGL 2.0 をベースにした開発を行うのであれば、あらかじめ VAO を使うことを前提にして実装を行うべきだと言えるでしょう。
以前、拡張機能としての VAO の使い方は既に当サイトで解説しています。
参考:WebGL: VAO(vertex array object)
基本的に、WebGL 2.0 になったからといって使い方や考え方が変わるということはありません。従来のとおりの概念を、そのままデフォルトで呼び出せるメソッドによって実行していく感じになります。
今回はおさらいも兼ねつつ、WebGL 2.0 版で実装してみましょう。
そもそも VAO とは
VAO は、WebGL で割と最初に覚えることになる VBO(VertexBufferObject)とつづりが似ているので紛らわしいのですが、意味合いは全然違います。
得てして冗長で煩雑になりがちな頂点バッファやインデックスバッファのバインド処理を、効率的に処理する上で VAO が活躍してくれます。
先ほど引用した以前の記事でも書きましたが、VBO が進化して使いやすくなったとか、そういったものではありません。あくまでも VBO は VBO のまま存在していて、VAO のほうはそれをひとまとめにして保持してくれる、いわばバッファオブジェクトのラッパーのような感じです。
WebGL 2.0 では拡張機能から標準機能に格上げとなり、前もって使えるかどうかを調べる必要はありません。直接、以下のように、VAO を生成することができます。
VertexArrayObject の生成
var vao = gl.createVertexArray();
まあ名前はそのままですが gl.createVertexArray
を呼び出して、新規に VertexArrayObject を生成すればいいんですね。
VAO を用いる場合でも、VBO や IBO を初期化するような処理は基本的にやるべきことは同じです。VAO はあくまでも VBO や IBO をラップしてくれるだけ なので、初期化の手順などがまるきり違ってくるわけじゃないのですね。
今回用意したサンプルでは、VBO や IBO の生成と、それらの VAO へのアタッチまでを一括りにしたオリジナルの関数を定義して使っています。ちょっとその中身を見てみましょう。
VAO 初期化のためのユーザー定義関数
function create_vao(vboDataArray, attL, attS, iboData){
var vao, vbo, ibo, i;
vao = gl.createVertexArray();
gl.bindVertexArray(vao);
for(i in vboDataArray){
vbo = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vboDataArray[i]), gl.STATIC_DRAW);
gl.enableVertexAttribArray(attL[i]);
gl.vertexAttribPointer(attL[i], attS[i], gl.FLOAT, false, 0, 0);
}
if(iboData){
ibo = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ibo);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Int16Array(iboData), gl.STATIC_DRAW);
}
gl.bindVertexArray(null);
return vao;
}
// 呼び出し例
var sphereData = sphere(16, 16, 0.75); // 頂点データを生成するユーザー定義関数
var sphereVAO = create_vao(
[sphereData.p, sphereData.n, sphereData.t], // 頂点データを格納した配列たち
attLocation, // attribute location を格納した配列
attStride, // attribute のストライドを格納した配列
sphereData.i // IBO のもととなるインデックスデータを格納した配列
);
ちょっと長くなってしまいましたが、やっていることは、これまでの VBO や IBO の生成とあまり変わりません。というよりも create_vao
というユーザー定義の関数の最初と最後に VAO をバインドする処理と、バインドを解除する処理があるだけで、実はそのほかの手順はなにひとつ変わりません。
VAO のバインドには gl.bindVertexArray
を使います。生成した VAO をバインドしたままの状態で VBO や IBO に関する処理を行なっていくだけで、自動的に VAO にそれらのバッファが紐付けられていくわけですね。頂点データを初期化するところはほんの少しだけ従来よりも冗長になりますが、ここから先は VAO 単位でバインドや解除を行えばいいので、描画する頂点データの切り替えが非常に楽になります。
正直なところ使わない理由が無いですし、標準機能として使えるようになったわけですし、WebGL 2.0 を使うならもうば VAO を使うのをデフォルトとして考えたほうがいいでしょう。
まとめ
VertexArrayObject を利用する上で、シェーダ等に変更を行う必要は一切ありません。その上、初期化処理が少しだけ複雑になるとは言え、描画するモデルの切り替えがメソッドひとつで簡単に行えるようになるわけですから、地味ですが、とてもありがたい存在だと個人的には思います。
WebGL 初心者の頃に、大量に登場するバッファ群のバインドや管理に頭が混乱したという経験は、多くの方がされているかと思います。少なくとも、私は混乱しましたし、理解した上でも面倒だなと感じていました。VAO を用いるとこれらの不満がある程度解消されますので、ぜひ自分なりに VAO を使った初期化が行えるようにしっかり手順などを確認しておきましょう。
外見的な変化等は無いためとにかく地味なんですが、今回も実際に動くサンプルを作ってあります。
ぜひ実際に動いている様子を確認してみてください。