GLSLシェーダープログラムの関数を切り替える拡張である GL_ARB_shader_subroutine を試してみました。使ってみた感想としては C言語プログラムの関数ポインタを設定して実行先を切り替える、そんなものとずいぶん近い気がします。
シェーダープログラムでは、切り替える関数の宣言、切り替えに使う変数を先に宣言しておきます。
今回はLambertとPhongのライティングを切り替えるように関数を準備してみました。
subroutine vec4 LightingType(); subroutine uniform LightingType selectedLighting; subroutine(LightingType) vec4 lambert() { // Lambertの計算 return resultColor; } subroutine(LightingType) vec4 phong() { // Phongの計算 return resultColor; } void main() { gl_Position = matProjViewWorld * in_position; gl_FragColor = selectedLighting(); }
なんとなくわかるかと思いますが、先に切り替える関数そのもののプロトタイプ宣言みたいなものを行います(1行目)。そして、関数を示す変数を uniform で用意します。main関数の箇所ではこの変数を関数ポインタのようにして、実際の処理関数を呼び出します。
切り替える関数そのものは、subroutine(プロトタイプ宣言)のような修飾を関数先頭におこなって、プログラムコードを書きます。
続いて C++ 側のコードについて説明します。uniform 変数のようにして関数ポインタもどきを用意しましたが、これは通常のuniform 変数のようにロケーションを取得できません。取得する際には、 glGetSubroutineIndex という関数を用います。また関数がどのシェーダーに実装が入っているのかを識別するためにシェーダーのタイプもまた必要になっています。
lambartRoutine = glGetSubroutineIndex( shaderProgram, GL_VERTEX_SHADER, "lambert" ); phongRoutine = glGetSubroutineIndex( shaderProgram, GL_VERTEX_SHADER, "phong" );
そして、このインデックスをシェーダーに対してセットすることで、処理関数が決定されます。
glUniformSubroutinesuiv( GL_VERTEX_SHADER, 1, &phongRoutine );
経過時間で関数を切り替えて描画してみたのが以下になります。
DirectXとの比較
DirectXでは、この同様の機能は、シェーダーの動的リンクというものになるのではないかと思います。DirectX11からの機能で、HLSLでインターフェースクラスを作成し、実装はその継承先でという形をとります。つまり Cスタイルで切り替えるか、C++スタイルで切り替えるかの違いでしかないようです。どちらも同じように処理自体は実現可能のように思います。