時間経過とマウスカーソル座標
今回のサンプルの実行結果
よりインタラクティブに
前回は、GLSL だけでレンダリングを行うための、基本となるコードについて解説しました。
今回からは、前回の内容をベースとしつつ、フラグメントシェーダを用いた様々な表現について解説していきたいと思います。
GLSL、特にフラグメントシェーダを利用して多種多様なシーンを描き出すことを目標とする本章。素晴らしい風景写真や芸術的な絵画がそうであるように、静止画でも素晴らしいと感じるものはこの世界にたくさんあります。ただ、よりユーザーとの対話性を重視した柔軟な描画を行うこともまた楽しいものです。
今回は時間の経過やマウス座標を利用しつつ、動きのある描画に挑戦してみましょう。
シェーダ内でのマウス座標の扱い
前回のテキストで利用したサンプルプログラムでも、uniform 変数を利用して[ 経過時間 ]と[ マウスカーソル座標位置 ]はシェーダに送られる仕組みになっていました。
しかし、前回の場合はあくまでもベースとなる基本的なコードということもあり、時間とマウスに関しては利用せずにシェーダを動かしていました。今回のサンプルでは、これらについても利用しつつ、簡単なシェーダで動作確認をしてみましょう。
それでは早速ですが、今回のサンプルのフラグメントシェーダコードです。
フラグメントシェーダ
precision mediump float;
uniform float time;
uniform vec2 mouse;
uniform vec2 resolution;
void main(void){
vec2 m = vec2(mouse.x * 2.0 - 1.0, -mouse.y * 2.0 + 1.0);
vec2 p = (gl_FragCoord.xy * 2.0 - resolution) / min(resolution.x, resolution.y);
float t = sin(length(m - p) * 30.0 + time * 5.0);
gl_FragColor = vec4(vec3(t), 1.0);
}
前回と同様、uniform 変数としてシェーダに入ってくるのは三つ。ちなみに、これは今後もしばらくは変わらない共通の仕様になります。※将来的にはテクスチャなどが必要になってきたら自然と追加されることになると思います。
さて、 main
関数の冒頭、一行目で変数 m
に値を代入している部分が、マウスカーソル座標に関するコードですね。
ここでは、0 ~ 1 の範囲で入ってくるマウスカーソル位置を、-1 ~ 1 の範囲に修正する処理を行っています。ここで注目すべき点は、Y 座標に関しては正負を逆転するように処理を書いているという点でしょう。
ブラウザ上のマウスカーソルの位置は、左上を原点として相対的に計測されるのが基本ですよね。前回のテキストで詳しく触れましたが、javascript 側ではピクセル単位で取得されるカーソル座標の位置を 0 ~ 1 の範囲に正規化することしかしていませんでしたね。
GLSL 側でこれをそのまま使ってしまうと、Y が下に行けば行くほどプラスになっていくため、2D や 3D で一般に使われる[ 上方向がプラス ]という座標系のイメージと逆になってしまいます。
別に javascript の側でこの Y 軸の変換までやってしまっても特別問題はないのですが、当サイトのサンプルではこのように GLSL 側で修正するような仕様で統一しています。
時間経過とサイン波
マウスの座標位置を -1 ~ 1 の範囲に修正できたら、前回も登場した、これから処理しようとしているピクセルの座標を、正規化した上で変数 p
に代入する処理が出てきています。ここは前回とまったく同じです。特別なことをする場合を除き、この正規化処理は基本的に毎回登場します。
一応説明すると、変数 p
には処理対象のピクセル位置に応じて、-1 ~ 1 の範囲の値が入ることになるのでしたね。
マウス座標、そして処理対象ピクセル座標のそれぞれの正規化ができたら、続いて float
型の変数になにかを計算しているコードが出てきます。
対象コードを抜粋
float t = sin(length(m - p) * 30.0 + time * 5.0);
さて、このコードはいったいなにをやっているのでしょうか。
先ほど、マウス座標は変数 m
に、そして処理対象ピクセルは変数 p
に代入しました。この二つの差分を GLSL のビルトイン関数 length
に渡すことで、両座標間の距離が取得できます。ビルトイン関数の length
は、その名が示すとおりベクトルの長さを float
型で返してくれる関数です。二つの座標の差分を取ると、それはすなわち 2 点間の距離をそのまま大きさとして持つベクトルになりますので、結果的に、マウス座標と処理対象ピクセルとの距離がわかるというわけですね。
サンプルでは、さらにそれを 30 倍したあと、時間の経過を表す uniform 変数である time
の値を加算します。最終的な計算結果を sin
関数にそのまま渡してサイン波を出力しているわけですね。
sin
の返してきた値をそのまま RGB の各要素として色を出力すると、冒頭のサンプル画像にあったような複数の輪が表れます。時間経過を表す time
を加算していますので、特になにもしなくても自然とアニメーションが起こり輪の大きさが時間の経過とともに変化します。
また、マウス座標の位置を原点として相対的に距離を測るようにしていますので、canvas 上でマウスカーソルを動かすと、マウスカーソルを原点として輪が生成される様子を確認できるでしょう。
応用とまとめ
今回のサンプルは冒頭でも書いたように、時間の経過や、ユーザーによるマウス操作によってレンダリング結果が変化します。
より柔軟なコードを記述できるようになるためにも、マウス座標の基本的な扱い方や、時間経過の効果的な利用方法の基本は押さえておきましょう。
また、今回のサンプルは GLSL のフラグメントシェーダのコードをほんの少し修正するだけで、様々に見た目を変えることができます。
たとえば、レンダリングされる輪の大きさは時間の経過によってアニメーションしますが、このアニメーション速度をもっと早くしたり、あるいは遅くしたりするにはどうしたらいいでしょうか。
答えは簡単で、uniform 変数 time
に掛けている 5.0 という値を変更してやればいいですね。
同様に、輪と輪の間の隙間をもっと狭くしたり、あるいは広げたりしたければどうしたらいいでしょうか。この場合は、 length
の結果に対して掛けている 30.0 という値を変更してやればいいですね。それだけで輪の描かれる間隔が狭まったり広がったりするはずです。
このように、たった一つの数値を変更するだけでレンダリング結果が劇的に変わるのがフラグメントシェーダを用いるレンダリングの面白いところです。サンプルを参考にしつつ、様々にパラメータを変化させて遊んでみると、思いもよらない発見があるかもしれません。
今回も、実際に動作するサンプルを以下のリンクより参照できます。