16bit 浮動小数点のテクスチャできれいにブレンドを行う
はじめに
前回の記事では、WebGL(Three.js)でFBO ping-pongすることで、テクスチャのブレンディングを行い、それによって映像中の動く物体を消す処理をやってみました。
しかし、問題がありました。
今回の手法だと、背景に対してコントラストが高い動体は、焼きついたように長時間残ることもあります。
前記事時点では、上の写真のよう焼き付きが起きてしまっていました。手法はあっているはずなのになんでこうなるのか、解明できず気持ち悪かったので、続けて検討したところ、間違いに気がついたので、修正して投稿します。
手法の検証
以下の式で毎回のカメラ画像を足し合わせていくと、動く物は消える(またはブラーがかかる)というのが、前回の記事の内容でした。
正しく出来ている場合のシミュレーション
まず、Excelを使って、この計算で狙ったとおりの効果が出るかを確認します。
簡単のためにグレースケールと言うことにして、はじめ入力が50の明るさだったところに、10フレームだけ200の明るさが入って、その後また50に戻る、というものです。フィルタの係数はここでは試しに
としてみます。(新しいフレームが0.1の重さで足される)
入力X | 出力Y |
---|---|
50 | 50 |
50 | 50 |
50 | 50 |
50 | 50 |
50 | 50 |
200 | 65 |
200 | 78.5 |
200 | 90.65 |
200 | 101.585 |
200 | 111.4265 |
200 | 120.28385 |
200 | 128.255465 |
200 | 135.4299185 |
200 | 141.8869267 |
200 | 147.698234 |
50 | 137.9284106 |
50 | 129.1355695 |
50 | 121.2220126 |
50 | 114.0998113 |
50 | 107.6898302 |
50 | 101.9208472 |
50 | 96.72876245 |
50 | 92.05588621 |
50 | 87.85029758 |
50 | 84.06526783 |
50 | 80.65874104 |
50 | 77.59286694 |
50 | 74.83358025 |
50 | 72.35022222 |
50 | 70.1152 |
50 | 68.10368 |
50 | 66.293312 |
50 | 64.6639808 |
50 | 63.19758272 |
50 | 61.87782445 |
50 | 60.690042 |
50 | 59.6210378 |
50 | 58.65893402 |
50 | 57.79304062 |
50 | 57.01373656 |
50 | 56.3123629 |
50 | 55.68112661 |
50 | 55.11301395 |
50 | 54.60171256 |
50 | 54.1415413 |
50 | 53.72738717 |
50 | 53.35464845 |
50 | 53.01918361 |
50 | 52.71726525 |
50 | 52.44553872 |
50 | 52.20098485 |
50 | 51.98088637 |
50 | 51.78279773 |
50 | 51.60451796 |
50 | 51.44406616 |
50 | 51.29965954 |
50 | 51.16969359 |
50 | 51.05272423 |
50 | 50.94745181 |
50 | 50.85270663 |
50 | 50.76743596 |
50 | 50.69069237 |
50 | 50.62162313 |
50 | 50.55946082 |
50 | 50.50351474 |
200という明るい色が入った後はしばらく画面が明るくなりますが、その後次第にもとの50に戻っていく様子がわかると思います。本当にこうなっていれば、焼き付きのような状態にはならないはずです。
GLSLでの小数丸め
ここからあーだこーだと考えて、はっと、小数の扱いが間違っていることに気がつきました。GLSLのテクスチャは、0-255 の8bit 整数で色が表されますので、 GLSLのmix
関数を使って色をブレンドした結果、計算結果が小数だったとしても、最終的に画面には当然整数の値で色が書き込まれます。つまり、どこかで小数の計算結果を整数に直しています。
今回使った THREE.BlendShader
では、mix
関数を使って、二枚のテクスチャを混色します。
gl_FragColor = opacity * mix( texel1, texel2, mixRatio );
ここで float mixRatio
にfloatの値を入れたとき、計算結果は小数になっているはずですが、 gl_FragColor
はもちろん整数です。おそらく、代入の際に四捨五入なり切り捨てなりが行われているはずです。
試しに、毎回計算結果の色が四捨五入されていたとしたら、同じシミュレーションをすると以下の様になります
Excelの計算式はこんな感じ
入力X | 出力Y |
---|---|
50 | 50 |
50 | 50 |
50 | 50 |
50 | 50 |
50 | 50 |
200 | 65 |
200 | 79 |
200 | 91 |
200 | 102 |
200 | 112 |
200 | 121 |
200 | 129 |
200 | 136 |
200 | 142 |
200 | 148 |
50 | 138 |
50 | 129 |
50 | 121 |
50 | 114 |
50 | 108 |
50 | 102 |
50 | 97 |
50 | 92 |
50 | 88 |
50 | 84 |
50 | 81 |
50 | 78 |
50 | 75 |
50 | 73 |
50 | 71 |
50 | 69 |
50 | 67 |
50 | 65 |
50 | 64 |
50 | 63 |
50 | 62 |
50 | 61 |
50 | 60 |
50 | 59 |
50 | 58 |
50 | 57 |
50 | 56 |
50 | 55 |
50 | 55 |
50 | 55 |
50 | 55 |
50 | 55 |
50 | 55 |
途中までは50に戻ろうとしますが、55で下げ止まっています。前回の値が55で今回50が入力されたとき、
と言うかんじで、四捨五入した結果、前と同じ55になってしまい、これは何回やっても同じだからです。
これが、色の焼き付きの正体だと思われます。
このシミュレーションをしたExcelのファイルをこちらにあげておきます
修正
さっきは でやりましたが、blendの係数を下げていくと、より焼き付きが顕著になります。しかし、もともと 長時間の平均画像を求めることで、動体を消す が目的なので、blendの係数は極力下げたいです。
小数の丸めによってblendが途中で止まってしまわないようにするには、Shader内のテクスチャの色計算で小数が使える様にすることが必要です。
浮動小数点テクスチャ
そういえば、いつもお世話になっている wgld.org 様に、このような記事が上がっていました
この記事によると、Three.js も準拠している WebGL 1.0 では、浮動小数点テクスチャはエクステンション扱いですが利用可能のようです。Three.jsで使っているサンプルがないか探してみたところ、例のFBOサンプルで使っていました。
このサンプルに倣って、WebGLRenderTarget を浮動小数点テクスチャ化します
やってみる
まず、Three の rendererからglのコンテキストを取得して、浮動小数点テクスチャのエクステンションを有効化します
gl = renderer.getContext(); if ( !gl.getExtension( "OES_texture_float" )) { alert( "No OES_texture_float support for float textures!" ); return; }
つぎに、毎フレームテクスチャとして保存される WebGLRenderTarget を THREE.FloatType
指定します
rt1 = new THREE.WebGLRenderTarget( vidWidth, vidHeight, { minFilter: THREE.LinearFilter, magFilter: THREE.NearestFilter, format: THREE.RGBFormat, type: THREE.FloatType } ); rt2 = new THREE.WebGLRenderTarget( vidWidth, vidHeight, { minFilter: THREE.LinearFilter, magFilter: THREE.NearestFilter, format: THREE.RGBFormat, type: THREE.FloatType } )
以上でOKです。THREE.BlendShaderは既存のままで大丈夫です。
(もっとやることあるかと思ったので拍子抜けしました)
これで、FBO ping-pong で毎回更新されるテクスチャは16bitで計算されるので、小さい係数でblendを行っても四捨五入の際に丸められたりせず、ちゃんと反映されます。
完成品
前回の失敗作
今回修正した物
動体がちゃんと消えて、変な焼き付きがなくなったことがわかると思います。
最後に
前回中途半端な記事を書いてしまい気持ち悪かったのですが、解決してスッキリしました。
また、浮動小数点テクスチャが有効に使えるシーンがわかったのも収穫でした。