openFrameworksでBloomエフェクト

Unityなどでよく見られるBloomエフェクトをopenFrameworksでも使ってみたかったのですが、あまりリソースが出てこなかったので、GLSLを使って簡単なものですが実装してみました。まだ荒い感じですが、エフェクトをかける参考になればと思います。 

<img width="1023" alt="スクリーンショット 2018-09-06 2.15.47.png" src="https://qiita-image-store.s3.amazonaws.com/0/250282/a51ada1e-5c22-b4b3-0b91-9a4617c16ae0.png">



# 実行環境
- macOS high Sierra 10.13.4
- openFrameworks 0.10.0

# addon
- ofxGui(エフェクトのかかり具合を決めるだけなので、無くてもいい)

# 概要
Bloomエフェクトでは、元の描画結果に[ガウシアンぼかし](https://ja.wikipedia.org/wiki/%E3%82%AC%E3%82%A6%E3%82%B7%E3%82%A2%E3%83%B3%E3%81%BC%E3%81%8B%E3%81%97)をかけ、ぼかしがかかった描画結果と元の描画結果を加算合成することによって、物体から光が溢れているような表現を作ることができます。今回はこれをFBO(Frame Buffer Objects)とシェーダ(GLSL)を用いて実装していきます。

#### ガウシアンぼかし
> 画像処理において、ガウシアンぼかし (ガウスぼかし、ガウシアンブラー、ガウシアンフィルター、ガウスフィルター、Gaussian Blur）とは、ガウス関数をもちいて画像をぼかす処理である。デジタルカメラの撮像画像などからノイズを除去したり、アンシャープマスク処理、エッジ抽出の前処理などに応用できる。

# 実装
### 1. 描画結果をFBOに保存
まずは、描画内容をFBOに保存します。FBOに保存すると1フレームごとの描画結果を画像のように扱うことができ、あとでシェーダ側に描画結果をテクスチャとして送ることができます。ここは今回の肝ではないので、描画結果を画像として保存していることがわかれば大丈夫です。

```c++:ofApp.cpp
void ofApp::update(){
    // fbo.begin()とfbo.end()で挟むことによって描画結果をfboに保存し
    // 画像のように使うことができる
    fbo.begin();
    
    ofClear(0, 0, 0, 0); // おまじないのようなもの(やらないとダメ)
    cam.begin();
    
    ofRotateZDeg(30.0);
    
    ofSetColor(200, 200, 200, 255);
    ofDrawBox(100, 0, 300, 100);
    ofSetColor(0, 0, 200, 255);
    ofDrawSphere(-100, 0, 300, 100);
    
    cam.end();
    
    fbo.end(); 
}
```

### 2. 元の描画結果にガウシアンぼかしをかける
ガウシアンぼかしをかけるのは画像のピクセルごとの処理になり、CPUで処理をしていると非常に重くなってしまうので、シェーダ(GPU側)で処理をします。
まずは、C++側の処理からです。

```c++:ofApp.cpp
    // X方向のガウシアンぼかし
    onePassFbo.begin();

    blur.begin();
    // シェーダ側にuniform変数で値を受け渡す。
    blur.setUniformTexture("tex", fbo.getTexture(), 0); // 描画結果
    blur.setUniform1i("horizontal", true); // X方向にぼかしをかけるかどうか
    blur.setUniform1f("strength", strength); // ぼかしの強度
    fbo.draw(0, 0);

    blur.end();

    onePassFbo.end();

    // Y方向のガウシアンぼかし
    twoPassFbo.begin();

    blur.begin();
    // 上に同じ
    blur.setUniformTexture("tex", onePassFbo.getTexture(), 0);
    blur.setUniform1i("horizontal", false);
    blur.setUniform1f("strength", strength); 

    onePassFbo.draw(0, 0);

    blur.end();

    twoPassFbo.end();
```
`onePassFbo`と`twoPassFbo`は、ぼかしをかけた結果を保存するFBOです。ガウシアンぼかしでは、X方向にぼかした画像を、さらにY方向にぼかすという処理を行うため、2段階のぼかしをかけることになります。よってFBOもそれ用に2つ用意しています。
次にシェーダ側の処理です。シェーダ側では実際にガウシアンぼかしをかける処理を行います。計算はフラグメントシェーダで行い、頂点シェーダは頂点情報などを受け流すだけなので、頂点シェーダ部分については割愛します。githubのリンクを貼っておくので、そちらを参照してください。

```glsl:blur.frag
#version 150
precision mediump float;

uniform sampler2DRect tex;
uniform bool horizontal;
uniform float strength;

float weight[5] = float[](0.382928, 0.241732, 0.060598, 0.005977, 0.000229);

in vec2 vTexCoord;

out vec4 vFragColor;

void main() {
    vec3 color = vec3(0.0);
    // texture(tex, vTexCoord)に入ってきたピクセルの値が格納されるので、
    // それをstrengthによってずらし、weight[]をかけることでぼかしをかけている
    color += texture(tex, vTexCoord + vec2(0.0, 0)).rgb * weight[0];
    if(horizontal) {
        for(int i = 1; i < 5; i++) {
            color += texture(tex, vTexCoord + vec2(strength * i, 0.0)).rgb * weight[i];
            color += texture(tex, vTexCoord - vec2(strength * i, 0.0)).rgb * weight[i];
        }
    } else {
        for(int i = 1; i < 5; i++) {
            color += texture(tex, vTexCoord + vec2(0.0, strength * i)).rgb * weight[i];
            color += texture(tex, vTexCoord - vec2(0.0, strength * i)).rgb * weight[i];
        }
    }
    
    vFragColor = vec4(color.rgb, 1.0);
}
```

簡単に説明すると、`texture(tex, vTexCoord)`には入ってきたテクスチャのUV座標内のピクセルの値が入っているので、`vTexCoord`の値を少しずらしてあげると少しだけずれたピクセルにアクセスすることができます。これを利用して少しずらしたピクセルの値を足していってあげることで、ぼかした画像が出来上がります。
テクスチャのUV座標の話については、以下のサイトが非常にわかりやすく載っているので、そちらを参照してみてください。
[テクスチャマッピング · けんごのお屋敷](http://tkengo.github.io/blog/2015/01/27/opengl-es-2-2d-knowledge-5/)

さて、ガウシアンぼかしがかけられました。結果は以下のようになります。

- X方向のぼかし
<img width="1024" alt="スクリーンショット 2018-09-06 2.16.26.png" src="https://qiita-image-store.s3.amazonaws.com/0/250282/5b637de8-dc81-5146-87c8-6e096b80a100.png">

- XとY方向のぼかし
<img width="1022" alt="スクリーンショット 2018-09-06 2.16.58.png" src="https://qiita-image-store.s3.amazonaws.com/0/250282/b122dda5-44f7-5e49-9074-9567fb957fd2.png">


### 3. 元の描画結果とぼかし結果を加算合成
最後です。元の描画結果とぼかし結果を加算合成します。c++側ではこれまでと同じように描画結果のテクスチャをシェーダ側に送っているだけなので、以下ではシェーダのコードを載せたいと思います。

```glsl:bloom.frag
#version 150
precision mediump float;

uniform sampler2DRect tex;
uniform sampler2DRect blur;

in vec2 vTexCoord;

out vec4 vFragColor;

void main() {
    const float gamma = 2.2;
    vec3 p = texture(tex, vTexCoord).rgb;
    vec3 b = texture(blur, vTexCoord).rgb;
    
    p += b;
    
    vec3 result = vec3(1.0) - exp(-p * 1.0);
    result = pow(result, vec3(1.0 / gamma));
    vFragColor = vec4(result, 1.0);
}
```
`tex`が元の描画結果、`blur`がぼかし結果のテクスチャです。ここでは、まず`p`と`b`にピクセルの色を格納し、ダイレクトに加算合成しています。そのあと`result`に`p`の値を調整した値を入れて出力して終了です。`result`ではガンマ補正をかけていますが、そこまで気にしなくてもいい部分だと思います。見栄えが良くなる程度に思っておいていいと思います。(自分もそこまで理解していない...)
最終的な出力結果は最初と同じですが、以下のようになります。
<img width="1023" alt="スクリーンショット 2018-09-06 2.15.47.png" src="https://qiita-image-store.s3.amazonaws.com/0/250282/8742add4-d96f-4750-a6ee-53f96c49ee05.png">


# まとめ
今回のオブジェクトの例だとあまりわかりづらいかもしれませんが、要するにオブジェクトの元のエッジを残しつつ、周りにぼんやりとした部分を作ることで、光が溢れているようなエフェクトを作っています。また、加算合成をしているので、ピクセルの色の値がそこまで大きくない場合などは色が非常に明るい表現へと変化します。
以下に、自分のスケッチに適用してみたらとても変化した例があるので載せます。

- エフェクトをかける前
<img width="1024" alt="スクリーンショット 2018-09-06 2.20.20.png" src="https://qiita-image-store.s3.amazonaws.com/0/250282/b66478db-590a-51f5-e13d-08081962b592.png">

- エフェクトをかけた後
<img width="1024" alt="スクリーンショット 2018-09-06 2.22.00.png" src="https://qiita-image-store.s3.amazonaws.com/0/250282/0fb6f1d9-48f3-0c52-6ea8-c504f1e3950a.png">

非常に明るくなっているのがわかると思います。このように光が溢れているような表現を使いたいときはとても使えるエフェクトなのでいいと思います。こんなことしなくても、こんな感じの表現ができるaddonはもう存在しているようですが。。。（笑）

このエフェクトも対象オブジェクトの大きさなどによって、強度などを変えたほうが良さそうなので、その辺は各自で試してみてください。あと、自分自身理解していない部分や拙い部分があるので、この辺違うよ！とか、こっちの方がいいよ！的なことを言って頂けるとありがたいです！！編集リクエストバンバン送ってください！！！

oFがTDやHoudiniなどに代替されていく中で、まだまだoFを書き続けていきたいと思うものです（笑)

# 参考
- [ソースコード(github)](https://github.com/sketchbooks99/Bloom-Effect)
- [ガンマ補正](http://w3.kcua.ac.jp/~fujiwara/infosci/gamma.html)
- [Bloom - Learn OpenGL](https://learnopengl.com/Advanced-Lighting/Bloom)