WebGL(GLSL)において、データを受け渡しする方法には3つあります。
- attribute
- varying
- uniform
その中でもvaryingは特にわかりにくいです。
varyingの挙動が理解できれば、GLSLのデータの流れはほぼ把握できたと言えるくらい重要なものです。
attributeについては、attributeと頂点シェーダについてをご確認ください。
varyingとは?
GLSLのvaryingは、頂点シェーダで値を書き込み、フラグメントシェーダで読み取るための変数修飾子です。頂点ごとに代入した値は、ラスタライズの段階でピクセル(フラグメント)ごとに線形補間され、各フラグメントには「周囲の頂点の値を混ぜた中間値」が届きます。uniformが全フラグメントで同じ値なのに対し、varyingはフラグメントごとに違う値になる点が最大の違いです。
varyingとは、頂点シェーダからフラグメントシェーダへの値の受け渡しをするデータのことです。
JavaScriptから頂点シェーダへは、attributeが渡されますが、頂点シェーダからフラグメントシェーダへは、このvaryingが渡されます。
attributeとは?と不明な場合は、上の頂点シェーダのリンクを確認してみてください。
varyは英語で「変化する」を意味する単語です。
特徴は、ユーザーが自由に定義でき、頂点間のフラグメントには線形補間された値が渡る点です。
varyingのメリット
スムーズなグラデーションができる
このvaryingのおかげで頂点間のフラグメントは線形補間されるため、フラグメントシェーダでの色がなめらかになり、グラデーションも綺麗に表現できます。
WebGLを使ったカラフルなグラデーションのサンプルデモはこちらで確認できます。
光の反射を計算できる
頂点の法線をフラグメントに渡すことで、光の反射を計算できます。
物体の明るさを、光の向きを考慮して決められます。
物体に画像や動画を貼り付けられる
varyingでuv座標をフラグメントシェーダに渡すと、物体に画像や動画を貼り付けて表現できます。Three.jsでの実装例はShaderMaterialで画像や動画を出力する方法で確認できます。
varyingを実装段階で覚えておきたいこと
頂点シェーダからフラグメントシェーダへは、このvaryingが渡されます。
つまり、頂点の位置がそれぞれ決定された後にvaryingが渡されます。
頂点シェーダでたとえば以下のコードを指定します。
attribute vec3 position; //positionはそれぞれの頂点で異なる値を保持
varying vec3 vertexPosition; //フラグメントに渡すvertexPositionを宣言
vertexPosition = position; //頂点ごとに代入
positionはそれぞれの頂点で異なる値を保持します。
フラグメントに渡すvertexPositionを宣言します。
そして、頂点ごとにvertexPositionをpositionに代入します。
フラグメントシェーダでは以下のようなコードになります。
varying vec3 vertexPosition;
こうすることでvertexPositionに値が渡ってくるのですが、このときの値は、各フラグメントで異なります。
各フラグメントに、線形補間された値が渡ってきます。
両方のシェーダで同じ型・同じ名前で宣言することが条件です。片方だけvec2にしたり、名前を変えたりするとリンク時にエラーになります。
WebGL2(GLSL ES 3.00)では out / in と書く
WebGL2で使うGLSL ES 3.00ではvaryingというキーワードはなくなり、頂点シェーダ側をout、フラグメントシェーダ側をinと書きます。attributeもinに変わります。
// 頂点シェーダ(GLSL ES 3.00)
in vec3 position;
out vec3 vertexPosition;
// フラグメントシェーダ(GLSL ES 3.00)
in vec3 vertexPosition;
Three.jsのShaderMaterialは既定でGLSL ES 1.00形式(varying)を受け付け、WebGL2環境では内部で変換してくれます。自分でGLSL ES 3.00を書く場合はglslVersion: THREE.GLSL3を指定します。
uniformとは
uniformとは、頂点シェーダとフラグメントシェーダに一様な(uniformな)値を渡すものです。
「1回の描画の間、全部の頂点と全部のフラグメントで共通のデータ」と考えると分かりやすいです。
varyingと同じく、ユーザーが定義できます。
異なる点は、varyingのような線形補間がなく、全部の頂点と全部のフラグメントに同じ値が渡ることです。
用途は、経過時間を使ったアニメーションや、クリックされたときに状態を変化させるアニメーションなどです。時間をuniformで渡してノイズを動かす例はglslifyのノイズの使い方とデモで確認できます。
uniformの使い方
uniformの宣言はvaryingとほぼ同じで、以下のように定義して、JavaScriptなどからWebGLに値を渡せます。varyingと違い、シェーダ側で値を代入することはできません(読み取り専用)。
// 頂点シェーダ
uniform float u1;
void main() {
vec3 p = position + u1; // attributeも読み取り専用なので別の変数に入れる
gl_Position = ...
}
// フラグメントシェーダ
uniform float u1;
void main() {
gl_FragColor = ...
}
JavaScript側からの渡し方は環境で異なります。素のWebGLではgl.getUniformLocation()で場所を取得し、gl.uniform1f(location, value)のような型別の関数で毎フレーム更新します。Three.jsのShaderMaterialではuniforms: { u1: { value: 0 } }と定義し、material.uniforms.u1.valueを書き換えるだけで反映されます。
varyingとuniformの違いまとめ
- uniform:JavaScriptから渡す。全頂点・全フラグメントで同じ値。シェーダ内では読み取り専用
- varying:頂点シェーダで代入し、フラグメントシェーダで受け取る。フラグメントごとに線形補間された値
- attribute:JavaScriptから渡す頂点ごとのデータ。頂点シェーダでのみ読める
「いつ・どこで値が変わるか」で覚えると迷いません。uniformは描画ごと、attributeは頂点ごと、varyingはフラグメントごとです。