配列uniform

配列型のuniform

 この節では配列型のuniformを扱います。今まで三角形の頂点はシェーダー内でvec2の配列を使って定義していましたが(だってアトリビュートが使えないんだもん)、それを配列を使って用意してみましょう。定義するのは簡単ですが、名前のところがちょっと厄介なことになってるので、そこに気を付けながら読み進めてください。

コード全文

作品10のリンク

プログラムの解説

 前回の記事で基礎をがっつりやったので、今回はそこまで詳しくやりません。不安な人は前回の記事に戻ってくださいね。

 さて配列uniformです。定義するには配列の形で定義します。配列変数と違い、型宣言の方には長さを書きません。型だけです。変数名の方で長さを指定します。

uniform vec2 uPoses[3];
uniform vec3 uColors[3];

 ここで定義しているのは位置情報と色情報です。頂点が3つの三角形を1つ描画するんですが、その頂点と、色ですね。フラグメントシェーダの方では白っぽさを長さ2のfloatの配列で用意しています。

uniform float uWhite[2];

 これがどんなふうに使われるかというと、まあ作品リンクで色々実験してみればいいんですが、白っぽさ、です。左上と右下の頂点の白っぽさを調整する感じになっています。lilは便利ですね。スライダーをいじるとuniform変数の再登録がされて同時に描画もされるので内容が変化します。デフォルトは両方0で、つまりサムネイルの見た目です。両方1にすると真っ白になります。そういう計算をしています。たとえばそれぞれ0,1を指定すると、次のような見た目になります:

white

 スクリーン乗算は計算も難しくないので、知っておくと便利です。

配列uniformのlocation

 プログラムについてはこのくらいにして、locationの説明に移ります。取得する方法は同じです。今回ダミーのuniformはありません。全部使います。同じようにアクティブの個数を取って、順繰りにuniformを取得します。その名前に着目しましょう。consoleに出ているはずですが、こう:

uPoses[0], uColors[0], uWhite[0]

 [0]が付いてます。そうです。[0]が付きます。間違いではなくて、[0]まで含めてuniform名です。なのでアクセスの仕方も特殊になります。これを名前として使うので、まあこんな感じですね...

  // 三角形の頂点たち
  const vertices = [-1, -1, 1, -1, -1, 1];
  // いろたち
  const colors = [1, 0, 0, 0, 1, 0, 0, 0, 1];
  // 型付配列でもいいが、許されてるのはFloat32Arrayのみである。
  // なぜなら今扱ってるのはfvだから。

  gl.useProgram(pg);
  gl.uniform2fv(uniforms["uPoses[0]"].location, vertices);
  gl.uniform3fv(uniforms["uColors[0]"].location, colors);
  // uWhiteは設定しない。既定値の0が使われる。

 前回uniform関数を紹介したのを見た人は覚えてるかもしれませんが、fv系は引数に配列を取ります。型付配列でもいいんですが、許されるのはFloat32Arrayだけです。通常はいつもの配列が使われるでしょう。ただFloat32なので若干丸められる場合はあります(シェーダー内で同じ精度で使われるとは限らないので)。で、uniformsオブジェクトを前回と同じように用意したので、こんな風に用意するわけですね。まあuPosesにしてしまってもいいんですが、後のことを考えてダイレクトに取っています。

 なおuWhiteは設定しません。なので前回説明したように0が使われます。ゆえに通常色です。

 つまりlocationの取得に使われる名前は"uPoses"ではなく"uPoses[0]"というわけです。しかし、実は"uPoses[1]"と"uPoses[2]"にもlocationが設定されています。

  console.log(gl.getUniformLocation(pg, "uPoses[1]") === null); // false;
  console.log(gl.getUniformLocation(pg, "uPoses[2]") === null); // false;

 いずれもnullになりません(失敗するとnullが返る)。そして次のような設定方法が可能です。

  //gl.uniform2fv(uniforms["uPoses[0]"].location, vertices);
  gl.uniform2fv(gl.getUniformLocation(pg, "uPoses[0]"), [-1,-1,1,-1]);
  gl.uniform2fv(gl.getUniformLocation(pg, "uPoses[2]"), [-1,1]);

 なのでuniform locationは数ではないんですが、一応配列の場合には連番のような仕組みになっていることが推測できます。glslがC言語なのでおそらくポインタですね。とはいえ、通常は0だけ取得して配列をまるごと入れるやり方で登録することになるでしょう。各番号ごとにlocationを取得して配列の形でuniformsにセットしてもいいんでしょうが、恐らくそういうのは使わないでしょうし...まあ何らかの事情で部分的に変更したい場合は重宝するテクニックかもしれないです。

 当然ですが、locationは数ではなくオブジェクトです。整数を足して他の番号のlocationを取得したりなどといったことはできないので注意しましょう。

getUniform関数

 話すことが無くなってしまいました。なのでおまけとしてプログラム内のuniformの設定値を取得する関数を紹介しましょう。getUniformといいます。とても単純明快な分かりやすい関数です。プログラムとロケーションを与えると、設定値が返ります。現時点でfとfvしか解説していませんが、そのようなuniformの場合Float32Arrayの形で返ります。なお、プログラムを走らせる必要はありません。

 例えば前回のコードで取得する場合、こうなります:

  gl.useProgram(null);
  console.log(gl.getUniform(pg, uniforms.uPos.location)); // Float32Array([-1,-1])
  console.log(gl.getUniform(pg, uniforms.uColor.location)); // Float32Array([0,0,1])
  console.log(gl.getUniform(pg, uniforms.uAlpha.location)); // 0.25(数値)

 配列でない場合はこんな感じですね。プログラムがセットされてなくても動くことを示すためにわざとnullにしてから使っています。vec2とvec3はFloat32Arrayで返ります。floatの場合、単にNumberで返りますが、Float32の値なので誤差が伴う場合があります。
 配列uniformの場合はこんな感じ:

  gl.uniform1fv(uniforms["uWhite[0]"].location, [0.5, 0.8]); // 実験用

  gl.useProgram(null); // こうしても動くよ、のテスト
  console.log(gl.getUniform(pg, gl.getUniformLocation(pg, "uPoses[0]"))); // Float32Array([-1,-1])
  console.log(gl.getUniform(pg, gl.getUniformLocation(pg, "uPoses[1]"))); // Float32Array([1,-1])
  console.log(gl.getUniform(pg, gl.getUniformLocation(pg, "uPoses[2]"))); // Float32Array([-1,1])
  console.log(gl.getUniform(pg, gl.getUniformLocation(pg, "uColors[0]"))); // Float32Array([1,0,0])
  console.log(gl.getUniform(pg, gl.getUniformLocation(pg, "uColors[1]"))); // Float32Array([0,1,0])
  console.log(gl.getUniform(pg, gl.getUniformLocation(pg, "uColors[2]"))); // Float32Array([0,0,1])
  console.log(gl.getUniform(pg, gl.getUniformLocation(pg, "uWhite[0]"))); // 0.5
  console.log(gl.getUniform(pg, gl.getUniformLocation(pg, "uWhite[1]"))); // 0.800000011920929

 実験のためにuWhiteに値を設定してから取得しています。ご覧のように、配列の場合、まとめて取得することはできません。番号ごとのアクセスとなります。まとめて取得できないので、欲しい番号のインデックスのロケーションが必要になります。まあまとめて照会することはほぼないと思います。floatの配列も同じようになっていますが、設定した0.8が奇妙な値で返っています。シェーダー内ではこれが使われています。すなわちFloat32なので、誤差があるわけです。0.5は2進数なので誤差無しです。

 getUniformについては以上です。ではまた次回。お楽しみに!

今回登場した関数

getUniform