OpenGL ESのシェーダとは、グラフィックスを操作するためのC言語ベースのプログラムです。例えば、cyberagent製のGPUImageのGPUImageContrastFilterのフラグメントシェーダは、次のようなものです。
public static final String CONTRAST_FRAGMENT_SHADER = "" +
"varying highp vec2 textureCoordinate;\n" +
" \n" +
" uniform sampler2D inputImageTexture;\n" +
" uniform lowp float contrast;\n" +
" \n" +
" void main()\n" +
" {\n" +
" lowp vec4 textureColor = texture2D(inputImageTexture, textureCoordinate);\n" +
" \n" +
" gl_FragColor = vec4(((textureColor.rgb - vec3(0.5)) * contrast + vec3(0.5)), textureColor.w);\n" +
" }";
Code language: PHP (php)
この中の5行目、uniform lowp float contrast; がuniform変数です。uniform変数は、値を動的に変更できます。
まず、GPUImageFilterのonInit()で、変数名を指定して、uniform変数のロケーション番号を取得します。見つからなければ、-1、見つかれば、0以上の数値が返ってきます。
public void onInit() {
super.onInit();
contrastLocation = GLES20.glGetUniformLocation(getProgram(), "contrast");
}
Code language: JavaScript (javascript)
次に、UIでスライダーなどが操作されたタイミングで、uniform変数の値を更新します。
public void setContrast(final float contrast) {
this.contrast = contrast;
setFloat(contrastLocation, this.contrast);
}
Code language: JavaScript (javascript)
uniform変数の型に合わせて、setInteger、setFloatVec3、setFloatArrayなどのメソッドも用意されています。
さて、GPUImageFilterのサブクラスを作り、独自のシェーダを作っていたときのことです。まず、何も加工しないシェーダを用意しました。いずれ、使う予定のuniform変数data1も宣言しました。
public static final String MY_FRAGMENT_SHADER = "" +
"varying highp vec2 textureCoordinate;\n" +
" \n" +
" uniform sampler2D inputImageTexture;\n" +
" uniform lowp float data1;\n" +
" \n" +
" void main()\n" +
" {\n" +
" gl_FragColor = texture2D(inputImageTexture, textureCoordinate);\n" +
" }";
Code language: PHP (php)
次に、シェーダのmain()の処理を書く前に、onInit()でuniform変数data1のロケーションを取得できているかをトレースで確認しました。0か1が返ってくるだろうと。
public void onInit() {
super.onInit();
data1Location = GLES20.glGetUniformLocation(getProgram(), "data1");
}
Code language: JavaScript (javascript)
予想に反して、data1Locationに -1 が返ってきました!変数名の長さを短くしたり、キャメルケース、スネークケースを試したり、x や contrastにしても、-1です。
そこで、GPUImageContrastFilter が動くことを確認してから、contrastの下にdata1を宣言しました。
public static final String CONTRAST_FRAGMENT_SHADER = "" +
"varying highp vec2 textureCoordinate;\n" +
" \n" +
" uniform sampler2D inputImageTexture;\n" +
" uniform lowp float contrast;\n" +
" uniform lowp float data1;\n" +
" \n" +
" void main()\n" +
" {\n" +
" lowp vec4 textureColor = texture2D(inputImageTexture, textureCoordinate);\n" +
" \n" +
" gl_FragColor = vec4(((textureColor.rgb - vec3(0.5)) * contrast + vec3(0.5)), textureColor.w);\n" +
" }";
Code language: PHP (php)
onInit()で、contrastとdata1のuniformロケーション番号を取得しました。すると、
public void onInit() {
super.onInit();
contrastLocation = GLES20.glGetUniformLocation(getProgram(), "contrast");
data1Location = GLES20.glGetUniformLocation(getProgram(), "data1");
}
Code language: JavaScript (javascript)
contrastLocationには、0が返ってきますが、data1Locationには、-1が返ってきました。
シェーダ内のuniform変数の宣言順序を入れ替えてみました。
" uniform lowp float data1;\n" +
" uniform lowp float contrast;\n" +
Code language: JavaScript (javascript)
結果は同じでした。
ここにいたって、data1は宣言だけして、値を参照していないから?ということに気づきました。
試しに、main()内の、contrastをdata1; で置き換えてみました。
" void main()\n" +
" {\n" +
" lowp vec4 textureColor = texture2D(inputImageTexture, textureCoordinate);\n" +
" \n" +
" gl_FragColor = vec4(((textureColor.rgb - vec3(0.5)) * data1 + vec3(0.5)), textureColor.w);\n" +
" }";
Code language: JavaScript (javascript)
すると、contrastLocationに -1、data1Locationに 0が返ってきました。
シェーダがコンパイルされるとき、コンパイラーの最適化で、参照していない変数は削除されてしまうんですね。
この記事は実際より簡略化したので、すんなり原因がわかったように見えます。実際は、7個のuniform変数を宣言し、mainでも少し処理を書いていたので、原因がわかるまで、かなりさまよいました。