OpenGLのVAOを使ってみた

前回のコードは、一連のコマンドのコンパイルもせず、vertex arrayやbuffer objectも使わずに、描画の度に毎回大量の命令を行っており、大変非効率なので、vertex arrayを使って少しマシにする。

・ソースコード
polygon_sphere_test2.c

・コンパイル方法
前のエントリー参照

・実行画面

※今回は処理負荷軽減が主旨であり、ポリゴンの各頂点を使い回すため、各頂点の法線ベクトルは頂点座標と同じ値にしているので、表面が前回よりツルツルになっている。

Vertex array初体験の筆者にとっては、vertex arrayとbuffer objectとvertex array object(VAO)の関係がややこしい。今週理解した限りでは、おそらく、次のような感じだと思う。


Vertex array
頂点座標データを含む配列。"Vertex"と言いながら、頂点の法線ベクトルや頂点の色情報やテクスチャーの座標データなど、いくつかの決められた種類のデータをvertex arrayにすることができる。データの種類と、種類によっては1要素当たりのバイト数を指定して使う。

Buffer object
H/Wが直接アクセス可能なメモリ領域に置くことができるオブジェクト。任意のデータを含めることができる。Vertex arrayを入れることもできる。
Vertex buffer object(VBO)はvertex arrayを入れたbuffer objectのこと。

Vertex array object(VAO)
各vertex arrayの実データの位置(buffer object内でも良い)と状態を保存するオブジェクト。これにより、複数のvertex arrayとbuffer objectを一発で切り替えることが可能になる。OpenGL 3.0以降で標準サポートされているが、それ以前のバージョンの環境やOpenGL ES 1.1の環境でも拡張I/Fとしてサポートされていることが多い。

今回、一旦VAOやVBOを使わずにglVertexPointer()等のvertex arrayのI/Fだけを使ってglVertex3d()やglNormal3d()の嵐を解消したが、いかにも中途半端な感じになった。負荷軽減の目的ならVBOを使わないと意味が無く、VAOが使えるならその方がすっきりするので、結局、頂点座標と法線ベクトルをVBOにしてVAOにまとめた。

glDrawElements()を使うには頂点のインデックスの配列(element array)(vertex arrayの文脈ではindex arrayは色番号の配列のことなので別物) が必要で、どうも頂点のインデックスの配列はVAOに登録できないようなので、glDrawElements()を使うにはglBindVertexArray()するのとは別に、 glBindBuffer()で頂点のインデックスの配列を有効にすることが必要になるようだ(上記ソースコードのdrawSphere()参照)。
なので、座標も法線も全く同じ頂点を使い回さないなら、glDrawElements()でなくglDrawArrays()を使う方がすっきりすると思ったが、連続平面ならポリゴンの各頂点を3回使うのが普通だろうと思い直して、やっぱりVAO+element arrayのbuffer objectという構成にした。


ところで、VAOのサンプルコードを何かの本で手に入れて、今回使おうとしたら、リンクエラーになったので、結構時間を失った。Mac OS X 10.7(Lion)ではGLUTを使うとOpenGL 2.1のAPIしか使えないので、glBindVertexArray()等のVAOのAPIも使えないのである。幸いにもXcodeで関数名を補完したら"glBindVertexArrayAPPLE"となったので、割とすぐにこれで代用できることがわかったが、FreeBSDで動かすのにはさらに手間取った。当然の如くglBindVertexArrayもglBindVertexArrayAPPLEもリンクエラーとなったが、

grep -r BindVertexArray /usr/X11R6/include/GL
すると、glBindVertexArrayもglBindVertexArrayAPPLEも見つかるのである。結果的には
glutGetProcAddress( "glBindVertexArrayAPPLE" );
とするとglBindVertexArrayAPPLE()のアドレスが得られることがわかったのだが、これに行き着くのにかなり手間取った。(しかも、"APPLE"じゃないglBindVertexArrayのアドレスを取得するとNULLじゃないのに、呼び出すと落ちる)
このように、ヘッダファイルに関数名があり、リンカオプションを"pkgconfig --libs glut"で与えているのに、リンクエラーになったら、どこからどのようにして解決方法を探せば良いのだろうか。適当なサンプルコードが無いとお手上げである。
もしかして、Macでも、glBindVertexArrayAPPLE()等は直にリンクするのでなく、glutGetProcAddress()でアドレスを取得して呼び出すのが正しいのだろうか?そのことも、どこを当たればわかるのかがよくわからない。


もう1つ、よくわからないのは、Macで

glEnable(GL_LIGHTING);
glLoadIdentity();
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_NORMAL_ARRAY);
glDrawElements(...)
すると、ライティングが無効になり、GL_NORMAL_ARRAYがGL_COLOR_ARRAYとして扱われたような表示になることである。

筆者のMacでは、上記のソースコードの
glEnableClientState(GL_COLOR_ARRAY);
を消すと、ドラッグして回転するまでは、このようなカラフルな球になる。ワイヤーフレーム表示から面表示に切り替えても同じである。色々試した結果、
・LightingをOFFにして、glNormalPointer()をglColorPointer()に変え、glEnableClientState(GL_COLOR_ARRAY)すると、上の画像と同じ表示になった
 →こういうことがOpenGLの内部で起こっているっぽい
・GL_COLOR_ARRAY, GL_SECONDARY_COLOR ARRAY, GL_INDEX ARRAY等をglDisableClientState()しても発生する
・glColorPointer()でcolor arrayを明示的に別のデータにしても発生する
・glNormalPointer()で指定するデータを書き換えると、それに合わせて表示も変わる
・今回のソースコードのように、単にglEnableClientState(GL_COLOR_ARRAY)すると発生しなくなる
・しかし、glEnableClientState(GL_COLOR_ARRAY)とglColorPointer()の両方を行うと発生する
・glEnableClientState(GL_NORMAL_ARRAY)をglBindVertexArrayやglBindBufferの前に移しても、display()内に移しても発生する
・glVertexPointerとglNormalPointerを別のbuffer objectにしても発生する
 →0番はvertex array, 1番はcolor arrayという初期設定がある訳では無さそう
・gGenBuffersで指定するbuffer objectの要素数を変えても発生する
・display()にてglScale3d(1.0以外)すると発生しない
・display()にてLightingをOFF→ONすると発生しない
ということを確認した。Modelview matrixをIdentityから変更すると直ることと、glutMainLoop()する前(当然実際に描画する前)にglEnable(GL_LIGHTING)しているのにライティングが有効にならないことから、ライティングを有効にした状態が適切にH/Wに反映されない、MacのOpenGLの不具合のような気がするが…