その後のその後

iOSエンジニア 堤 修一のブログ github.com/shu223

「FILTERS」で学ぶ GLSL

GLSL を書いてオレオレフィルターをつくれる」というコンセプトのカメラアプリがリリースされました。


Filters | 面白法人カヤック



シェーダを書いて動的に適用する、というアイデア自体は昔からあるものですが、


wonderfl や jsdo.it をつくったカヤック製アプリなので、

  • フォークできる
  • リアルタイムプレビューしてくれるエディタでコード(GLSL)を書ける
  • シェアできる

という非常に魅力的な点があります。あと前述の従来品はサンプルだったりするので、もちろんカメラアプリとしてのクオリティも全然違います。



既に魅力的なフィルタがいろいろとアップされています。



GLSL には興味があったものの、なかなか勉強する機会がなかったので、他の方がつくったフィルタをフォークしてコード(GLSL)を読みつつコメントを入れていくということをやってみました。


タイトルの後ろに (commented) と入れているので、よろしければ参考にしてみてください。*1


以下、いくつか紹介していきます。

グレースケールにする

入力画像をグレースケールに変換するフィルタ。



これはフィルタを新規作成すると生成されるデフォルトテンプレートの最後の行だけいじってコメントを入れたもの。GLSLを書いてみる最初の1歩として、この3行を理解するといいかもしれません。

grayscale (commented)
void main()
{
    // 座標を取得
    vec2 uv  = iScreen;
    
    // カメラからの入力テクスチャから、該当座標の色を取得する
    vec4 color = texture2D(iCamera, uv);
  
    // RGBのGとBをRで置き換えた色を生成して適用
    gl_FragColor = vec4(color.r,color.r,color.r, 1.0);
}

歪める&青っぽくする

入力画像を歪め、全体的に青っぽくするフィルタ。



iCamera(カメラからの入力テクスチャ) + texture2D関数で、入力画像の任意の座標の色をとってこれることを利用して、座標をx,yそれぞれ2乗した先の画素値をとってきて歪みを実現しています。

water (commented)
void main()
{
    // 座標取得
    vec2 scr = iScreen;
  
    // 座標に応じて近隣の色を取得する
    // (カメラの入力テクスチャから、注目座標のx,yを2乗した座標の色をとってくる
    vec4 color  = texture2D(
      iCamera,
      vec2(
        pow(scr.x, 2.0),
        pow(scr.y, 2.0)
      )
    );
  
    // 青っぽくして適用
    gl_FragColor = vec4(
      vec3(
        color.r *   0.0 / 255.0,
        color.g * 153.0 / 255.0,
        color.b * 204.0 / 255.0
      ),
      1.0
    );
}

画像の一部にモザイクをかける

ピンポイントにモザイクをかけるフィルタ。ピンチイン・アウトにも対応。



モザイク処理は、座標値を floor 関数で粗くして周辺画素と同じ色を使用することで実現されています。


また、出力位置が円の中にあるかどうかでモザイクをかける・かけないを判定して、円形のモザイク処理が実現されています。


ピンチイン・アウトに対応しているので座標処理がちょっと複雑な感じがする場合は、いったん iSize をなくして考えてみるとわかりやすいかもしれません。

Pinpoint Mosaic
// 白色を定数として定義
const vec3 white = vec3(1.0, 1.0, 1.0);

// 座標positionが、半径size・中心座標offsetにある円の内側にあるかを判定する
bool inCircle(vec2 position, vec2 offset, float size) {
    float len = length(position - offset);
    if (len < size) {
        return true;
    }
    return false;
}

void main( void ) {
    // 座標取得
    vec2 uv = iScreen;
    // 座標を粗くする(15 x ピンチサイズ倍して切り捨て)
    uv = floor(uv * iSize * 15.0) / 15.0 / iSize;
    // 粗くした座標の色を取得
    vec4 color = texture2D(iCamera, uv);
    
    // 白色(whiteは定数として定義済み)を生成
    vec3 destColor = white;
    // 出力位置を計算
    vec2 position = (gl_FragCoord.xy * 2.0 - iResolution) / min(iResolution.x, iResolution.y);
    
    // 出力位置が円の内側にあるか?
    if (inCircle(position, iPosition, 1.0 + (1.0 - iSize))) {
        // モザイク化する(粗くした座標の色を使用)
        destColor = color.rgb;
    }
    else {
      
        // モザイク化しない(元々の色を使用)
        destColor = texture2D(iCamera, iScreen).rgb;
    }
    
    // 決定した色を出力出力
    gl_FragColor = vec4(destColor, 1.0);
}

所感

FILTERS、GLSLの勉強におすすめです!


(GLSL関連記事)

(余談)iOS 8 とシェーディング言語

シェーダが書けると iOS の Core Image のフィルタ(CIFilter)も自作できるようになります。(iOS 8 から)


拙作『iOS8-Sampler』にもシェーダを書いて作成したカスタムフィルタのサンプルがいくつか入っています。

(2014.12.12追記)他のアプリからFILTERSのフィルタを使う



ってツイートしたら、中の人からリプライがあり、なんと既に Photo Editing Extension 対応してるとのこと。すごい!


標準の「写真」アプリから試してみました。



現状では、上位30個ぐらいのフィルタが選べるようです。



*1:なお、コードをシンプルにするため、処理に使われていない関数や定数は削除しました。