はじめに
最近 OpenGL ES 2.0 触っていてシェーダに興味を持ったのでやってみました。
解説
解説というのもおこがましいですが…、要は gl_FragColor に与える vec4 を現在のピクセル位置 gl_FragCoord や uniform 変数として与えられている resolution や time を利用しながらゴリゴリ作るわけです。
しかしながら、Sandbox の gallery から色々なサンプルコードを見てみてもパッと見何やってるのか全く分からなかったので、取り敢えず自分なりに整理して以下のように考えてみました。
光る●を出す
#ifdef GL_ES precision mediump float; #endif uniform vec2 resolution; uniform float time; void main() { float x = resolution.x / 2.0; float y = resolution.y / 2.0; float size = 5.0; vec2 pos = vec2(x, y); float dist = length(gl_FragCoord.xy - pos); float color = size / dist; gl_FragColor = vec4(vec3(color), 1.0); }
●を描画する位置を決めてそこからの距離の逆数で光を減衰させることで●を表現します。逆数だと広がりが大きすぎると思う場合は適当に累乗しておけば目的の大きさに収束していきます。
複数個の●を描画する
#ifdef GL_ES precision mediump float; #endif uniform vec2 resolution; uniform float time; const int num_x = 10; const int num_y = 10; float w = resolution.x; float h = resolution.y; void main() { float size = 1.0 - 0.5 * sin(time * 10.0); float color = 0.0; for (int i = 0; i < num_x; ++i) { for (int j = 0; j < num_y; ++j) { float x = w/2.0 + (float(i-num_x/2)) * w/float(num_x); float y = h/2.0 + (float(j-num_y/2)) * h/float(num_y); vec2 pos = vec2(x, y); float dist = length(gl_FragCoord.xy - pos); color += pow(size/dist, 2.0); } } gl_FragColor = vec4(vec3(color), 1.0); }
for 文の外側で gl_FlagColor に与える color を定義して、内側で足し合わせます。付近に光らせたい (x, y) がない場合はほぼ初期値の 0.0 となるわけですね。
複数個の●をぐるぐる動かしてみる
ここまで来れば1個1個の点を動かすのは容易なので適当な式を用意して動かしてみます。
#ifdef GL_ES precision mediump float; #endif uniform vec2 resolution; uniform float time; const int num_x = 10; const int num_y = 10; float w = resolution.x; float h = resolution.y; void main() { float t = time; float color = 0.0; for (int i = 0; i < num_x; ++i) { for (int j = 0; j < num_y; ++j) { float x = w/2.0 * (1.0 + cos(1.2 * t + float(3*i+4*j))); float y = h/2.0 * (1.0 + sin(1.7 * t + float(3*i+4*j))); float size = 2.0 - 1.0 * sin(t); vec2 pos = vec2(x, y); float dist = length(gl_FragCoord.xy - pos); color += pow(size/dist, 2.0); } } gl_FragColor = vec4(vec3(color), 1.0); }
適当に肉付け
後は本能の赴くままに適当にモリモリします。
#ifdef GL_ES precision mediump float; #endif uniform vec2 resolution; uniform float time; uniform sampler2D backbuffer; const int num_x = 5; const int num_y = 5; float w = resolution.x; float h = resolution.y; vec4 draw_ball(int i, int j) { float t = time; float x = w/2.0 * (1.0 + cos(1.5 * t + float(3*i+4*j))); float y = h/2.0 * (1.0 + sin(2.3 * t + float(3*i+4*j))); float size = 3.0 - 2.0 * sin(t); vec2 pos = vec2(x, y); float dist = length(gl_FragCoord.xy - pos); float intensity = pow(size/dist, 2.0); vec4 color = vec4(0.0); color.r = 0.5 + cos(t*float(i)); color.g = 0.5 + sin(t*float(j)); color.b = 0.5 + sin(float(j)); return color*intensity; } void main() { vec4 color = vec4(0.0); for (int i = 0; i < num_x; ++i) { for (int j = 0; j < num_y; ++j) { color += draw_ball(i, j); } } vec2 texPos = vec2(gl_FragCoord.xy/resolution); vec4 shadow = texture2D(backbuffer, texPos)*0.7; gl_FragColor = color + shadow; }
これで冒頭のものが完成です。
おまけ
Vim で編集する際は glsl.vim を入れるとシンタックスハイライトと補完をしてくれるので便利です。
" 通常版 NeoBundle 'glsl.vim' autocmd BufNewFile,BufRead *.frag,*.vert,*.fp,*.vp,*.glsl \ set filetype=glsl " 遅延ロード版 NeoBundleLazy 'glsl.vim' augroup NeoBundleLazyForShader autocmd! autocmd BufNewFile,BufRead *.frag,*.vert,*.fp,*.vp,*.glsl \ set filetype=glsl autocmd FileType glsl NeoBundleSource \ glsl.vim augroup END
おわりに
シェーダの世界は奥が深そうですね。。