はじめに
ここ最近、メガデモ界隈を中心にGLSL(WebGL)で1枚の四角ポリゴン(以下、板ポリ)に絵を描くのが流行ってます。GLSLSandboxやShaderToyなどでスーパーデモシーナーな方々の作品が見れますが、すごいですね。。よくこんなの書けるなと思うものがたくさんあります。
GLSLSandboxや@h013さん作のC++版GLSLSandbox"LiveCorder"などを使うと、GLSLでのライブコーディングが出来きます。うまく行けばすごくかっこいいパフォーマンスが出来るのですが、ライブコーディングはとても難しいです。。もちろん私はできません。なので、GLSLに慣れるまではQuartzComposerを使い、VJ用ソフトでネタを切り替えるのが無難かなと思っています。
前置きが長くなりましたが、今回はQuartzComposer+GLSLで板ポリに絵を描く方法をまとめました。
※ QuartzComposerの今後は怪しいですが、まだ動いてますので。。
GLSLについては説明を割愛しているところがありますので、ご了承下さい。 もし興味を持って頂けたなら、以下のようなサイトを参考にして勉強してもらえるとすごくうれしいです。
パッチの用意
主に使用するのは、GLSL GridとGLSL Shaderパッチです。
GLSL Grid
描画される板ポリです。実際のところ、GLSL GridではなくSpriteパッチなどでもOKです。
画面全体に板ポリを表示するため、GLSL GridのWidthとHeightを2に設定します。
GLSL Shader
GLSLを書くためのパッチです。Macroパッチになっており、GLSL Shader内に置かれたオブジェクトに対してシェーダを適用します。つまり、GLSL Shader内のオブジェクトは、レンダリングパイプラインが固定ではなくプログラマブルシェーダになります(陰影計算に関わるところを全部自分でプログラムする必要がある、、という事です)。この辺りは床井先生のページに分かりやすい解説がありますので、興味のある方は是非見て下さい。
GLSL Gridパッチ(GLSL Shaderパッチの中にあります)
この2つのパッチを準備します。
動作確認
では簡単に動作確認してみましょう。GLSL ShaderパッチのInspectorを開き、Settingsを選択します。
Settingsには、"Vertex Shader"と"Fragment Shader"を書くところがあります。
GLSL ShaderパッチのSettings ![GLSLShaderInspector01.png](https://qiita-image-store.s3.amazonaws.com/0/42772/3df605a6-4a25-adb1-dc80-c0760c494466.png)
* * * **Vertex Shaderとは** Vertex Shaderの役割は、頂点の座標変換と頂点色の決定です。 実行単位は頂点毎になるので、Vertex Shaderのソースは描画するオブジェクトの頂点数分実行されます。
Fragment Shaderとは
Fragment Shaderの役割は、ピクセルの色の決定です。なのでPixel Shaderとも呼ばれます。
実行単位はピクセル毎になるので、解像度数分実行されます。
まずはVertex Shaderからですが、今回は頂点に関する処理は必要ないので、モデルビュー変換を適用した頂点座標を計算する処理だけ書いておきます。 ※ gl_ModelViewProjectionMatrix、gl_VertexはGLSLの組み込み変数です。
void main(){
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
gl_PositionはVertex Shaderの出力変数(vec4型)で、座標変換後の頂点座標が保存されます。
※ 厳密には、gl_Positionにはクリッピング座標系(モデルビュー変換+透視変換の結果をクリッピング空間に投影したもの)の値を出力します。
参考
次にFragmen Shaderです。動作確認なので、シンプルに単一色で塗りつぶすコードを書いてみます。
void main(){
gl_FragColor = vec4(0.0, 0.0, 1.0, 1.0);
}
Fragment Shaderの処理結果は、gl_FragColor(vec4型)に保存します。保存する値は、シェーダが処理しているピクセルの最終的な色になります。
上記のコードをGLSL Shaderに書いてやることで、青色で塗りつぶされたポリゴンが表示されます。
無事に表示されたら、GLSL Shaderは動いているということになりますね。
(0.0, 0.0, 1.0, 1.0)のカラーで塗りつぶされたいたポリ ![GLSLShaderTest.png](https://qiita-image-store.s3.amazonaws.com/0/42772/821edaca-dd37-4fb7-1786-d52433dade97.png)
板ポリに絵を描いてみよう
(説明不足だとは思いますが、、)ようやくここまできました。実際に絵を描いてみましょう。
タイトルにもある通り、描画対象となるオブジェクトが板ポリ1枚なので、Vertex Shaderは触りません。
すべてFragment Shaderで実装します。
###新しい座標系を作る###
まず準備として、画面の左下が(-w, -1)、右上が(w,1)となるような座標系を作ります。
この座標系を基準にすることでウィンドウの中心座標が(0.0, 0.0)となり、プログラムが組みやすくなります。
以下のプログラムは、新しく作成した座標系のx、yをRとGの色に割り当てたものです。
uniform vec2 resolution;
void main(){
vec2 uv = 2.0 * (gl_FragCoord.xy / resolution) - 1.0;
gl_FragColor = vec4(uv, 0.0, 1.0);
}
新しい座標系のxとyをRとGに入れた時の板ポリ ![GLSLShaderCoord.png](https://qiita-image-store.s3.amazonaws.com/0/42772/64fe4fee-8945-e555-ac76-18d1db72c107.png)
gl_FragCoordはウィンドウ上のフラグメント位置(処理の対象となっているピクセルの位置)です。
これを解像度で割ると0.0 ~ 1.0の値に正規化された座標になります。なので、 2.0 * (gl_FragCoord.xy / resolution) - 1.0
とすることで左下が(-w, -1)、右上が(w,1)となる座標を作ることができます。
さて、上記の計算で使用した解像度(resolution変数)ですが、これは組み込み変数ではありません。なので、GLSL Shaderパッチの外から値を渡してもらう必要があります。
このように、シェーダの外から入力を受け取る場合、uniform変数という形で受け取る用の変数を用意します。それが1行目のuniform vec2 resolution;
です。
uniform変数を宣言し、実際に使用するとGLSL Shaderパッチに入力ポートが増えるので、そこに値を代入することができます。
解像度を入力データとして渡した時のGLSL Shaderパッチ ![GLSLShaderPatch02.png](https://qiita-image-store.s3.amazonaws.com/0/42772/9d3da1ff-e068-7c87-4f07-9fc66153be0c.png)
###横線を描いてみる###
では、画面中央に白い横線を描いてみます。
中央に横線を書くには、y座標が0.0付近のときに1.0以上の値が帰ってくる計算式を作ればよいです。
ちょっとかっこ悪いですが、以下の式もそうなります。
※ (uv.y + 0.001)
としているのはゼロ割を回避するためですが、これが無くても動きます。
uniform vec2 resolution;
void main(){
vec2 uv = 2.0 * (gl_FragCoord.xy / resolution) - 1.0;
float col = abs(1.0 / (uv.y + 0.001) * 0.01); // uv.yが0.0付近のとき、値が1.0以上になる。
gl_FragColor = vec4(col, col, col, 1.0);
}
GLSLで画面中央に横線が描かれた板ポリ ![Result01.png](https://qiita-image-store.s3.amazonaws.com/0/42772/7c098c7c-cd52-77cd-fd35-20f0d897be24.png)
###横線を動かしてみる###
次は横線を上下に動かして見ましょう。
そのためには、新しくtimeというuniform変数を宣言します。
uniform vec2 resolution;
uniform float time;
void main(){
vec2 uv = 2.0 * (gl_FragCoord.xy / resolution) - 1.0;
float col = abs(1.0 / (uv.y + sin(time) + 0.001) * 0.01); // sin(time)によって横線が上下に移動する
gl_FragColor = vec4(col, col, col, 1.0);
}
解像度と時間を入力データとして渡した時のGLSL Shaderパッチ ![GLSLShaderPatch03.png](https://qiita-image-store.s3.amazonaws.com/0/42772/e87dc02e-1565-8684-b7f7-dfa2d173c787.png)
###適当な関数を加えてみる###
uniform vec2 resolution;
uniform float time;
void main(){
vec2 uv = 2.0 * (gl_FragCoord.xy / resolution) - 1.0;
float col = abs(1.0 / (uv.y * tan(time * uv.y) + 0.001) * 0.01); // ちょっと変更
gl_FragColor = vec4(col, col, col, 1.0);
}
横線の描き方を少し変えた板ポリ ![Result03.png](https://qiita-image-store.s3.amazonaws.com/0/42772/2c59b9b3-b12a-7ec9-0596-417e18aa55d4.png)
このように、いろいろと関数を組み合わせていくことで模様が変わっていきます。面白いですね。
今回のサンプルはここからダウンロード出来ますので、是非遊んでみて下さい。
終わりに
QuartzComposerにはオーディオやビデオ用のパッチが用意されているので、インタラクティブな入力データを簡単に扱うことができます。これはQuartzComposerならではのメリットですね。QuartzComposerを使うなら、これらのデータを扱う作品に挑戦してみましょう。
その他
GLSLSandboxやShaderToyの中にはBackBuffer(1フレーム前の画面)を入力として使用する作品があるのですが、これはQuartzComposerだとうまく動きません(Mountain Lion/Snow Leopard用のQuartzComposerで確認済み)。どうもBufferの初期化がうまくいっていないようなのですが、今のところこの現象を解決するには至っていません。