#はじめに
- 導入編(ここ)
- 実践編
こんにちは、避雷です。最近は主にUnityでばかりシェーダーを書いて遊んでいますが、最初にShaderを投稿したのは実はShadertoyです。今回はShadertoyを主題にした記事を書いてみようと思います。
#今回の話題
今回はマルチパスシェーダーを使うことでレイマーチングの新しい表現を模索してみようと思います。
Shadertoyにはデフォルトでマルチパスシェーディングする環境が揃っています。
コードを書く場所をBufferA,B,C,Dのいずれかにすることで描画結果はそのまま出力されず、一旦バッファに収納されます。
これをメイン描画の部分で取り出してあげることでBufferAでの描画結果をあたかも普通のテクスチャであるかのように扱うことができるようになります。
#何が嬉しいの
GLSLの特徴として各ピクセルの描画は同時に行われる(順序がない)(かなり大雑把な表現をしています)と言うものがあり、「他のピクセルを参照する」という処理をすることは一般には不可能です(ddx,ddyと言った特殊な関数は有ります)。
マルチパスシェーダーでは一旦レイマーチングで生成した画像をバッファに保存して置けるので、次のパスでの処理はすべてのピクセル情報が出揃った状態で始めることができます。
このため、例えば別のピクセルを参照したり、周囲のピクセルとの平均値を取ったり、ピクセルの偏微分の値を正確に取ったりすることができます。
一般に画像処理と呼ばれるものは周囲のピクセルの情報を利用するものが多いので、シングルパスでは出来なかったような様々な表現が可能になります。
他には、バッファにColorを用いてデータを書き込むことによって、通常1Fで揮発してしまうglslのデータを保持しておくことが可能になります。状態を持てることによってglslは極端に言ってしまえばなんでも作ることができるようになった、と言えます。例えばiq神のブロック崩しだったりがあるようにシェーダーだけでゲームを作ることもできます。
https://www.shadertoy.com/view/MddGzf
こっちの用途での実装は今回は取り扱いません(僕が把握しきれてないので)。いつか書きたいです。
蛇足ですが、レンダリングを複数のパスに分けることでコードの可読性を上げる、なんて使用法もあります(あまり現実的ではありませんが。)
#実装してみよう
##もとになるレイマーチングを作る
まずは元となるレイマーチングを用意します(3分クッキング)。今回はこちらのシェーダーを使ってポストプロセスの実装をしてみようと思います。
https://www.shadertoy.com/view/WlSXWc
##下準備をする
このままでは普通のシングルパスシェーダなので、一旦これらをバッファに移していきます。
左上の+ボタンを押してBufferAを選択、さっきのコードを丸まんま切り取り⇒貼り付けすればBufferA側に描画されるようになります。
BufferAに収納されたテクスチャを読み取るにはiChannelのいずれかにBufferAを登録、その後普通のテクスチャと同じようにTexture
関数を呼び出せばバッファを読み込むことができます。
##なんか処理を書き足してみよう
今回は簡単にモザイク処理を実装していこうと思います。
まずは普通にテクスチャを読み込んで表示してみます。
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 uv = fragCoord/iResolution.xy;
vec3 col = texture(iChannel0,uv).xyz;
fragColor = vec4(col,0.);
}
出力はこんな感じ。BufferAが読み込めていることがわかります。
次に一定の区間ごとに上下左右の平均値をとる処理を加えてみます。
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 uv = fragCoord/iResolution.xy;
vec2 grid = floor(uv * 50.) / 50.;
vec2 gridN = grid + vec2(0.,0.005);
vec2 gridS = grid + vec2(0.,-0.005);
vec2 gridE = grid + vec2(0.005,0.);
vec2 gridW = grid + vec2(-0.005,0.);
vec3 col = texture(iChannel0,grid).xyz;
col += texture(iChannel0,gridN).xyz;
col += texture(iChannel0,gridS).xyz;
col += texture(iChannel0,gridE).xyz;
col += texture(iChannel0,gridW).xyz;
col /= 5.;
fragColor = vec4(col,0.);
}
出力はこんな感じ。
サンプルもおいておきます。
https://www.shadertoy.com/view/3sySzh
ちゃんとモザイクが掛かっていることがわかります。この程度ならfloor関数をそのまま使っても大して変わりませんが、複雑な処理となるとマルチパスの方が簡単かつ綺麗に書けることが多くなります。
#おわりに
今回はマルチパスシェーダーの導入について書いてみました。明日は実際に様々なエフェクトをかけてみる実践編としたいと思います。