半透明の要素を描いたFBOを描画する際のハマりポイント


TL;DR

不透明ピクセルに半透明ピクセルをアルファブレンディングした結果は半透明だから気をつけような!

現象

  1. FBOを背景色(不透明)でクリアする
  2. FBOに半透明の要素を描画する
  3. FBOをウィンドウに描画する
  4. ウィンドウ背景色が透過して見える

例:黒でクリアしたFBOに白い半透明の矩形を描き、赤背景のウィンドウに描画した結果

何が問題なのか・どうなってほしいのか

FBOの背景色が不透明なのに、ウィンドウ背景色が見えているのが問題
ウィンドウ背景が透けてほしくない

例:こうなってほしい

原因

不透明要素の上に半透明要素をアルファブレンディングした結果が半透明であること。
つまりFBO内の半透明要素を描画した領域のアルファ値は1より小さくなること。

アルファブレンディングの計算式(glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)の場合)

out = src\cdot src_a+dst\cdot (1-src_a)\\
(src: 描画色, dst: 背景色, out: 結果)

アルファ値のみに着目すると

out_a = src_a\cdot src_a+dst_a\cdot (1-src_a)\\

つまり$dst_a$が1(背景が不透明)の場合、

out_a = src_a^2-src_a+1 \qquad (dst_a=1)\\
\therefore \left\{
\begin{array}{ll}
out_a \lt1 & (0\lt src_a\lt 1) \\
out_a = 1 & (src_a=\{0,1\})
\end{array}
\right.

となり、半透明要素を描画するとその結果が半透明となることがわかる。

ちなみに以下は背景色と描画色とから結果のアルファ値がわかるグラフ
https://www.desmos.com/calculator/azbx3o4tn9

対処

対処1 : アルファ値の計算に別の式を使う(OpenGL)

FBOに半透明要素を書き込む際に以下のどちらかを指定しておく。
不透明背景に描く場合はどちらでも結果は1になるので変わらない。

glEnable(GL_BLEND);
glBlendFuncSeparate(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA,GL_SRC_ALPHA,GL_ONE);

glEnable(GL_BLEND);
glBlendFuncSeparate(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA,GL_ONE,GL_ONE);

参考:
ofFbo上でのアルファブレンディング - ofxTips-JP
Alpha Blending の2種類の算出方法と使い分け – NEAREAL

対処2 : FBOをブレンディングなしで描画する

FBOの背面に何も描画しない場合はこちらがシンプル。

glDisable(GL_BLEND);
fbo.draw();