物理ベースレンダリング -リニアワークフロー編 (2)-


CEDEC2015では色々な交流ができて良かったです。こんにちは、岩崎です。

前回に続き、色空間のお話です。前回はリニア色空間とsRGB色空間について解説をしました。
モニターに出力するときにはsRGB色空間で出力しなければ意図通りの輝度にならないということがわかっていただけたかと思います。

人間の視覚的な知覚でまっすぐ線形に見えるというのがsRGB空間です。
今回は実際に制作するときに注意する点を解説したいと思います。

計算はリニア空間 / 表示はsRGB空間

wood光の強さが2倍になれば輝度も2倍になります。
言葉にすると当たり前のことですが、実際に処理手順を整理するときにはこれを意識しておく必要があります。
正しく計算できるのは「リニア(線形)色空間」です。計算する場合はカーブで歪んでいる状態のまま行うには不便です。
sRGB空間のように非線形の空間で輝度計算すると本来とは異なる結果になってしまいます。

ポイントは計算はリニア空間表示をsRGB空間で行うということです。
映像の世界ではリニアワークフロー(Linear workflow)という用語があります。色をリニアカラースペース(Linear color space)で扱うという手法です。
リニア空間で計算を行うことで数学上正しい輝度計算が可能になり、扱うデーターもリニア空間で扱うことでそのまま計算に使うことができるようになります。

そのためには、どの描画タイミングでどのデーターがリニア空間なのかsRGB空間なのかをしっかりと把握しておく必要があるというわけです。

手描きの絵のデーターはリニア空間?sRGB空間?

monitor描画の時の注意点は今までお話しした通りですが、それでは今扱っているデーターはリニアなのでしょうか?sRGBなのでしょうか?
これを判断する基準の例としては若干乱暴ですがモニターを見ながら手描きで作ったものはsRGB、計算で作ったものはリニアと考えられます。
もちろん手描きのものでも、後で変換したりカラープロファイルを正しく設定していればリニアデーターにすることは可能です。
ではなぜペイントの絵はsRGBと言えるのでしょうか?

絵を描くときには基本的には「見た目」で描いています。カラーパレットで色を選択するときも描いていくときもディスプレイに表示されている色がすべてです。
表示されている輝度はモニター上ではsRGB空間ですから、塗っている色はsRGBの色で判断して塗っていることになります。
50%グレーのつもりで暗くなってしまっているリニア21%のグレーを選択して塗っていたとしても「もともとそういう色」としてモニター上の色の見た目で塗っているわけですから、描き手には何ら支障なく意識せずとも絵は描けます。
同様に写真データーなどもディスプレイで綺麗に正しい輝度で見えるように色補正されているということになりますからsRGB空間と言えます。
(厳密にはモニターのガンマ補正設定もしっかりとキャリブレーションしている環境が必要です)

ここで注意しなければならないのは手描きの絵(sRGB)のデーターを計算で使うときにリニア空間への変換を通さなければならないということです。
「リニア色空間に変換なんて考えたことなかった」という方は、もしかしたら実はシェーダーの計算結果が歪んでいるかもしれません。

法線マップなどの計算上のデーターは基本的にリニア空間のデーターになっていると思います。
計算結果をそのまま出力しているものですので基本的にはsRGB変換は不要です。

sRGB→リニア 色空間変換

sRGB_Linear

「リニア空間に変換するには元の画像の輝度を2.2乗すればよい」ということがまず前回からの基本ですが、厳密にsRGB色空間の仕様が存在します。定義を調べてみます。

Wikipedia sRGB
https://en.wikipedia.org/wiki/SRGB

WikipediaでのsRGB空間からリニア空間への変換式は次のように書かれています。

wikipedia_toLinear

※Wikipediaから引用
RGB輝度を0.0~1.0とした場合に上記のような計算をすればよいことになります。
この計算を厳密にコーディングしてみます。aとありますから、aのところに0.055を当てはめてみます。

if ( ColorSRGB <= 0.04045 ) {
	ColorLINEAR = ColorSRGB / 12.92;
}
else {
	ColorLINEAR = pow((ColorSRGB + 0.055) / 1.055, 2.4);
}

実際には最適化すると思いますのでSIMD化やfloat化などで各RGB要素に上記計算を高速に行います。
この計算の高速化近似が「2.2乗」の正体です。2.2乗することで上記計算に概ねフィットします。

ColorLINEAR = pow(ColorSRGB, 2.2);

当然ながら、手描きの絵をリニア色空間へ変換すると元の見た目よりも暗く見えるようになります。
srgblinear
計算中の輝度は暗く見えても問題ありません。線形になっていることが重要です。

リニア→sRGB 色空間変換

Linears_sRGB

次に、計算がすべて終わった画像を視覚的に線形に見えるようにするためにはsRGB空間に変換する必要があります。
これは表示上の問題ですので最後の出力結果のところに適用すればよいです。

同じくWikipediaから

wikipedia_toSRGB

こちらもa=0.055ですので次のようなコードになります。

if ( ColorLINEAR <= 0.0031308) {
	ColorSRGB = ColorLINEAR * 12.92;
}
else {
	ColorSRGB = 1.055 * pow(ColorLINEAR, 1.0 / 2.4) - 0.055;
}

これもまた高速化近似では「1/2.2 (0.45454545…)」乗で実現されます。

ColorSRGB = pow(ColorLINEAR, 1.0/2.2);

リニア色空間とsRGBの変換計算いかがだったでしょうか?
次回はDirectX11上で実装をしてみるお話です。

参考資料 (さらに詳しく)

■[コンポジゴク]分かる!リニアワークフローのコンポジット
コンポジションのためのリニアワークフローについて詳しく解説されています。

■[CHILLIANT] sRGB Approximations for HLSL
IAN TAYLOR氏による2.2乗の場合の誤差について比較と様々なフィッティングで近似を行っている資料です
chilliant