Edited at

[連載]やってみれば超簡単! WebGL と GLSL で始める、はじめてのシェーダコーディング(3)

More than 5 years have passed since last update.


まずは挑戦してみよう

シェーダを自分でコーディングするなんて……

きっとお難しいんでしょ……

と、お思いの奥様方。そんなことはないんです。コツをつかめば意外と楽しめます。当連載では、シェーダというものに対して抱かれてしまいがちな、漠然とした 難しそう感 を払拭すべく、簡単なシェーダの記述とその基本について解説したいと思います。

前回:[連載]やってみれば超簡単! WebGL と GLSL で始める、はじめてのシェーダコーディング(2) - Qiita


想定する読者

当連載では、シェーダってなんか難しそう……とか、シェーダプログラミング始めてみたいけど……とか、なんとなく興味を持ってるけどシェーダを記述したことがない方を読者に想定しています。

たとえば Unity などのツール、あるいはマインクラフトのようなゲーム、またはモデリングソフトなどでもシェーダを自分で記述することができるような世の中です。きっとシェーダに触れた経験は無駄にはならないでしょう。

すぐに業務で活かすとか、そういう壮大な話はさておいてまずは気軽にシェーダに触れてみましょう。

難しい 3D の数学的知識はとりあえず要りません。もちろん難しいことをやろうとする場合は話が変わってきますが、当連載ではそのあたりの知識は求めません。ちょっとくらい、三角関数とかは出てきますがそんなに難しくないですから安心してください。


対象のシェーダ記述言語は GLSL

「シェーダ」という言葉には非常に広い意味が含まれるので、世の中にはシェーダといってもいろいろなものが存在しています。また、それを記述するための専用の言語にも、いくつか種類があったりして非常に初心者には敷居が高いのかなと思います。

当連載では GLSL というシェーダ専用言語を用います。

GLSL は 昨今話題の WebGL でも採用されているので、特別な開発環境の準備などをしなくても、ブラウザとテキストエディタさえあれば簡単に始められます。

また、当連載では著者自作の GLSL editor というオンラインでシェーダが記述できるエディタを使いますので、もはやブラウザとネット環境さえあればシェーダが書けます。気軽ですね。

それでは早速、前回に引き続き、シェーダプログラミングについて考えていきましょう。


時間の経過によるアニメーション

前回は、GLSL editor ではフラグメントシェーダというシェーダのコードを記述していることや、処理対象のピクセル座標を取得して、グラデーションを表示する方法などについて解説しました。

今回は、ここに時間の経過によるアニメーションを加えて、動的に生成されるシェーダの面白い世界へと足を踏み入れてみましょう。

連載の第一回で触れたとおり、GLSL editor には時間の経過によって格納される値が変化する、特殊な変数が定義されています。それがどんなものだったか憶えているでしょうか。

シェーダに外部からデータが送られてくる変数には uniform というキーワードが使われます。そして、GLSL editor にはこの uniform 変数である t という変数が定義されていたのでしたね。

この uniform 変数 t の中身は、秒単位の浮動小数点数データが入ってきます。

シェーダの実行が開始されてから、ちょうど 1 秒経過しているなら 1.0 が入っていますし、1 秒半経過しているなら、変数 t には 1.5 が格納されています。

この値をうまく利用すると、時間の経過と共にアニメーションする処理を書くことができるようになります。今回はこれを使って、画面の色がめまぐるしく変化するような、そんなシェーダを記述してみましょう。


時間をどう扱うのか

時間経過によるアニメーションを考える場合、ポイントになるのは外部からシェーダに送られてくるその数値をどのように扱うのかです。

先ほども書いたように、変数 t の中身は時間の経過と共にどんどん大きな数字になります。しかし、これをそのまま色の出力に使ってしまうと、ちょっと困った問題が起こります。

その困った問題というのは、色を表す場合の数値の範囲です。

GLSL における色の表現は、0 ~ 1 の範囲で行います。vec4(1.0, 1.0, 1.0, 1.0) のように、最大値が 1.0 になるわけです。仮に、1.0 よりも大きな数値を指定した場合でもエラーにはなりませんが、1.0 より大きな数値はすべて 1.0 に強制的に丸められてしまいます。そうなると、uniform 変数の t の値をそのまま利用してしまうと、たったの一秒たらずで色表現の最大値に達してしまうことになってしまいます。

これに対処する方法はいろいろ考えられますが、一般によく利用されるのが、三角関数を利用した方法です。

三角関数……なんて言われるといかにも難しそう! というイメージがありますが、ちょっと考え方を変えてみればどうということはないです。むしろ簡単です。

臆せず見ていきましょう。


サインとコサイン

三角関数といえば「サイン、コサイン、タンジェント」のように皆さん一度は耳にしたことがあると思います。

シェーダでよく利用するのは、サインとコサイン。これはシェーダ内では sincos という風に書きます。

これらを三角関数だと思ってしまうとすごく難しい印象を受けると思いますが、要は 使い方が大事 なだけで、全然難しくはありません。

シェーダにおける sincos といった関数は、引数に float 型の数値(正確にはラジアン)を受け取り、それに対応した値を返してきます。

サインの場合もコサインの場合も、どのような値を渡しても必ず戻り値の範囲は -1 ~ 1 の範囲に収まります。なぜこの範囲に収まるのかは、三角関数が理解できていれば簡単ですし、むしろ理解できていなくても現時点ではまったく問題ないです。

大切なポイントは、これらの関数が返してくる値は、引数に指定された数値が大きかろうが小さかろうが 必ず -1 ~ 1 の範囲に収まる という事実です。

先述のとおり、uniform float t という外部からの入力は、時間の経過と共に値がどんどん大きくなっていきます。普通に使ったら大きすぎる数値は扱いにくいですが、これをサインやコサインといった関数にぶち込んでやれば、その戻り値の範囲は必ず -1 ~ 1 の間に収まります。

戻り値の範囲があらかじめ明確になっているなら、これを色として使える状態に変換するのは簡単です。

たとえば、次のようにシェーダを記述するとどうでしょうか。

precision mediump float;

uniform float t; // time
uniform vec2 r; // resolution

void main(void){
float r = abs(sin(t)); // *1
float g = abs(cos(t));
float b = (r + g) / 2.0; // *2
gl_FragColor = vec4(r, g, b, 1.0);
}

さて、上のコードの中で *1 となっている行に注目してみましょう。

ここでは、外部入力の変数 t を、サインの中に入れています。時間の経過によって t の中身はどんどん大きな数値になっていきますが、サインで包んでいることによって範囲は常に -1 ~ 1 の範囲に限定されることになります。

さらに、そのサインの計算をもう一つの関数で包んでいるのがわかりますね。

外側にある abs も、シェーダ内で最初から使うことができる組み込みの関数で、この関数は絶対値を返してくれるものです。わかりやすく言うと、マイナスの数値であってもプラスに変換してくれるもの、と考えるのがいいと思います。

abs がマイナスも強制的にプラスにしてくれるので、サインの計算結果である -1 ~ 1 という範囲がさらに変換されて、最終的には 0 ~ 1 の範囲に収まるようになります。こうなってしまえば、あとはもう色の出力としてこの値をそのまま gl_FragColor に突っ込めますね。

ちなみに、コサインの場合もサインと同じように abs で絶対値を取ることで 0 ~ 1 の範囲に値を変換しています。

赤の要素としてサインを利用した値を。そして緑の要素としてコサインを利用した値をとっておきます。

青の要素に使うのは、rg を合算して、それを 2 で割ったものです。rg も最大値は 1.0 ですから、これらを足した結果を 2 で割れば、やはり 0 ~ 1 の範囲の数値を得ることができるわけですね。

このようなシェーダを実行すると、時間の経過と共にスクリーン上の色が目まぐるしく変化するようになるはずです。実際に GLSL editor にコードを貼りつけて実行してみると、色が動的に変化する様子がよくわかると思います。


時間の経過を調整する

さて、ここからは応用です。

時間の経過は uniform 変数 t に送られてきます。この t の中身が増えていく速度を調整すれば、当然アニメーションのスピードを調整することもできることになります。

たとえば、先ほどのコードを少しだけ修正して、次のようにするとどうなるでしょうか。

precision mediump float;

uniform float t; // time
uniform vec2 r; // resolution

void main(void){
float r = abs(sin(t * 0.1));
float g = abs(cos(t * 2.0));
float b = (r + g) / 2.0;
gl_FragColor = vec4(r, g, b, 1.0);
}

このように、変数 t に係数を掛けてから利用するようにすれば、時間の経過を自在に操ることができるようになります。

上記のようなコードの場合、赤の色は時間の経過が十分の一の速度でゆっくりと変化するようになる一方、緑の色は二倍の速度で素早く明滅することになります。

もっと極端に大きな数字を使ったりしてみれば、ほとんど点滅と言っても差支えないような高速な時間経過処理を行うこともできるでしょう。

シェーダプログラミングの面白いところは、上記の例で示したように、ほんの少しの係数の違いによって、描画結果が劇的に変化しやすいという側面があると思います。

ちょっと係数を変化させたり、符号を反転させたり、あるいはサインやコサインを使ってみたりするだけで、思いもよらない視覚効果が生まれることがあるのですね。これが非常に面白い。

GLSL editor は、シェーダのコードを変更してすぐに実行できるように作られています。

また、ソースコードを短縮 URL としてシェアしたり、ローカルにダウンロードしたりする機能も付けられています。

ぜひ、GLSL editor を活用して面白いシェーダのコードをたくさん書いてみてください。慣れてくると本当に楽しいですよ。

さて、次回からはもう少し工夫して模様を描くことにチャレンジしてみましょう。ほんの少しの工夫で、思いもよらない美しい模様や映像が作れるようになっていきます。

お楽しみに!

次回:[連載]やってみれば超簡単! WebGL と GLSL で始める、はじめてのシェーダコーディング(4) - Qiita