WebGL & Three.js

【WebGL】GLSLの曲者varyingとuniformとは?その挙動の説明と使い方について

WebGL(GLSL)でデータを受け渡すattribute・varying・uniformのうち、特にわかりにくいvaryingの挙動(頂点シェーダからフラグメントシェーダへ線形補間されて渡る仕組み)とuniformの違い、WebGL2でのout/inの書き方まで解説します。

この記事の目次
  1. varyingとは?
  2. varyingのメリット
  3. スムーズなグラデーションができる
  4. 光の反射を計算できる
  5. 物体に画像や動画を貼り付けられる
  6. varyingを実装段階で覚えておきたいこと
  7. WebGL2(GLSL ES 3.00)では out / in と書く
  8. uniformとは
  9. uniformの使い方
  10. varyingとuniformの違いまとめ

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と書きます。attributeinに変わります。

// 頂点シェーダ(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はフラグメントごとです。

スポンサーリンク