OpenGLの仕組み

 現代的なオブジェクト指向プログラミングから入ったプログラマーにとっては、OpenGLは時に、摩訶不思議な振る舞いをするように感じることがあります。
 それもそのはず、教科書的なOOPではグローバル変数などは唾棄すべき禁忌の1つとされていますので、そもそもグローバル変数が禁忌とされた理由自体を知らないか、それは知っていても実際にグローバル変数に起因するバグに遭遇した経験のないプログラマーも増えてきているかもしれません(まあ、staticやらsingletonやらでグローバル変数の二の舞になった事はあるかもしれませんが)。

 ところでOpenGLの関数は、グローバルなグラフィックリソースを弄ります。それも何種類も。

 つまり、OpenGLわけわからん、と感じるプログラマーは、単純にグローバル変数を使ったプログラミングに慣れていないだけかもしれません。
 そこで一旦、OpenGLの仕組みについてまとめておくことにします。


●GL関数のタイプ

 OpenGLの関数を動作の仕方で分類すると、幾つかのタイプに分かれます。このことを理解しておくと、OpenGLを扱う上で、多少の助けになるかもしれません。

(1) リソースを操作する関数
 glColor*(), glMultMatrix*(), glEnable()/glDisable(), glTexImage*(), …
(2) リソースの現在状態を取得する関数
 glGet*(), glIsEnabled(), …
(3) 操作対象となるリソースを変更する関数
 glMatrixMode(), glBindTexture(), glNew/EndList(), …
(4) 新しいリソースを確保/解放する関数
 glGen/DeleteLists(), glGen/DeleteTextures(), …
(5) その他
 (説明は省略)

(1) リソースを操作する関数
 使用頻度としては、一番多い(はずの)関数です。
 それぞれ、どの種類のリソースを操作するかが決まっていますが(glColor*()なら頂点属性[色]、glMultMatrix*()なら行列スタック、等)、同じ種類のリソースが複数ある場合には、(2)のタイプの関数を用いて具体的なリソースを指定する必要があります。

(2) リソースの現在状態を取得する関数
 (1)で設定した値を取得する関数です。
 ただし同じ種類のリソースが複数ある場合、(3)を使って対象を指定してから実行する(1)とは違い、関数の引数で対象を直接指定するものがあることに注意です。この辺が少々混乱を招きます。

(3) 操作対象となるリソースを変更する関数
 例えば行列スタックには、GL_PROJECTION/GL_MODELVIEW/GL_TEXTURE/GL_COLOR の4種類がありますが、行列スタックを操作する関数(glMultMatrix*()等)には、それらの種類を指定するための引数がありません。
 そこでプログラマーは、このタイプの関数(glMatrixMode())を使い、以降の行列スタック操作関数がどれを操作するか、指定することになります。
 ディスプレイリストを作成するglNewList()も、描画対象をディスプレイからディスプレイリストに変更するという点で、この種類の関数の一種だと理解するとわかりやすくなるでしょう。

(4) 新しいリソースを確保/解放する関数
 (3)をしっかり理解していないとややこしくなるのが、この種類の関数です。
 この種類の関数は、操作対象そのものを作成/削除します。C++でいえば、new/deleteでしょうか(複数一気に作れるので、[]がつきます)。
 注意すべきは、リソースを確保したからといって、即座に操作対象が新しいリソースに移るわけではないことです。一度に複数作るのだから当たり前といえば当たり前なのですが、1つだけ作る場合には見落としがちかもしれません。


●構造化に際して

 以上のように、OpenGLを用いたプログラム中では、操作対象リソースの変更→リソースの操作、という処理を繰り返していくことになります。
 当然ながら、一度操作したリソースは再び元に戻すまでそのままですので、注意しないと「表示色を青にして図形を書く関数を呼んだ後、しばらくしてもう一度同じ関数を呼んだら赤く表示された」みたいなことが起こります。

 そして、輪をかけてややこしいのが、操作対象リソースの変更も、戻すまでそのままになる、ということです。これを忘れていると、「PROJECTION_MATRIXを変更した後、ちゃんと元に戻したはずなのに戻らない」のような現象が発生して困るわけです(つまり、元に戻す時は操作対象がMODELVIEW_MATRIXになっていたわけですね)。

 OpenGLC++でラップしようと思うと、どうやらこの辺を上手くできれば便利になりそうです。