DirectXの話 第135回

YCoCgカラー空間を用いたフレームバッファ圧縮

今回はタイトル詐欺です。

詐欺なのは圧縮はしてないという点です。実は圧縮出来てません。

これについては最後の方にこの技術の問題点として触れますので、とりあえず今のところは「YCoCgカラー空間を用いた」という部分だけ気にしておいていただければOKです。

なお、元の論文はこちら

こちらではちゃんとフレームバッファ圧縮出来ていますが…まあ、その辺の含めてあとで触れます。

シェーダで主に使用される色空間はRGBです。レッド、グリーン、ブルーですね。

この他にもYCrCbやHSVなどの色空間がありますが、今回使用するのはYCoCg色空間です。

YはYCrCbのYと同じで輝度を表しますが、計算式は違うので結果は変わります。

CはchrominanceのCです。色度とか彩度とか言われるもののようです。

なお、色度と彩度は同じ物ってわけではないらしいのですが、いまいち違いはわかりません。

まあ、ある種の色の鮮やかさだと思っていただければOKかと思います。

今回使用するCoはオレンジの色度、Cgはグリーンの色度となっています。

変換式は以下のようになります。

極めて簡単ですが、負の値が入っている点に注意が必要です。

逆変換は以下の式で行います。

こちらも簡単ですね。

この計算式によってRGB色空間をYCoCg色空間に変換することは可能です。

しかし、これだけではフレームバッファ圧縮になりません。

RGBの3要素がYCoCgの3要素に変化するだけでは何の意味もありませんし、画面に映す時はRGBに戻さなければいけないため手間が増えるだけです。

例えば、Yは8bit、CoとCgは4bitずつで格納する?

多分、RGBの2要素を4bitにするよりは画像品質が高くなるとは思いますが、劣化が大きいことには変わりありません。この方法ではダメです。

この論文ではどうしているかというと、Yは全てのピクセルに格納し、CoとCgは交互に格納するという手法を採っています。

つまり、以下の画像のように要素を格納しています。

こうすることで1ピクセル3要素が1ピクセル2要素に減ります。

1ピクセル当たりの要素数が2/3になるのでフレームバッファが圧縮出来るというわけです。

しかし、ここからどうやってRGBに戻せばいいでしょう?

2要素だけでは前述の計算式からしてもRGBに戻すことが出来ません。

そこで、4近傍のピクセルから足りない要素を補完します。YCoを格納しているピクセルならCgを、YCgを格納しているピクセルならCoを近傍から取得します。

キモとなるのは近傍から取得したその要素をどのように利用するか、です。

論文では3種類の方法が紹介されています。

Nearestは4近傍の内最もYの値が近いテクセルの色度を採用する方法です。

画像の劣化が大きく、しかも4近傍をフェッチする必要があるために速度面のアドバンテージもありません。

Bilinearは4近傍の色度を全てブレンドする方法です。

Nearestより結果は良好になりますので、Nearestを利用するぐらいならこちらを採用しましょう。

Edge-Directedは最も良好な結果を得られる手法です。今回のサンプルでもこれを採用しています。

技術的には簡単で、近傍のYの値との差が閾値以内のテクセルの色度のみをブレンドして利用します。

結局、この3種類の方法はどれを採用するにしても4近傍のテクセルフェッチが必要になりますので、結果が良好な手段を採らない理由があまりないんですね。

実際、速度面での差はほとんどありません。

さて、この手法を採用する場合においていくつかの疑問点があるかと思いますので、論文から特に気になる部分をピックアップします。

まずはMSAAです。

結論から言うと、可能です。ただ、Resolveをするに当たっては自前で行う必要があります。

基本的には通常通りにMSAAをONにして描画すればいいのですが、そのままResolveしてしまうのはよろしくないようです。

Resolveする前にRGBに戻す必要があり、戻す場合は各サブサンプリングポイントごとにRGB変換をしてやる必要があります。

これでRGBに戻しておきさえすれば普通にResolveしてOKです。ちょっと手間がかかりますね。

次にブレンドです。

YCoCg色空間はリニア空間なのでブレンドはそのまま行ってかまいません。

ただ、SRGBフォーマットのフレームバッファを使用する場合はこの方法は採用出来ない点に注意してください。

SRGBフォーマットの場合、ブレンド時にリニア変換→ブレンド→sRGB変換が行われるらしく、これにYCoCg色空間は対応出来ません。

また、変換式に負の値があることからわかると思いますが、CoとCgは-0.5~0.5の値になります(LDR時)。

これを1要素8bitのカラーフォーマットにそのまま格納することは出来ません。

そこで、-0.5~0.5を0.0~1.0に変換して格納するわけですが、この状態では普通にアルファブレンド出来ません。

OpenGL4.0以降(でしたっけ?)ではブレンド方法もプログラマブルに出来るという機能がつくらしいですが、この機能が使えない環境では8bitカラーフォーマットでブレンドが使用出来ないということになります。

そして、その機能が使えるような環境であればフレームバッファ圧縮をする必要性も薄い可能性が高いのではないでしょうか?

で、後で触れると言っていたフレームバッファ圧縮になっていない理由も実はここにあったりします。

浮動小数点バッファを使用するとブレンドはそのまま行うことが出来るのですが、2要素の浮動小数点バッファは1要素16bitの合計32bitが最小です。

それに対して3要素の浮動小数点バッファの最小はR11G11B10の32bitフォーマットです(Xbox360ではR10G10B10A2があったはず)。

同じ32bitであればフレームバッファ圧縮になりません。その上でYCoCgを採用する理由は何もないでしょう。

浮動小数点の精度が問題とされる可能性もあるかとは思いますが、不可逆圧縮してる時点で説得力はありません。

では、環境マップ等のテクスチャとして利用するのはどうでしょう?

残念ながら、これもNGです。

なぜなら、この手法ではテクスチャのバイリニアフィルタやトライリニアフィルタが使用出来ないからです。

各ピクセルの色度は互い違いに格納されているので、隣のテクセルとブレンドされてしまうのは困った自体以外の何物でも無いのです。

環境マップにしろアルベドテクスチャにしろリニアフィルタは必要になるため、この手法の採用は出来ません。

ドットバイドットで表示されることが確定しているUIのテクスチャなどなら使用出来るかもしれませんが、UIのテクスチャは境界がはっきりしたものが多いため、圧縮による劣化が大きくなる懸念があります。

なにより、最近のUIはドットバイドットとは限りませんしね…

そこで使えそうな場所を考えてみたのですが、Deferred Shading時のアルベドテクスチャ格納バッファはどうでしょうか?

通常ならRGBの3要素+アルファ値に何かという格納法になると思いますが、この手法を使えばもう1要素、何らかの要素を追加出来るようになります。

Deferred Shadingなら使用時にもポイントサンプリングとなるのでリニアフィルタの問題は解決出来ますし、不透明物品にしか使わないのでブレンドの問題はありません。

あとは…描画完了したフレームバッファを次のフレームで使用する際にこの手法で格納しておくというのもあるかもしれません。

ただ、通常であればそのフレームバッファをそのまま利用するところ、色空間変換しながらのコピーをしなければいけないという問題があります。

それをしなければバッファが足りない、という状況でなければフレームバッファをそのまま使い回した方が圧倒的に速いのは間違いないでしょう。

そんないまいち使えなさげな技術のサンプルは以下です。

Download:Sample126.zip

タイトル直下の画像を見ていただければわかるとおり、映像品質の差は拡大してよく見ればわかるよね、というレベルでしかありません。

そういう点ではとても素晴らしい技術ではあります。

ただ、採用する理由がほとんど無いのが残念でなりませんね。

なお、サンプルではFXAAのON/OFF機能を付けていますが、YCoCgを使う場合はFXAAが使用出来ません。

FXAAではリニアフィルタを利用するため、YCoCgバッファに直接使用することは出来ないのです。

RGBに戻してからなら使えますが、この辺もやはりYCoCg色空間を利用しない理由となるでしょう。

次回も完全に未定。

これ面白そう、という技術がありましたら紹介してください。