はじめに
"DXGI_FORMAT の _SRGB の有り無しの違い" というものを真面目に確認しようという話。
ぶっちゃけここに書かれている通りなのですが、実際の動作を見てみましょう。
ガンマ補正については下記の記事がとてもわかりやすいので一読されておくことをお勧めします。
調べ方
テストプログラムを作成しました。
テスト環境
- Windows 10
- Visual Studio 2017
- Core i5-2410M 内蔵 GPU (Intel HD Graphics 3000)
Direct3D 11 の API が通る環境なら大体は動くと思います。
256 x 1 のサイズで DXGI_FORMAT_R8G8B8A8_UNORM と DXGI_FORMAT_R8G8B8A8_UNORM_SRGB のテクスチャを作成し、 2 種類の Pixel Shader を実行します。
- パターン 1 (PixelShader.hlsl)
単純に 0.0 ~ 1.0 のを線形な UV 座標値をそのまま色として出力します。 - パターン 2 (PixelShader2.hlsl)
パターン 1 の結果をソースにしてテクスチャサンプリングして同座標にそのまま出力します。
出力結果は CPU 側にコピーし、 CSV で出力してグラフにしました。
パターン 1 は主に書き出し時の結果の違いを確認します。
パターン 2 は主に読み出し時の取得値の違いを確認します。
結果
表にしました。
パターン 2 の入力データは 2 の出力結果を CPU 側に持ってきて再度指定フォーマットでテクスチャ化したものを使用しています。
No. | パターン | 入力データ | 入力フォーマット | 出力フォーマット | グラフ |
---|---|---|---|---|---|
1 | パターン 1 | 線形値 | DXGI_FORMAT_R8G8B8A8_UNORM | (a) | |
2 | パターン 1 | 線形値 | DXGI_FORMAT_R8G8B8A8_UNORM_SRGB | (b) | |
3 | パターン 2 | ガンマカーブ | DXGI_FORMAT_R8G8B8A8_UNORM | DXGI_FORMAT_R8G8B8A8_UNORM | (b) |
4 | パターン 2 | ガンマカーブ | DXGI_FORMAT_R8G8B8A8_UNORM_SRGB | DXGI_FORMAT_R8G8B8A8_UNORM | (c) |
5 | パターン 2 | ガンマカーブ | DXGI_FORMAT_R8G8B8A8_UNORM | DXGI_FORMAT_R8G8B8A8_UNORM_SRGB | (d) |
6 | パターン 2 | ガンマカーブ | DXGI_FORMAT_R8G8B8A8_UNORM_SRGB | DXGI_FORMAT_R8G8B8A8_UNORM_SRGB | (b) |
グラフは横軸がシェーダーでの入力値 (UV 座標) 、縦軸が出力値 (VRAM の値) になります。
(a) | |
(b) | |
(c) | |
(d) |
考察
パターン 1 は Pixel Shader 内では確実に線形のデータを書きだしています。 No.1 の出力が _SRGB でない場合は入力の線形値がそのまま出力されている事が確認できる一方、 No.2 の _SRGB の方は出力結果がガンマ補正が適用されて出力されています。
パターン 1 での違いを元にパターン 2 を見ていきます。
No.3 では出力結果が No.2 と同じガンマカーブになっています。これはガンマカーブのデータを _SRGB なしで読み込み _SRGB なしに対して書き込んだため、読み込んだガンマカーブのデータがそのまま書き出された結果であろうと考えられます。
No.4 はガンマカーブデータを _SRGB ありで読み込み、 _SRGB なしで書き込んだ結果、線形なデータが得られていますが値を見ていくとガタガタした値になっています。これまでの結果から _SRGB なしで書き出す場合はそのまま出力されているはずなので、読み込み時にガンマカーブを線形に戻す補正がされたと考えられます。ガタガタしているのは計算誤差の影響。
No.5 はさらにガンマがかかったものになっていますが、入力が _SRGB なし、出力が _SRGB となっているのでガンマカーブが適用されている値が Pixel Shader に渡され、それを書き出し時にさらにガンマ補正をした結果と考えられます。
No.6 は入出力ともに _SRGB あり。これまでの結果から読み込み時にガンマカーブを線形に戻し、出力時に再度ガンマ補正がされたと考えられます。
まとめ
まあ MSDN の記述通りなのですが、
- _SRGB なしのフォーマットではメモリ (VRAM) 上の値を線形の値とし、そのまま 0.0 ~ 1.0 へ線形にマッピングしてシェーダー上で読み書きを行います。
- _SRGB ありのフォーマットではメモリ上の値は sRGB ガンマが適用されているものとして扱います。 sRGB フォーマットのものをシェーダーで読み込む時は線形の値に変換して入力し、書き出す時は再度ガンマ補正をしてメモリに書き出します。
ではなぜこのような機能があるのか、という話なのですが。
まず PC で映像を扱う場合、現在のところ SDR の sRGB なディスプレイで作業をしているケースが多いと思うのですが、そのような環境で制作をした映像データーは sRGB の映像データーなわけです。
sRGB の (というかガンマ補正がされた) 映像データーは暗い部分はデーターの諧調が細かく、明るい部分は諧調が荒くなっています (グラフ (b) 参照) 。画像処理をする場合、線形な空間で扱った方が都合がいい (正確にやるならそうしなくてはならない) ので sRGB の画像を線形に変換することになります。
そういった時、 sRGB として扱うテクスチャに対しては _SRGB 付きを指定すると入出力時に自動で補正をしてくれるので、シェーダー内では線形のデーターとして、シェーダー外では sRGB として扱えて便利ですよ、ということです。
先に挙げた sRGB でデザインされた素材を入力する場合、 _SRGB 付にすれば線形に補正され、それを元に HDR レンダリングした後、結果を出力する先を _SRGB 付にすれば出力先も sRGB でガンマ補正され、自分で計算をしなくても (sRGB のディスプレイで) 適切に表示されるようになるはずです。
ただ、色空間のフォーマットは様々なものがあり、 sRGB はそのうちの一つにしかすぎません。 Direct3D の場合、今のところ API でサポートしてくれるのは sRGB だけなのでそれ以外については自力でなんとかする必要があります。それだったら sRGB 含めて自前で処理する、というのも全然ありだと思います。実際、 Microsoft が提供している Direct3D での HDR 出力サンプルである "D3D12HDR" では sRGB / BT.2100 (PQ) / Linear の変換を全て Pixel Shader による計算処理を実装して行っています。
そんなわけで _SRGB の出番は今後は少なくなってくるかもしれません。