グラデーションの実装
- 頂点シェーダー
- フラグメントシェーダー
今回はグラデーションの実装について考えてみる。
前回まではgl_FragColorに固定の値を格納していた。つまりすべてのフラグメントシェーダーで同じ値を実行していたので、単色のオブジェクトがレンダリングされていた。
一方でグラデーションを表現するためには、ピクセルごとに色を変える必要があるので、gl_FragColorに格納する値を変える必要がある。その値をどのように用意するのか。まずはgl_FragCoordについて考えてみる。
gl_FragCoordはGLSLの組み込み変数であり、対象となるピクセルの座標が入っている。gl_FragCoordはvec4型ではあるが、gl_FragCoord.xyとすれば、x座標とy座標が取得できる。たとえば一辺が500pxの正方形だとすると、左下が(0, 0)、右上が(500, 500)の値となる。この時左下が原点であることに注意する。
では早速gl_FragCoordを使って、以下のようにfragmentShaderを実装してみる。黒から赤へのグラデーションが何回か繰り返されているのが確認できる。
先にfractについて簡単に説明しておくと、fractはGLSLの組み込み関数で、x - floor(x)の計算をしてくれる関数である。「The Book of Shaders: fract」を読むとイメージがつきやすい。ようは値を渡すと少数部分を返してくれる関数であり、たとえばfract(1.5)は0.5、fract(2.3)は0.3となる。
// gl_FragCoord.x にはピクセルのx座標が渡ってくる
// 仮に横幅が 800px なら 0 ~ 800 の値が gl_FragCoord.x の範囲になる
// fract(gl_FragCoord.x / 100.0) は 0 以上 1 未満
float red = fract(gl_FragCoord.x / 100.0);
では上記では何をしているのだろうか。
gl_FragCoord.xにはピクセルごとのx座標が渡ってくる。本記事ではCanvasの横幅がそのまま横幅の値になるのだが、デバイスやブラウザの画面幅によってCanvasのサイズは異なる。一応本記事ではCanvasが最大でも800pxになるようにコーディングしているので、gl_FragCoord.xの値は0 ~ 800の間である(デバイスや画面幅によっては最大値が小さくなる)。
したがってgl_FragCoord.x / 100.0は0 ~ 8であり、fract(gl_FragCoord.x / 100.0)はx軸に基づいて、0以上1未満の値が100pxごとに、最大8回繰り返されることとなる。この値をredとしてgl_FragColorに格納しているので、グラデーションが繰り返される模様となった。
次にred = gl_FragCoord.x / Canvasの幅としてグラデーションを実装したい。これができればredはCanvasの幅に関係なく、左端が0、右端が1となる。つまり左から右にかけて、黒からだんだん赤になるグラデーションを実装できる。
これを実現するためには、uniformを使ってJavaScriptからGLSLにCanvasの大きさを渡せば良いのだが、ここで「WebGLProgram – three.js docs」を読んでみる。
頂点シェイダーにはattribute vec2 uv;が定義されているのがわかる。これはThree.jsがビルドインで用意しているUV座標であり、まさに先ほど欲しかったgl_FragCoord.x / Canvasの幅の値に合致する。厳密言うとThree.jsにおけるUV座標は(x, y)の2次元座標であり、左下を原点(0, 0)として、右上が(1, 1)となる座標である。
UV座標は頂点シェイダーにしか用意されていないのでvaryingを使ってフラグメントシェイダーに渡す必要がある。
黒から赤に変化するグラデーションの実装ができた。簡単にコードをみてみる。
varying vec2 vUv;
void main () {
vUv = uv;
//...
}
UV座標をフラグメントシェーダーに渡すために、頂点シェーダーでvarying vec2 vUv;を宣言している。mainの中でuvをvUvに格納した。
varying vec2 vUv;
void main () {
float red = vUv.x;
//...
}
フラグメントシェーダーでもvarying vec2 vUv;を宣言し、UV座標を受け取る。そのうちのx座標をredに代入しているので、x軸に基づいて黒から赤になるグラデーションとなった。