gl_VertexID と gl_InstanceID
今回のサンプルの実行結果
GLSL ES 3.0 から利用できるビルトイン変数
前回は、WebGL 2.0 から標準機能に格上げとなったインスタンシングを利用した描画についてあらためて扱いました。
同じジオメトリ構造のモデルであれば、インスタンス描画を行うことで同時に、かつ負荷を抑えながら描くことができます。WebGL 1.0 の頃は拡張機能だったので若干使いにくい側面がありましたが、標準機能になりましたので積極的に利用していきましょう。
さて今回ですが、WebGL が 2.0 にバージョンアップしたことに伴い、同時に利用することが可能となった新しい GLSL のバージョンである、GLSL ES 3.0 で追加されたビルトイン変数について見ていきましょう。今回はふたつのビルトイン変数をご紹介します。
これらは頂点に関連した処理を劇的に変えるというようなものではありませんが、状況によっては、かなりいろいろな用途に使うこともできる非常に面白い変数だと思います。
ぜひ使いこなして、一歩上行く表現に挑戦してみてください。
より汎用性の高い頂点処理
今回紹介する新しいビルトイン変数は gl_VertexID と gl_InstanceID のふたつです。
両者は gl のプレフィックスが付いていることからも、組み込みの変数であることがわかりやすいかと思います。また、その変数が持つ意味についても、名前から想像しやすいですね。
まず最初に gl_VertexID
のほうですが、こちらは頂点のインデックスをシェーダ内で利用することができる変数です。あくまでも、その頂点シェーダが実行されているときの対象となる頂点のインデックスが取得されてくる形ですので、入力専用です。
WebGL 1.0 のときにも、頂点が何番目に定義されたものなのかによって処理を分岐したい、というケースが結構ありました。これを従来の GLSL ES 1.0 で実現するためには、頂点属性として、つまり attribute 変数として頂点自身にインデックスに相当する情報を与えるしか方法がありませんでした。
GLSL ES 3.0 からは、デフォルトでシェーダ自身がそれに相当するインデックスを提供してくれますので、わざわざ頂点属性を追加して VBO に含めてやる必要はありません。今回紹介する gl_VertexID
を参照しながら処理を行ってやることで、たとえば一定のルールに則って頂点を異なるアルゴリズムで座標変換したりといったことが可能になります。
続いては gl_InstanceID
のほうですが、こちらはインスタンス描画を行っている場合にのみ意味を持ちます。
つまり、この ID を参照するとインスタンシングによって描かれているときのインスタンスのインデックスが取得できます。インスタンシングによって複数のモデルをレンダリングしている際に、シェーダ内で、そのインスタンス番号を取得できるわけですね。
こちらのビルトイン変数も、やはり入力専用です。シェーダ内で動的にインデックス番号を変更したりできるわけではないので、その点には注意しましょう。
また、ここで紹介したいずれの ID も、データ型は int
として定義されています。
もしシェーダのなかでこれらの ID を参照した処理を行う場合には、この ID のデータ型の扱いに気をつけながら利用するようにしましょう。
ID に応じた分岐処理
さて、それでは非常に簡単な例を見ながら、その使い方を考えてみましょう。
今回のサンプルはあまり実用性の高い処理というわけではないのですが、インスタンス描画を行いつつ、頂点の ID も取得しながら、その効果がわかりやすいように処理しています。
まずはシェーダのコードを見てみましょう。
ID を利用した頂点シェーダの記述例
#version 300 es
layout (location = 0) in vec3 position;
layout (location = 1) in vec3 normal;
layout (location = 2) in vec2 texCoord;
layout (location = 3) in vec3 offset;
uniform mat4 mMatrix;
uniform mat4 mvpMatrix;
uniform mat4 normalMatrix;
out vec3 vPosition;
out vec3 vNormal;
out vec2 vTexCoord;
void main(){
vec3 pos = position + offset;
if(mod(float(gl_VertexID), 4.0) == 0.0){
pos += normal * 0.05;
}
vPosition = (mMatrix * vec4(pos, 1.0)).xyz;
vNormal = (normalMatrix * vec4(normal, 0.0)).xyz;
vTexCoord = texCoord;
if(mod(float(gl_InstanceID), 2.0) == 0.0){
vTexCoord = 1.0 - vTexCoord;
}
gl_Position = mvpMatrix * vec4(pos, 1.0);
}
さてちょっと文字数は多いですが、ポイントを絞って考えてみましょう。
まず今回の機能は GLSL ES 3.0 で追加されたものですので、必然、GLSL ES 3.0 で処理することが前提です。そして、当たり前といえば当たり前ですが、今回の頂点に関する ID はいずれも頂点シェーダのみで利用できます。
あまり効率などは考えず処理してしまっていますが、上記の main
関数のなかで、実際に ID を用いた処理が行われているのがわかるかと思います。先に出てくるのは gl_VertexID
のほうで、一度型を変換してから 4 で割ってその余りを使った分岐処理を行っています。
つまり、頂点の持つインデックスが 4 の倍数になる場合とそれ以外で、異なる処理が行われるようになっているわけですね。
もしも頂点のインデックスを 4 で割った余りが 0 になる場合は、頂点の座標を法線方向にほんの少しだけ膨らませています。こうすれば、4 つにひとつの割合で、頂点が少しだけ外側に飛び出したような感じになるはずです。
そして同様に gl_InstanceID
を使っているところも、やはり分岐処理が書かれていますね。
こちらは 2 で割った余りが 0 になる場合だけ、テクスチャ座標を上下反転させています。ですから、インスタンスがひとつ増えると、順番にテクスチャの貼られ方が上下に反転するような感じになります。インスタンシングによって描くモデルが複数の場合でないと効果がわかりませんので、それだけ注意です。
まとめ
さて GLSL ES 3.0 で追加された新しいビルトイン変数について見てきましたが、いかがでしたでしょうか。
今回のサンプルは実用性ほぼゼロの簡易なものですが、頂点のインデックスなどを取得することができれば、同じシェーダのなかで個別に頂点を制御することができるようになり、より複雑なロジックを組み込んでいくことも可能になります。
たとえば大量の頂点をパーティクルとして描こうとしているとき、特定の範囲の頂点だけを別の制御ロジックで動かしたりすれば、ひとつのシェーダでかなり複雑な動きを頂点に対して適用することもできるでしょう。工夫次第で、表現に様々な幅を持たせることできる可能性がある非常に面白い機能だと個人的には思います。
またこれが既定の機能として使えることも、嬉しいポイントです。
実際に動作するサンプルは以下のリンクから。ぜひこれらを活用した様々な表現にトライしてみてください。