VAO(vertex array object)

実行結果

今回のサンプルの実行結果

頂点情報をより扱いやすく

前回は、WebGL の拡張機能のひとつである float texture を用いて、頂点テクスチャフェッチを行いました。実用性はあまりないサンプルでしたが、float texture を利用することの可能性のひとつとして参考にしていただければと思います。

さて、今回ですが VAO (vertex array object)を扱ってみようと思います。VAO は、標準の WebGL では利用できない技術ですが、float texture と同様に拡張機能として実装されており、同機能を有効化することで初めて利用が可能になります。

とはいえ、VAO を使ったら何が嬉しいのか、というかそもそも VAO ってなんやねんという人も意外と多いのではないでしょうか。※自分はそうでした(笑)

今回は、この vertex array object について見ていくことにしましょう。

VBO と VAO

WebGL を扱う上で、VBO は欠かせない存在です。もちろん、当サイトのサンプルでもほとんど全てと言ってもいいくらい、たびたび VBO は登場してきました。ちなみに、VBO については過去のテキストでも詳しく解説していますので、もしわからないということであれば復習しておくといいでしょう。

参考:頂点バッファの基礎

VBO は vertex buffer object の頭文字をとった略でしたね。一方、VAO は vertex array object の略です。双方を比較すると非常に紛らわしい名前がつけられていますが、その役割は全く違います。

array という響きからもなんとなく想像できるかもしれませんが、VAO は頂点の情報をカプセル化して包み込むオブジェクトです。VAO を用いれば、煩雑で紛らわしい頂点バッファの扱いをより簡潔に記述することができるようになります。これには、実際に例をあげたほうがわかりやすいでしょう。

まず従来の、VAO を用いていないやり方から見てみます。

従来の場合、まず VBO を生成しデータを登録した後、実際にレンダリングを行う前の段階でいくつかのメソッドを複数回呼んでやる必要がありました。

従来どおりの VAO 無しの場合

// VBOをバインドし登録する関数
function set_attribute(vbo, attL, attS){
	// 引数として受け取った VBO の配列を順に処理する
	for(var i in vbo){
		// バッファをバインドする
		gl.bindBuffer(gl.ARRAY_BUFFER, vbo[i]);
		
		// attributeLocationを有効にする
		gl.enableVertexAttribArray(attL[i]);
		
		// attributeLocationを通知し登録する
		gl.vertexAttribPointer(attL[i], attS[i], gl.FLOAT, false, 0, 0);
	}
}

頂点の位置、法線、頂点の色など、複数の VBO をループしながら処理する上記の関数は、今までに何度も使ってきた当サイトの独自関数です。処理が冗長になってしまうので、わざわざこんなふうに自前で関数を作って処理を行っていたわけですね。

ちなみに、インデックスバッファ(IBO)を使っている場合には、さらにこのインデックスバッファのバインド処理も加わりますので、レンダリングしたいモデルを変更するたびに、それなりにたくさんのコードを走らせなくてはいけなくなってしまいます。

同じ頂点データだけを使いまわしている場合ならともかく、複数のモデルを切り替えながらレンダリングするようなケースでは、こういった頂点データの扱いが非常に面倒でした。そして、この問題を解決するのが他ならぬ VAO なのですね。

VAO を用いた頂点処理

VAO は冒頭でも書いたように WebGL の拡張機能として実装されています。float texture のときと同じように、まずは拡張機能を有効化しましょう。

VAO の拡張機能を有効化する

// 拡張機能を有効化
var ext;
ext = gl.getExtension('OES_vertex_array_object');
if(ext == null){
	alert('vertex array object not supported');
	return;
}

以前も使った拡張機能を有効化するための getExtension メソッドに VAO を有効化するための文字列を渡します。戻り値として返されるオブジェクトが、VAO の機能を管理するためのメソッドなど一式を持っています。

VAO は、煩雑な頂点情報の管理を簡単にしてくれます。とは言っても、VAO 自体が VBO の代わりになるわけではありません。VAO は一度設定された頂点に関する情報をまとめて保持しておいてくれるものと考えるのがいいでしょう。

従来であれば VBO の個数分だけ繰り返し処理しなければならなかった頂点処理が、VAO というオブジェクトにまとめて設定を突っ込んでおくだけで、後から簡単に再利用できるようになります。VAO はあくまでも頂点情報をパッケージするものであってけして VBO のパワーアップ版などではありません。このあたりは紛らわしいので注意しましょう。

VAO はその名のとおりオブジェクトです。生成するためには、拡張機能のオブジェクトが持つ createVertexArrayOES メソッドを使います。

VAO オブジェクトを生成する

var VAO = ext.createVertexArrayOES();

そして、VAO はテクスチャなどと同様に WebGL にバインドすることによって利用します。テクスチャにパラメータなどをセットする際には、あらかじめ対象のテクスチャをバインドしておき texParameteri メソッドなどを呼び出しますよね。あれと同じ要領です。

VAO をバインドする

ext.bindVertexArrayOES(VAO);

WebGL に対するバインドを行う際にも、拡張機能のオブジェクトが持つメソッドを使います。 bindVertexArrayOES メソッドに、生成した VAO オブジェクトを渡して呼び出せば OK です。これで、WebGL に VAO がバインドされました。

バインドが完了すると、ここから先に行うすべての頂点関連処理(主に Attribute 系)を通じて VAO に頂点情報が登録されていきます。先ほど登場した自前の関数の中身で行っていたように、 gl.bindBuffer gl.enableVertexAttribArray gl.vertexAttribPointer などを適宜呼び出しましょう。これらのメソッドが呼び出されるたびに VAO に情報がひとまとめにされて格納されていきます。

ここで勘のいい人なら気がついたかもしれませんが、VAO がバインドされている限り、同じ VAO に対して設定が上書きされていくことになります。ですから、異なるモデルの頂点情報を登録する際にはバインドする VAO を切り替えるのを忘れないようにしないと、どんどん設定が塗り替えられていってしまいますので注意しましょう。

基本的には、フレームバッファやテクスチャと同様にバインドするためのメソッドに NULL を渡せばバインドは解除されます。また、ことなる VAO をバインドすれば、もともとバインドされていた VAO は勝手にバインドが解除されます。この辺はあまり違和感なく理解できるでしょう。

インデックスバッファはどうする?

さて、Attribute 系の処理は VAO に対して一度だけ登録すれば、あとは VAO を使うことで設定した情報をまるまる再利用することができます。

しかし IBO、つまりインデックスバッファを使っている場合はどうなるのでしょうか。

結論から言うと、VAO には IBO に関する情報もまとめて一式格納することが可能です。従来であれば gl.bindBuffer メソッドなどを用いて、レンダリングするモデルを切り替えるたびに、IBO もバインドしなおす必要がありました。VAO を利用する場合には IBO の情報も一緒に保持しておいてくれますので、まとめて一括して処理できます。

わざわざ IBO をバインドする処理を挟む必要がなくなりますので、VBO と IBO は初期化などの段階でまとめて VAO に対して設定してしまうのがいいでしょう。

実際に VAO を利用する

さてそれでは実際に VAO を使った処理の一連の流れを考えてみましょう。

従来であれば、当サイトのテキストでは以下のような感じで処理していました。

  • 初期化処理で VBO や IBO を生成
  • モデルの数だけ繰り返し VBO などを生成
  • レンダリングループの中で VBO などをバインド
  • ドローコール
  • 別のモデルの VBO などをバインド
  • ドローコール……以下繰り返し

この場合、レンダリングループの中では何度も頂点情報を扱うためのメソッドを呼び出さなくてはなりません。また、レンダリングするモデルが切り替わる場合には、VBO や IBO を全て漏れなく都度バインドしなおしてやる必要がありました。

自前関数などを使って見た目上はスッキリさせることができても、実際に呼び出されているメソッドの数は、毎ループごとに結構な数になってしまいます。これが、VAO を利用することで以下のような流れに変わります。

  • 初期化処理で VBO や IBO を生成
  • 生成したら VAO に情報を登録
  • モデルの数だけ繰り返し行う
  • レンダリングループの中では VAO だけをバインドする
  • ドローコール

このように、レンダリングループに入る前にあらかじめ VAO に対して頂点情報を登録しておくことができ、ループの中では VAO をバインドするためのメソッドの呼び出し一回で頂点情報の切り替えが完了します。

これはすごく簡単ですね。

あらかじめ登録だけしておけば、あとは何も考えずに VAO を切り替えるだけで異なるモデルをレンダリングできるようになるわけです。非常に便利ですし、何よりわかりやすく簡潔ですね。

VAO の今後

非常に利便性の高い VAO ですが、WebGL ではあくまでも拡張機能ですので環境に依存する可能性が捨てきれないのが残念なところ。このテキスト執筆時(2014年4月)では、PC であればほぼ問題なく使えるようですが、いかんせんモバイル端末などの特殊な環境でどれほどサポートされているのかはなんとも言えません。

しかし、この VAO は WebGL 2.0 では標準機能に格上げされます。また、OpenGL 3 系(ES がつかない)では VAO の使用が必須になっています。

これらのことを踏まえると、その仕組みや使い方を理解しておくことはけして無駄にはならないでしょう。

まとめ

さて、拡張機能のひとつ vertex array object について見てきましたがいかがでしたでしょうか。

名前だけを聞くと、いまひとつなにが便利なのかわかりにくい上に、名前が VBO と似ていて非常に紛らわしい VAO ですがその利便性は非常に高いように感じます。

ただでさえ冗長になりがちな WebGL のコードが非常にシンプルに記述できるのはすばらしいです。将来的には VAO を使うことが WebGL でも当たり前になっていくでしょうし、今のうちからぜひ触れておいてほしい拡張機能です。

今回のサンプルでは、実際に VAO を利用してトーラスと球体のふたつのモデルをレンダリングしています。VAO を使うことで簡潔にレンダリング対象となるモデルを切り替えながら処理できている様子が見て取れると思います。

entry

PR

press Z key