JavaScript Advent Calendar/WebGLコース10日目・画像フィルターを作る
JavaScript Advent Calendar 2011 WebGL駅伝10日目、独走3日目になります。今からでも参加したい方がいましたらぜひ教えてください。
今日はこのオルセー美術館をTiltShiftさせてみます。実はこれがやりたくて一昨日から書いてきたのでした。
サンプルです。
シェーダー部分以外は9割ぐらい昨日と同じです。
シェーダー
昨日は頂点シェーダーでテクスチャーの座標をvaryingに入れてましたが、今日は別の方法でやるので、頂点シェーダーはたったこれだけです。
attribute vec2 aPosition; void main() { gl_Position = vec4(aPosition, 0.0, 1.0); }
フラグメントシェーダーでは、ガウスフィルター(Gaussian Blur)のための関数を定義しています。フィルターのパラメーターは適当にググッて拾ってきました。そこはまあ重要ではありません。
precision mediump float; uniform sampler2D uSampler; uniform vec2 uTextureSize; vec4 GaussianBlur(in sampler2D tex, in vec2 c, in vec2 size) { // http://homepages.inf.ed.ac.uk/rbf/HIPR2/gsmooth.htm return ( 41.0 * texture2D(tex, vec2(c / size)) + 26.0 * ( texture2D(tex, (c + vec2( 1.0, 0.0)) / size) + texture2D(tex, (c + vec2(-1.0, 0.0)) / size) + texture2D(tex, (c + vec2( 0.0, 1.0)) / size) + texture2D(tex, (c + vec2( 0.0,-1.0)) / size) ) + 16.0 * ( texture2D(tex, (c + vec2( 1.0, 1.0)) / size) + texture2D(tex, (c + vec2(-1.0, 1.0)) / size) + texture2D(tex, (c + vec2( 1.0,-1.0)) / size) + texture2D(tex, (c + vec2(-1.0,-1.0)) / size) ) + 7.0 * ( texture2D(tex, (c + vec2( 2.0, 0.0)) / size) + texture2D(tex, (c + vec2(-2.0, 0.0)) / size) + texture2D(tex, (c + vec2( 0.0, 2.0)) / size) + texture2D(tex, (c + vec2( 0.0,-2.0)) / size) ) + 4.0 * ( texture2D(tex, (c + vec2( 1.0, 2.0)) / size) + texture2D(tex, (c + vec2(-1.0, 2.0)) / size) + texture2D(tex, (c + vec2( 1.0,-2.0)) / size) + texture2D(tex, (c + vec2(-1.0,-2.0)) / size) + texture2D(tex, (c + vec2( 2.0, 1.0)) / size) + texture2D(tex, (c + vec2(-2.0, 1.0)) / size) + texture2D(tex, (c + vec2( 2.0,-1.0)) / size) + texture2D(tex, (c + vec2(-2.0,-1.0)) / size) ) + 1.0 * ( texture2D(tex, (c + vec2( 2.0, 2.0)) / size) + texture2D(tex, (c + vec2(-2.0, 2.0)) / size) + texture2D(tex, (c + vec2( 2.0,-2.0)) / size) + texture2D(tex, (c + vec2(-2.0,-2.0)) / size) )) / 273.0; } void main() { float y = gl_FragCoord.y / uTextureSize.y; if (y < 0.3 || y > 0.4) { gl_FragColor = GaussianBlur(uSampler, gl_FragCoord.xy, uTextureSize); } else { gl_FragColor = texture2D(uSampler, gl_FragCoord.xy / uTextureSize); } }
一番のポイントは、gl_FragCoord
という組み込み変数を使っていることです。この変数は、ビューポートの左下からの座標がピクセル単位で入っています。つまり、ビューポートをテクスチャー画像のサイズと同じにした場合は、xは0から画像の幅まで、yは0から画像の高さまでの値をとります。
uTextureSize
にその画像のサイズを入れてあります。(この場合300x240)
var uTextureSizeLocation = gl.getUniformLocation(program, "uTextureSize"); … gl.uniform2f(uTextureSizeLocation, img.naturalWidth, img.naturalHeight);
このように一枚画像を全面に表示する場合は、gl_FragCoord.xy / uTextureSize
が昨日やった(0,0)から(1,1)のテクスチャー座標と同じものになります。(gl_FragCoord.xy
ってこれ以外の用途はあるんだろうか?)
まとめ
実は、WebGLによる画像フィルターばかりを集めたデモが公開されていて、TiltShiftもちゃっかりあります。
WebGLで画像処理はCanvas2DContextより簡単です。魚眼レンズエフェクトとかも簡単にできるはずです。
↓のように端をclamp(GLSLのtexture2D関数に0より小さい座標を与えた場合は0、1より大きな座標を与えた場合は1と考える)にしておけばエッジのことも考え無くていいですし。
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
しかし、FirefoxのWebGLがバグってるため、今のところはやはりテクスチャーのサイズは2の累乗にしたほうがいいです。今日のサンプルもFirefoxとChromeでは縮小のされ方が違います。