ディザリングの実装

ディザリング

トーンマップやガンマ補正を行うと, Float精度のHDRテクスチャから8bit精度のSDRテクスチャへのマッピングができるわけですが, シーンによっては精度不足であったりしてバンドが見えてしまうことがあります。

f:id:hikita12312:20170919235626p:plain:w600

上の図は,画面の左から右にかけて,なめらかに0.0~1.0で明るさを変化させているシーンに対して, Logトーンマップを適用した結果です。精度不足なのかバンドが微妙に見えています。

f:id:hikita12312:20170919235750p:plain

SDRテクスチャは256分割の分解能しかないので,シーンによってはマッピング後に階段状に見えてしまうことがあります。

そこで今回実装するのがディザリングです。

https://en.wikipedia.org/wiki/Ordered_dithering

Bayer Matrix

以下のような行列を4x4のBayer行列と呼びます。

0/168/162/1610/16
12/164/1614/166/16
3/1611/161/169/16
15/167/1613/165/16

実際行列演算をするわけではなくて,このタイルをスクリーン全体に敷き詰めて使います。
あるピクセルのトーンマップ後のカラーを256分割の整数値{n}で表したとき, その小数部分が対応する位置のBayer行列の値よりも大きいか小さいかを比較して, 出力色として{n}を採用するか,{n+1}を採用するかを判断します。

シェーダー内部でif文など条件分岐で,この比較を行うと遅くなるので,このBayer行列を4x4のテクスチャに変換します。

f:id:hikita12312:20170920000806p:plain:w300

あとはシェーダーで,Bayer行列テクスチャをWRAPサンプラでフェッチしながら,適当にスケールして足し算することでディザリングができます。

// SCREEN_SIZE : スクリーンのピクセルサイズ
// DITHER_TEXTURE_SIZE : Bayer行列テクスチャサイズ. DITHER_TEXTURE_SIZE=4
// DITHER_SCALE : スケール.8bit整数ならば DITHER_SCALE=1/255
const float3 ditherMatrix = SampleLod0(g_BeyerMatrixTexture, g_BeyerMatrixSampler, uv*SCREEN_SIZE / DITHER_TEXTURE_SIZE).rgb;
outputColor.rgb += ditherMatrix * DITHER_SCALE - DITHER_SCALE / 2.0f;

結果

f:id:hikita12312:20170919235626p:plain:w300
ディザリング無し
f:id:hikita12312:20170920001316p:plain:w300
ディザリング有り

左右で並べてみると,よくわかりませんが,それは縮小して見ているからです。 フルサイズで比較すると,バンドが軽減されているのがわかります。

f:id:hikita12312:20170920001423p:plain:w600

ディザリング有り/無しの結果の一部分をトリミングして,適当にレベル補正をした結果の比較です。 バンド付近の濃淡がドットで表現されています。