Mesa+GLUTでもシェーダーを使ってみた

GLUTを使うことによって移植性を確保しながらOpenGLのプログラム作りの練習を進める中、VMWare+FreeBSD+Mesa 7.4.4の環境でもなんとかOpenGLのシェーダーが動くことがわかったので、続いて、シェーダーを使って鏡面反射っぽいことをしてみる。

・ソースコード
polygon_sphere_test4.c

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

・実行画面

ちょっとわかりづらいが、画面中央にある球に、周囲の景色が映り込んでいるつもりである。今回の環境マッピングはcube mappingで行っているので、景色の背景画像は巨大な立方体の面の内側に貼られている感じになっている。

背景画像を変えたりもしてみたが、やっぱりわかりづらい。

床が格子模様、天井が同心円模様、側壁が波模様のつもりである。
きっと、意味不明な絵だからであろう。

絵心が無い癖に、プログラムで背景を生成しようと思ったのが間違いだった。

・操作方法
画面上をドラッグすると、背景が回転し、球への映り込みも同時に変化する。
'c'を押すと、背景画像が切り替わる。


GLSLによるshaderのコードの作成には、「Win32APIによるOpenGL 3Dプログラミング 」(工学社)という本を多分に参考にした。
Vertex shaderのコード(initShader()内のvertexShaderSource[])にmatrixが増えたので、後で読む為にそれぞれの意味をメモしておく。
modelViewMatrix
オブジェクト座標系の各頂点の法線ベクトルを回転させて視点座標系にするためのModel-View変換行列
参考:OpenGL 2.1 Spec.のFigure2.6
viewTransposeMatrix
視点が原点から離れている分、平行移動させる行列
視点から物体の頂点までのベクトルを得るのにこれを使用し、そのベクトルを法線ベクトルで反射させて、環境マッピングのベクトルを計算している
modelViewProjectionMatrix
Z軸方向の必要な範囲を[-1,1]に収めるための、いわゆるProjection Matrix
glFrustrum()で得られる行列をそのままshaderに引き渡すのに使用している
environmentRotateMatrix
環境を回転させる行列
環境マッピングの参照ベクトルを回転させるのに使用している

Shaderの記述に用いるプログラミング言語であるGLSL(OpenGL Shading Language)のバージョンは、1.5以降と1.2とではかなり異なり、1.2は時代遅れのようである(参考書籍では4.0)が、筆者の手持ちのMacOSX 10.7+GLUTの環境では1.2しか使えないようであるため、今回はGLSL 1.2で記述している。それでも、今回のようにtextureCube()でcube mappingはできるし、反射ベクトルを求めるreflect()や屈折ベクトルを求めるrefract()も使えるので、十分に使い勝手があると思った。

前回のVAOに続き、今回も、glUseProgram()等のOpenGL 2.x標準のshader APIをOpenGL 1.3相当のMesaで動かす為に、glutGetProcAddress()による拡張機能のリンクを使いまくって、何とかOpenGL 2.1用のコードをそのまま動かすことに成功した。
しかし、それぞれの拡張APIとOpenGL 2.x以降の標準APIとの違いや、拡張APIの中でも、名前の後ろに"ARB"が付いているAPIと"EXT"が付いているAPIとの違いとかは全く調べていない。ただ、試行錯誤したら動いたというだけである。
1つ分かったのは、glUseProgramをglUseProgramObjectARBで代用しているが、

glUseProgram(0);
(shader program不使用への切り替え)のつもりで
glUseProgramObjectARB(0);
とすると異常動作するようである。幸い、今回はglUseProgram(0)しなくて済んだので、MesaではglUseProgram(0)を無処理に置き換えて回避した。

一応、Mesaのソフトウェアレンダリング環境でも動作したが、グラフィックハードウェアでユーザープログラムを処理する為の仕組みであるshaderをソフトウェアで処理するだけあって、極端に遅い上に、表示が汚い。上の画像はMac OS X上の実行結果であるが、同じMacでVirtualBox+FreeBSD上で実行すると、次のようになる。

ソフトウェアレンダリング環境でも動くようにシェーダーを作るというのはあまり意味が無いと思った。


Shaderは少しでも軽い方が良かろうと思って、背景描画用と鏡面描画用のshaderを別にしようとしていたが、vertex shaderが分かれると、shader programが別になり、共通の行列データをprogram毎に設定する必要が生じ、ソフトウェア設計理論的に言うと、常に同じであるはずの値がずれる余地が生じて良くないので、やめた。

同様に、頂点毎に走るvertex shaderは軽い方が良かろう、と思って、行列の掛け算はなるべくshader内で行わず、外部から計算結果を渡すようにしようかとも思ったが、fragment shaderに比べると遥かに実行回数が少ないだろうし、今回はshader外に移動できそうな掛け算が1つだけだし、OpenGLやGLUTに行列演算のルーチンが見当たらず、そのためだけに行列演算のコードを書くのが面倒だったので、やめた。

また、前回は、vertex arrayはインデックス形式(index arrayを別に用意し、glDrawElements()で描画する)にして、使い回す頂点のデータを1つにまとめてメモリを節約するようにしたが、
それだとvertex shaderの負荷が少し増すので、今回はインデックス参照が必要ない、glDrawArrays()で描画できるようなシーケンシャルなデータにした。

その結果、shaderのコードは、上記参考書籍の第8章のコードから始まり、背景が回転する以外は、上記書籍のコードとほぼ同じになった。


なお、Mac OS X 10.7(Lion)ではGLUTを使わなければGLSL 1.5も使用可能、Mesaでも1.4が使用可能なのだそうだ。