様々な図形を描く
今回のサンプルの実行結果
創意工夫を重ねて
前回は、オーブ(光の玉)を GLSL だけでレンダリングしてみました。
最終的なレンダリング結果は似ていても、そこに至るプロセスには様々なやり方があったのでしたね。個人的には、プログラマの工夫次第でどのような表現にも対応できるという点が GLSL シェーダプログラミングのいいところなのではないかと思っています。
さて今回ですが、前回と同じように様々な工夫を凝らしつつ、いろいろな図形のレンダリングに挑戦してみましょう。前回同様、順を追ってレンダリング結果を掲載しつつ解説していきます。
途中でこれは使えそうだなと思うような表現やコードを見つけたら、ぜひ自分なりに修正しながら使ってみてください。思わぬ表現方法が、不意に見つかるかもしれません。
リングを描く
前回表示したのは、円形の球状で描く光の表現でした。
まずは、これをリング状の輪にしてみましょう。
以下にコードを掲載しますが、どのようにしたら光のリングがレンダリングできるのか――自分なりに考えてからコードを見てみるのもいいかもしれません。
また、前回も書いたことですが同じような表現を行うコードは一通りとは限りません。本テキストで掲載したコード以外にも、もっと効率的に、もっとスマートに記述できる方法があるかもしれません。
そんなことを考えつつ、参考にしてみてください。
それではまず、今回のサンプルのベースとなるシェーダコードから見てみます。
ベースとなるフラグメントシェーダコード
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);
// ring
float t = 0.02 / abs(0.5 - length(p));
gl_FragColor = vec4(vec3(t), 1.0);
}
前回のサンプルと大差ありません。
マウス座標と、処理対象ピクセルの正規化処理を行った後、float
型の変数 t
に、なにかしらの計算を行った結果を格納しておきます。
最終的なレンダリングには、この変数 t
の値をそのまま RGB の値として gl_FragColor
に代入します。
シンプルで、簡単ですね。
そして上記のコードは、スクリーン上にリング状の光の輪を描き出します。
リング状の光の輪をレンダリング
どうしてこのような結果になるのか、コードを見て理解できるでしょうか。
// ring
float t = 0.02 / abs(0.5 - length(p));
前回のオーブのレンダリングでは、length
の戻り値を使って、そのまま除算処理を行っていました。今回の場合、0.5 から 減算した上、さらに GLSL のビルトイン関数 abs
を使って絶対値を取っています。
絶対値とは、数学的にはいろいろと難しいこともあるのですが、簡単に言うと0 からどれだけ離れているかを表す数値です。もっともっと完結に言うなら、負の数値も強制的に正の値にするということです。
float t = abs(0.5); // t == 0.5
float u = abs(-0.5); // u == 0.5 ※どちらも abs によって 0.5 になる
先ほどのフラグメントシェーダのコードでは、0.5 から減算する処理を用いることによってスクリーンの中心(原点)から、正負は関係なく純粋に距離が 0.5 となる場所ほど数値が小さくなります。
前回のオーブのときに紹介したコードと同様に、小さい数値で除算するということは結果的に大きな数値が得られることになります。これにより、原点から 0.5 の距離にある部分一帯にリング状の光の輪が表れるわけですね。
様々なレンダリング
さて、ここからは少しペースアップしていきましょう。
先ほど掲載したベースとなるコードの、変数 t
に値を入れる部分だけを様々に変化させてみます。
まずはこんなコード。
リングの大きさを時間経過で変化させる
// time scale ring
float t = 0.02 / abs(sin(time) - length(p));
これは簡単ですね。
sin
と time
を組み合わせて、リングの大きさが時間の経過とともに変化するようにしただけです。
続いては、画面上にグラデーションを表示してみましょう。
これ単体ではあまり面白くないですが、割といろんなことに利用できます。
画面上にグラデーションを表示
// gradiation
vec2 v = vec2(0.0, 1.0);
float t = dot(p, v);
この場合、vec2
型の変数 v
に Y 方向にプラスとなるベクトルが入っています。これと p
で内積をとった結果をそのまま色に出力した結果が、グラデーションになります。
これは、内積がどのような計算を行うのかが理解できていれば、自然とグラデーションになる仕組みはわかるでしょう。
そして、このグラデーションを改造すると、円錐を真上から見た時のような、不思議なレンダリング結果を表現することもできます。
上から円錐を見たような描画
// cone
vec2 v = vec2(0.0, 1.0);
float t = dot(p, v) / (length(p) * length(v));
先ほどの内積の結果を、length
を用いた計算結果で除算しています。
この場合だと上方向にのみグラデーションが発生していますが、abs
などを駆使すれば当然上下や左右に複雑に展開させることもできますね。
アークタンジェントの活用
ここからはアークタンジェント、GLSL のビルトイン関数でいうと atan
を利用したレンダリングを見ていきましょう。
まずはこんなコード。
集中線のような放射状のライン
// zoom line
float t = atan(p.y, p.x) + time;
t = sin(t * 10.0);
変数 p
の X と Y それぞれを atan
に渡し、その結果からサイン波を作ります。
なんとなく、どうしてこんな模様が現れるのか不思議ですね。三角関数について調べてみると、きっと理解できると思います。
続いてはこんなコード。
これは、先ほどの集中線のロジックと、最初のほうに出てきたリングを描いたロジックの合わせ技です。
花のような模様を描く
// flower
float u = sin((atan(p.y, p.x) + time * 0.5) * 6.0);
float t = 0.01 / abs(u - length(p));
若干コードが複雑になってはいますが、よくよく見てみれば、さっきまでとやっていることは同じというのがわかるのではないでしょうか。
先ほどの集中線を描いたときにやっていたことを、変数 u
に値を代入している部分にまとめてしまい一行にしています。さらに、リングを描いたロジックを利用して変数 t
に値を取得しているのですね。
さらに、これと同じロジックを使いつつ、係数をいくつか変更するだけで波打つリングを表現することもできます。
波打つリングを描く
// wave ring
float u = sin((atan(p.y, p.x) + time * 0.5) * 20.0) * 0.01;
float t = 0.01 / abs(0.5 + u - length(p));
コードの基本構造はほとんど同じで、四則演算される各種係数がちょっと変わっている感じですね。ほんの少しの違いですが、こんなにもレンダリング結果は変化します。
なんかカップケーキとかに使うアルミのカップを思い出します。実に楽しい。
応用として、こんな風に花びらの数を極端に増やすことも。
花模様
// flower
float u = abs(sin((atan(p.y, p.x) + time * 0.5) * 20.0)) * 0.5;
float t = 0.01 / abs(0.25 + u - length(p));
実に美しいレンダリング結果ですよね。
これも、係数をいくつか変更しただけに過ぎないのですが、印象はだいぶ変わりましたね。
さて、最後はこの花模様にもうひと手間加え、まるで扇風機の羽のようにひねりを加えてみましょう。
花模様をファンのように変形
// fan
float u = abs(sin((atan(p.y, p.x) - length(p) + time) * 10.0) * 0.5) + 0.2;
float t = 0.01 / abs(u - length(p));
ここまで順番に見てきていれば、GLSL のコードが少しずつ変化し、このような結果になっていることがわかると思います。
まとめ
さていかがでしたでしょうか。
本当にちょっとした工夫ひとつで、いろいろな表現ができるということが理解できたのではないでしょうか。
今回紹介したコードは、様々な表現の下地となるようなものばかりです。これらを応用したらどんなことができるだろう……そんなふうにさらに想像力を膨らませつつ、参考にしていただければと思います。
また、全体に非常に行数の少ないコードで様々な表現ができるのだということも、なんとなく感じたのではないでしょうか。前回も書きましたが、コードをいろいろ修正しながらチェックなどを行う研究の段階では、極力コードは省略せずにわかりやすくコードを記述していきましょう。最終的に、最適化はいくらでもできますからね。
サンプルは以下のリンクから。今回紹介した各種の処理は、コメントアウトして GLSL のソースにすべて含めてあります。