構造体uniform

構造体型のuniform

 前回配列のuniformをやったので、今回は構造体です。構造体自体はWebGL1からあるのですが、詳しくない人も多いでしょう。私もそうです。uniformの基礎を振り返る過程で初めてまともに扱いました。ざっくりいうとオブジェクトです。ドットでアクセスします。それゆえ名前もそんな感じのことになっています。落ち着いて読み進めましょう。構造体の配列や配列メンバについても扱います。基礎編のまとめ的な感じになると思います。

コード全文

作品11のリンク

構造体について

 構造体とはオブジェクト定義です。いくつかのプロパティを一つにまとめたもので、シェーダーを見ると分かりますが、こんな感じで指定します。メンバに配列と通常型をそれぞれ用意しました。メンバにも構造体を用意して入れ子にしたりもできます。

struct property{
  vec2 pos[3];
  vec3 color[3];
  float size;
};

 位置と色を長さ3で用意しています。三角形の頂点です。このあと2つ作るコードを書くんですがとりあえず1つだけです。sizeは単純に位置の変数に掛けるために用意しています。今回はlilでいじったりはしません。シンプルなコードになっています。
 メンバにはドットでアクセスします:

void main(){
  vec2 p = uTriangle.pos[gl_VertexID];
  vColor = uTriangle.color[gl_VertexID];
  p *= uTriangle.size;
  gl_Position = vec4(p, 0.0, 1.0);
}

このuTriangleに値をセットすればいいわけですね。

構造体uniformのlocation

 いつものように名前を取得します。今回はこんな感じで出力されます:

uTriangle.pos[0], uTriangle.color[0], uTriangle.size

 すなわちドットでアクセスして降りて行き、構造体でないところまで来たら配列かどうかに応じて[0]を付与するわけです。これらを元にロケーションを取得し登録するわけです。あとは前回や前々回と同じように、fやfvを使ってセットするだけです。

  gl.useProgram(pg);
  // 配列ならばfvで。そうでなければfで。
  gl.uniform2fv(uniforms["uTriangle.pos[0]"].location, [-1,-1,1,-1,-1,1]);
  gl.uniform3fv(uniforms["uTriangle.color[0]"].location, [1,0,0,0,1,0,0,0,1]);
  gl.uniform1f(uniforms["uTriangle.size"].location, 0.75);

 0.75の影響でちょっと小さくなっています。ともあれ、きちんと登録されていますね。もちろんインデックスごとにロケーションを取得したりもできます。

構造体uniformの配列

 次に、構造体uniformの配列をやります。通常の配列と違い、今回配列になっているのは構造体なので、0だけ取ってまとめてポン!ということはできません。なんとインデックスごとに登録します。インデックスを取ることで構造体に落とし、あとは同じようにドットで降りて行って配列か通常型になったらそこで登録を実行するわけです。降りた先に構造体の配列があったらまたインデックスでばらして...とキリがないですが、さすがにそこまではしません。まあコードを見てみましょう。

作品12のリンク

構造体uniform配列のlocation

 いつものように名前を取得すると、次のようになっています:

  uTriangle[0].pos[0], uTriangle[0].color[0], uTriangle[0].size
  uTriangle[1].pos[0], uTriangle[1].color[0], uTriangle[1].size

 まあそういうことです。これら一つ一つに対して、値をセットするわけですね。しんどいね...まあforループで適切にまとめることもできるんですが、数によってはuniformの呼び出し回数が半端ないですね。まあそこまでたくさん扱うことはあまりないとは思いますが。今回はドローコールで頂点を6つ用意しています。つまり作る三角形は2枚で、シェーダーもそれに応じたものになっています。

uniform property uTriangle[2];
out vec3 vColor;
void main(){
  // 三角形ごとのパラメータ
  int index = (gl_VertexID < 3 ? 0 : 1);
  int paramIndex = gl_VertexID - index*3;
  vec2 p = uTriangle[index].pos[paramIndex];
  vColor = uTriangle[index].color[paramIndex];
  p *= uTriangle[index].size;
  gl_Position = vec4(p, 0.0, 1.0);
}

 indexは三角形の番号、paramIndexは頂点ごとの0,1,2の値ですね。構造体配列にindexでアクセスして、プロパティにparamIndexでアクセスしています。これをCPUサイドでこんな感じで用意します:

  gl.useProgram(pg);
  // 配列ならばfvで。そうでなければfで。
  gl.uniform2fv(uniforms["uTriangle[0].pos[0]"].location, [-1,-1,1,-1,-1,1]);
  gl.uniform3fv(uniforms["uTriangle[0].color[0]"].location, [1,0,0,0,1,0,0,0,1]);
  gl.uniform1f(uniforms["uTriangle[0].size"].location, 0.75);
  gl.uniform2fv(uniforms["uTriangle[1].pos[0]"].location, [-1,1,1,-1,1,1]);
  gl.uniform3fv(uniforms["uTriangle[1].color[0]"].location, [1,0.5,0,0,1,0.5,0.5,0,1]);
  gl.uniform1f(uniforms["uTriangle[1].size"].location, 0.5);

 ほんとにひとつずつです。たとえばスキンメッシュとかだと大量のuniformが必要になったりします。多分そういう場合のためにuniformBufferObject(UBO)があるのかなぁ...今は説明できないけれども。もしこれが100個とか200個だったらと思うとぞっとしますね...それはともかく、図を見るときちんと2枚描画されているのが分かるかと思います。サイズもちゃんと変えてあります。

 なお、getUniformで照会する場合も、配列か通常型まで降りていってロケーションを(配列の場合はインデックスごとに)指定して取得します。これでfとfvについての説明は終わりです。お疲れ様でした!

 以降は整数型など補足事項になるかと思います...が、メインディッシュが控えています。行列と言います。よろしく。

構造体uniform配列の実例

 たとえば次のmebiusboxさんのコードに登場します:物理ベースレンダリングの実装
 こういうコードで勉強したいと思った場合、構造体uniformの知識が無いと不便です。なので知っておいて損はないでしょう。