まずは挑戦してみよう
シェーダを自分でコーディングするなんて……
きっとお難しいんでしょ……
と、お思いの奥様方。そんなことはないんです。コツをつかめば意外と楽しめます。当連載では、シェーダというものに対して抱かれてしまいがちな、漠然とした 難しそう感 を払拭すべく、簡単なシェーダの記述とその基本について解説したいと思います。
前回:[連載]やってみれば超簡単! WebGL と GLSL で始める、はじめてのシェーダコーディング(7) - Qiita
想定する読者
当連載では、シェーダってなんか難しそう……とか、シェーダプログラミング始めてみたいけど……とか、なんとなく興味を持ってるけどシェーダを記述したことがない方を読者に想定しています。
たとえば Unity などのツール、あるいはマインクラフトのようなゲーム、またはモデリングソフトなどでもシェーダを自分で記述することができるような世の中です。きっとシェーダに触れた経験は無駄にはならないでしょう。
すぐに業務で活かすとか、そういう壮大な話はさておいてまずは気軽にシェーダに触れてみましょう。
難しい 3D の数学的知識はとりあえず要りません。もちろん難しいことをやろうとする場合は話が変わってきますが、当連載ではそのあたりの知識は求めません。ちょっとくらい、三角関数とかは出てきますがそんなに難しくないですから安心してください。
対象のシェーダ記述言語は GLSL
「シェーダ」という言葉には非常に広い意味が含まれるので、世の中にはシェーダといってもいろいろなものが存在しています。また、それを記述するための専用の言語にも、いくつか種類があったりして非常に初心者には敷居が高いのかなと思います。
当連載では GLSL というシェーダ専用言語を用います。
GLSL は 昨今話題の WebGL でも採用されているので、特別な開発環境の準備などをしなくても、ブラウザとテキストエディタさえあれば簡単に始められます。
また、当連載では著者自作の GLSL editor というオンラインでシェーダが記述できるエディタを使いますので、もはやブラウザとネット環境さえあればシェーダが書けます。気軽ですね。
それでは早速、前回に引き続き、シェーダプログラミングについて考えていきましょう。
座標系の回転
前回は、非常に短いコードで、簡単に様々な模様が描けるということを実感してもらうことが狙いの回でした。
いくつかコードも掲載しましたので、わかりやすかったのではないでしょうか。
そして、前回の最後にはホログラム的な加工がされたキラキラのシールのような、不思議な模様が浮かび上がるシェーダのお手本コードを紹介しました。
ここを読まれている方のうち、どれくらいの方が実際に実行してみたのかはわかりませんが、一応今回もリンク貼っておきます。
はい、これを実行すると、なんだか美しい模様が綺麗にアニメーションして描画されたと思います。
今回はまずこのコードの解説から始めていきましょう。
それでは以下に、コードを掲載します。
precision mediump float;
uniform float t; // time
uniform vec2 r; // resolution
void main(void){
vec2 p = (gl_FragCoord.xy * 2.0 - r) / min(r.x, r.y);
vec2 q = mod(p, 0.2) - 0.1;
float s = sin(t);
float c = cos(t);
q *= mat2(c, s, -s, c);
float v = 0.1 / abs(q.y) * abs(q.x);
float r = v * abs(sin(t * 6.0) + 1.5);
float g = v * abs(sin(t * 4.5) + 1.5);
float b = v * abs(sin(t * 3.0) + 1.5);
gl_FragColor = vec4(r, g, b, 1.0);
}
前回も解説したように、mod
関数を利用するとまるでタイルを敷き詰めたかのように反復する座標系を作ることができます。お手本コードの中でも main
関数の二行目の部分で、mod
を利用した計算が行われているのがわかると思います。
今回のコードのポイントになるのは、その mod
が登場している部分の下、変数 s
と変数 c
に値を入れているあたりから下の部分に書かれているコードです。
ここでは見てわかる通り、時間の経過からサインとコサインを算出して変数に代入しています。そして、この変数 s
と c
を使って、何やら計算をしている雰囲気になっていますが、ここで初めて目にするものが唐突に出てきていますね。
mat2
と書かれているところで、先ほどのサインとコサインが使われています。これがキラシールシェーダの肝でもある 座標の回転 を行っている部分です。なぜ、このような記述を行うと座標が回転するのでしょう。というよりも、そもそも座標を回転させるというのはどういうことなんでしょうか。
行列を利用した回転
行列といっても、らーめん屋さんの行列じゃありません。れっきとした数学のお話です。
少しくらい 3D や数学を勉強している方やしたことがあるという方であれば、行列という数学上の概念について聞いたことがある人もいるかもしれませんね。
しかし、行列はなんとなく難しいものというイメージが強いと思います。当連載では、行列の意味やその計算方法など、詳細については解説しません。大事なのは 使い方を知ること です。そして実際に使ってみて、便利だなあと感心することからスタートするのが肝要です。
行列を使うと、座標を移動したり、あるいは今回のように回転させたりといったことができるようになります。
今までやってきたように、GLSL editor では一辺が 512px のスクリーン上で、座標をまずは正規化して -1 ~ 1 の範囲に成形した後、その座標の情報を使って様々な模様を描いてきました。光のオーブを描いてみたり、光の輪を描いてみたり、いろいろやりましたね。
しかし、今までやってきた様々な描画はすべて、正規化した座標をそのままダイレクトに計算に使って行ってきました。今回は、正規化した座標をあらかじめ行列によって回転させておくことで、描画結果がくるりと回転したような状態になるようにコードを書いてみましょう。
話を分かりやすくするために、以下のようなコードを使って説明します。
void main(void){
vec2 p = (gl_FragCoord.xy * 2.0 - r) / min(r.x, r.y);
vec2 q = vec2(p.x - 1.0, p.y);
float f = 0.1 / length(q);
gl_FragColor = vec4(vec3(f), 1.0);
}
なんとなく、見たことあるようなコードだとは思いませんか?
これは、光のオーブを描くシェーダをちょっとだけ加工したものです。描画結果は次の画像のような感じになります。
このように、右にオーブが寄った状態で描かれます。
このシェーダに、先ほど登場した座標の回転という概念を持ち込んで、光のオーブが時間の経過で回転していくようにしてみましょう。
GLSL における行列の扱い方
行列による座標の回転を行うためには、文字通り 行列 を利用します。
GLSL において行列を表現する場合には、ベクトル型を表す場合と同じ要領で mat
系の変数を利用します。GLSL ではわざわざ関数を呼び出したりしなくても、変数として宣言するだけで行列を利用することができるんですね。
mat
系の変数は、vec
系と同じように 2 ~ 4 までのサイズがあります。ですが、要素の数が vec
系とはまったく異なっており、mat2
と書いた場合には、2 * 2 で合計 4 つの値をパッキングできます。
同様に mat3
型の場合には 3 * 3 で合計 9 つの値を、mat4
であれば 4 * 4 で実に 16 個もの要素が一度に格納できます。
三次元の座標を扱う 3D プログラミングの世界では、mat3
や mat4
を使うことが多いですが、今回のようなケースでは mat2
で十分に事足りますので、mat2
を使うことにしましょう。
行列は実はベクトルと非常に相性がいい構造をしています。GLSL では、なんとベクトルと行列を直接掛け算することができるようになっています。中身でどんなことが起こっているのかは数学的な難しい話になるので説明しませんが、ただ乗算するだけであとは勝手に GLSL がうまいことやってくれます。
ただし!
行列とベクトルを掛ける場合、末尾につく数字の数が同じになっている必要があります。
mat2 m;
vec2 v = vec2(1.0, 1.0) * m;
上記のように書けばいいわけです。mat2
と vec2
は末尾がいずれも 2 ですね。これだったら大丈夫。もし、vec2
型の変数に mat4
を掛けるような書き方をしてしまうとエラーになってしまいます。
また上記のコードを見るとわかると思いますが、行列とベクトルを掛け合わせると、その結果は ベクトル になります。これもポイントです。例によって、なんでベクトルになるのかはここでは割愛。要は使い方さえわかってればいいんです。詳細を知りたい人は、自分で調べてみましょう。
行列にあらかじめ情報を与えておく
行列とベクトルは直接掛け算することができ、しかもその計算結果はベクトルになる、ということまではわかりました。
しかし、まだ行列を使うとなにがうれしいのかよくわかりませんね。
行列は、先ほども少し書きましたが、座標を移動させたり、あるいは座標を回転させたりするのに利用できます。
もう少し違った言い方をすると、行列にあらかじめ情報を与えておいてベクトルと掛け合わせると、行列に与えてあったパラメータ通りに座標を変換してくれる――ということでもあります。要はすごく大雑把な言い方になってしまいますが、行列はベクトルを変換することができる便利ツールのようなものなんですね。
便利ツールとは言っても、行列に最初に情報を与えてやる段階では一定のルールというものがあります。ただ、このルールさえ覚えてしまえば行列を自在に操ることができるようになるはずです。
行列に回転の情報を与えたい場合には、次のようにして値をセットしてやるのがルールです。
mat2 m = mat2(cos, sin, -sin, cos);
事前に回転させたい角度分のサインとコサインを計算しておいて、上記のコードのようなルールで行列に値をセットしておきます。するとあら不思議、この行列をベクトルに掛けてやるだけで、そのベクトルの座標がサインやコサインの計算の際に指定した角度分だけ回転します。
嘘みたいでしょ。
でも実際にやってみればわかりますね。
先ほどのオーブが右寄りで描かれていたシェーダのコードに、行列の処理を組み込んでみましょう。
void main(void){
vec2 p = (gl_FragCoord.xy * 2.0 - r) / min(r.x, r.y);
float s = sin(t); // サインを求める
float c = cos(t); // コサインを求める
mat2 m = mat2(c, s, -s, c); // 行列に回転用の値をセット
p *= m; // 行列をベクトルに掛け合わせる
vec2 q = vec2(p.x - 1.0, p.y);
float f = 0.1 / length(q);
gl_FragColor = vec4(vec3(f), 1.0);
}
まずは、時間の経過をもとにサインとコサインを求めておきます。さらにそのサインとコサインをもとにして行列にルール通りに値をセットしていますね。
正規化した座標、つまり変数 p
に行列を掛け合わせたあと、先ほどの右寄せオーブのコードをそのまま実行しているのがわかると思います。
これを GLSL editor に貼りつけて実行すると、見事にオーブが回転するようになったはずです。
行列は使い方を知るところから始めるのがいい
行列に限ったことではないですが数学的に難しい概念を初めて利用するときは、理屈云々を理解しようと四苦八苦するくらいなら、いっそのこと模倣でもパクリでもいいので 使い方 を覚えてしまうのがいいと個人的には思います。
中身でどんなことが起こっていて、どうして最終的にそんな結果が得られるのか――それを知ることは確かに大事です。でも、大事だからといってわからないものを無理に理解しようとして、つらくなってしまって挫折したりするくらいなら、とりあえず使ってみればいいんです。
今回の連載記事を読むと、理屈はよくわからないとしても、レンダリング結果を回転させられるようにはなったはずです。この、まずできるようになってしまうというのが大切です。できるようになってしまいさえすれば楽しいですから。それが一番です。
仕上げにキラシールのコードを読み解く
さあ、それでは最後に、前回から登場しているあのキラシールのコードを読み解いてみましょう。
行列の回転について理解した今なら、もうこのキラシールのコードは恐くないでしょう。
precision mediump float;
uniform float t; // time
uniform vec2 r; // resolution
void main(void){
vec2 p = (gl_FragCoord.xy * 2.0 - r) / min(r.x, r.y);
vec2 q = mod(p, 0.2) - 0.1;
float s = sin(t);
float c = cos(t);
q *= mat2(c, s, -s, c); // ← ここで座標を回転させている
float v = 0.1 / abs(q.y) * abs(q.x); // ← 回転済みの座標を使って計算
float r = v * abs(sin(t * 6.0) + 1.5);
float g = v * abs(sin(t * 4.5) + 1.5);
float b = v * abs(sin(t * 3.0) + 1.5);
gl_FragColor = vec4(r, g, b, 1.0);
}
main
関数の冒頭から順番に見ていくと、先ほど説明した行列による座標の回転を行っているのがわかりますね。
行列によって回転された座標を利用して 0.1 を割るような処理をしている部分がありますね。変数 v
に値を格納している箇所です。ここで変数 v
に入った値が、キラシールの光っている部分の光の強さになります。
そして、その変数 v
を利用して、さらに下のほうでは RGB の値をなにやら怪しげな計算で割り出しています。ここはよく見ればなんとなくわかると思いますが、要は光の強さを RGB の各要素ごとにばらけさせるための計算をしています。
このばらけさせる処理をしないと、虹色に綺麗に明滅するキラシールっぽい演出効果が出ないので、ちょっと時間差をつけるためにやってる感じですね。
もしこの RGB を計算している部分が気になるのであれば、実際に GLSL editor にコードを貼りつけていろいろパラメータをいじってみると一番理解が早いと思います。適当になんかいじってるだけで、結構違った結果になって面白いと思いますよ。
さて、キラシールのコードの全容が解明したところで今回はここまで。
次回は応用範囲をさらに広げるべく、シェーダ内で関数を定義して呼び出してみましょう。
お楽しみに!
次回:[連載]やってみれば超簡単! WebGL と GLSL で始める、はじめてのシェーダコーディング(9) - Qiita