#はじめに
- 導入編
- 実践編(ここ)
こんにちは、アドベントカレンダー5日目の避雷です。今回は昨日に引き続きマルチパスによるポストエフェクトの実装をやっていきたいと思います。今回は具体的な実装をしていこうかと思います。
#実装
今回はいくつか限られた事例のみを紹介するに留めますが、マルチパスの2パス目を使うことで一枚のテクスチャに対して出来るような処理をレイマーチングの描画結果に対して行うことが出来ます。
##色収差
https://ja.wikipedia.org/wiki/%E8%89%B2%E5%8F%8E%E5%B7%AE
ここ数年スタイリッシュな画の代名詞とも言える色収差をマルチパスで実装してみましょう。
wikipediaを見ると光の色ごとの屈折率の違いから色にブレが発生するみたいで、大体外側が青、内側が赤になればよいみたいです。もしシングルパスなら色ごとに向きを変更して複数回レイを飛ばす羽目になるところでしたが、ありがたいことにマルチパスなら別座標のピクセルの色を参照出来るので、R,G,B毎に別のピクセルから色を取ってくるようにしましょう。
サンプル↓
https://www.shadertoy.com/view/wsVXzW
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 uv = fragCoord/iResolution.xy;
vec2 delta = (uv - 0.5) * 0.05;
fragColor.x = texture(iChannel0,uv + delta).x;
fragColor.y = texture(iChannel0,uv).y;
fragColor.z = texture(iChannel0,uv - delta).z;
}
##エッジ検出
画像処理の分野ではお馴染みのエッジ検出です。近傍との色の勾配を取って、それが一定以上なら表示するようにします。勾配の取得自体はシングルパスでもddx,ddyを使えば可能ですが性質上どうしても2x2ピクセルがひとかたまりになってしまい、解像度が半分になるので、ビジュアルを追究するならマルチパスの実装の方が綺麗に仕上がります。
サンプル↓
https://www.shadertoy.com/view/tsKSRD
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 uv = fragCoord/iResolution.xy;
float delta = 0.002;
vec3 baseColorE = texture(iChannel0,uv + vec2(delta,0.)).xyz;
vec3 baseColorW = texture(iChannel0,uv - vec2(delta,0.)).xyz;
vec3 baseColorN = texture(iChannel0,uv + vec2(0.,delta)).xyz;
vec3 baseColorS = texture(iChannel0,uv - vec2(0.,delta)).xyz;
vec3 grayVec = vec3(0.2126,0.7152,0.0722);
float graydx = dot(baseColorE,grayVec) - dot(baseColorW,grayVec);
float graydy = dot(baseColorN,grayVec) - dot(baseColorS,grayVec);
float grayScale = length(vec2(graydx,graydy));
fragColor.xyz = vec3(grayScale);
fragColor.w = 1.0;
//fragColor.xyz = texture(iChannel0,uv).xyz;
}
今回は一旦グレイスケール変換してエッジを取っていますが、RGBそれぞれでエッジを取ると元の色を引き継いだ線が描画されます。これはこれでおしゃれ。
vec3 dx = baseColorE - baseColorW;
vec3 dy = baseColorN - baseColorS;
fragColor.x = length(vec2(dx.x,dy.x));
fragColor.y = length(vec2(dx.y,dy.y));
fragColor.z = length(vec2(dx.z,dy.z));
##複数表示
マルチパスなら複数個のレイマーチングをスライドショーのように描画することもできます。
例えばこの4つのレイマーチングをバッファに貯めて最後でビジュアルを構築します。
↓サンプル、重いので注意!
https://www.shadertoy.com/view/wsKXRD
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
// Normalized pixel coordinates (from 0 to 1)
vec2 uv = fragCoord/iResolution.xy;
vec3 colA = texture(iChannel0,uv).xyz;
vec3 colB = texture(iChannel1,uv).xyz;
vec3 colC = texture(iChannel2,uv).xyz;
vec3 colD = texture(iChannel3,uv).xyz;
float t = (floor(iTime * 0.75) + smoothstep(0.1,0.9,fract(iTime * 0.75))) * 3.14 / 2.;
vec2 st = mat2(cos(t),-sin(t),sin(t),cos(t)) * (uv - 0.5);
vec3 col = st.x - st.y < 0. ?
(st.x + st.y < 0.? colA : colB):
(st.x + st.y < 0.? colC : colD);
col = ((min(abs(st.x + st.y),abs(st.x - st.y))) < 0.01) ? vec3(0.0) : col;
col = ((max(abs(st.x + st.y),abs(st.x - st.y))) < 0.2) ? vec3(0.0) : col;
col = ((max(abs(st.x + st.y),abs(st.x - st.y))) < 0.19) ? vec3(0.9) : col;
fragColor.xyz = col;
}
もちろん最終的に描画しない部分の処理もバッファする際に走ってしまっているので負荷的には厳しいです。サンプルシーンを作る際も4つもバッファに書き込んだのが響いて結局PCが爆音を鳴らすことになりました。
可読性、実装の楽さとのトレードオフって感じでしょうか。最終的に使う部分が決定しているならバッファの段階でuv座標を基準に「ここはレイを飛ばさずダミーのデータを入れよう」みたいな分岐させて軽量化を図る
ことはできそうです。
#まとめ
稚拙ですがマルチパスで出来る表現の例を紹介してみました。マルチパスシェーダーによってより一層表現の幅を広げたレイマーチングをやっていきましょう。