varying変数によるシェーダー間のデータ伝達
前回は、WebGL側からバーテックスシェーダーとフラグメントシェーダーの両方から参照可能な値を渡すためのuniform変数を試してみました。これで、JavaScript(WebGL)からフラグメントシェーダーに情報を送れるようになったので、続いてバーテックスシェーダー内で処理したデータをフラグメントシェーダーに送るvarying変数を試してみます。
varying変数は、双方でvaryingとして宣言した変数を通してバーテックスシェーダーからフラグメントシェーダーにデータを送る仕組みです。バーテックスシェーダーでvarying変数にデータを書き込むと、そのデータ(をピクセル位置に応じて調整した値)をフラグメントシェーダー側のvarying変数でも参照することができるようになります。
試しに、varying変数vRGBにバーテックスシェーダーで適当なvec3(RGB)値を入れ、フラグメントシェーダーではそのvRGBの値をそのままピクセルのRGB値として出力する形で三角形を一つ描いてみましょう。
WebGLから設定するシェーダーのコードを以下のようにし、バーテックスシェーダーからフラグメントシェーダーに(1.0, 0.5, 0.5)というvec3型の値を渡します。フラグメントシェーダーでは、受け取った値をそのままピクセルのRGB値に設定するようにしてみました。
・バーテックスシェーダー
attribute vec2 vPos;
varying vec3 vRGB;
void main() {
gl_Position = vec4(vPos, 0.0, 1.0);
vRGB = vec3(1.0, 0.5, 0.5);
}
・フラグメントシェーダー
varying mediump vec3 vRGB;
void main() {
gl_FragColor = vec4(vRGB, 1.0);
}
実行してみると、実際にvRGBで指定した色で三角形が描かれますね。
ただ、バーテックスシェーダーは「頂点」ごとに処理を行うのに対し、フラグメントシェーダーは「ピクセル」単位で処理を行うはずです。バーテックスシェーダーで出力された頂点の情報を元に画面上に出力するピクセルを決定し(WebGLによるラスタライズ処理)、そのピクセルに対してフラグメントシェーダーが呼び出されるので、ピクセルと頂点には直接の対応関係はありません。
上の例ではすべての頂点(バーテックスシェーダー呼び出し)で同じ値を渡していましたので、その値を受けてそのままRGB値とするフラグメントシェーダーの出力結果もすべて同じになりましたが、頂点ごとに異なる値を設定するとどうなるか、見てましょう。
今度は、attribute変数vRGBに三つの頂点に対応するRGB値を格納し、頂点を格納したattribute変数vPosと一緒にバーテックスシェーダーに渡します。バーテックスシェーダーでは、受け取った頂点をそのままgl_Position(処理結果)として、RGB値をそのままvarying変数vRGB2に格納するようにしてみました。
・バーテックスシェーダー
attribute vec2 vPos;
attribute vec3 vRGB;
varying vec3 vRGB2;
void main() {
gl_Position = vec4(vPos, 0.0, 1.0);
vRGB2 = vRGB;
}
・フラグメントシェーダー
varying mediump vec3 vRGB2;
void main() {
gl_FragColor = vec4(vRGB2, 1.0);
}
vRGBには、JavaScript側で各頂点に対応するRGB値として(1.0, 0.0, 0.0)、(0.0, 1.0, 0.0)、(0.0, 0.0, 1.0)とR/G/Bそれぞれ一成分のみを1とした値を用意しました。
あとは、WebGLコンテキストのvertexAttribPointer()でデータをattribute変数に設定してやれば、バーテックスシェーダーで頂点が処理されるときにvRGBの値も渡されるわけです。
// vRGBを有効化
gl_context.enableVertexAttribArray(vRGBLocation);
// vRGB用バッファをバインドしてデータ領域を初期化しデータを転送
gl_context.bindBuffer(gl_context.ARRAY_BUFFER, cbuf);
gl_context.bufferData(gl_context.ARRAY_BUFFER, new Float32Array(new Array(1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0)), gl_context.STATIC_DRAW);
// データを書き込んだバッファをvRGBのデータとして設定
gl_context.vertexAttribPointer(vRGBLocation, 3, gl_context.FLOAT, false, 0, 0);
実行してみると……
頂点の部分は、指定した色になりますが、その他の部分はグラデーションになっています。「頂点の間」は、補間される(混ぜ合わされる)わけですね。