リニアワークフロー
Linear Workflow別名:リニア空間、線形色空間
概要
画像に保存されている色の値は、光の量に比例していない。混色やぼかし、ライティングの計算は、いったん光の量に比例する値(リニア)に戻してから行い、表示の直前に保存用の値へ変換し直す。この手順をリニアワークフローと呼ぶ。
デモ
使われる場面
3D のレンダリング全般
物理ベースのライティングは、光の量が足し算で合成されることを前提にしている。現在の 3D 制作ツールやゲームエンジンの多くは、既定で内部の計算をリニアな値で行う。
テクスチャの読み込み設定
テクスチャごとに、色のデータ(sRGB)か数値のデータ(変換なし)かを指定する。制作者がリニアワークフローを意識する場面の多くはここである。
画像編集での合成とぼかし
画像編集ソフトには、描画モードやぼかしを sRGB の値のまま計算するものがある。色の境目が暗く濁るときは、リニアで計算する設定があるか確認する。
実写と CG の合成
映像の合成では、実写の素材も CG もリニアな値にそろえてから重ねる。光の足し算として合成しないと、明るさの境目が不自然になる。
判断基準
| こういうとき | こうする |
|---|---|
| ライティング、混色、ぼかし、アンチエイリアスなど、光の量を足したり平均したりする計算 | リニアな値で行う。 |
| 色を表すテクスチャ(ベースカラー、発光色、UI の画像) | sRGB として読み込み、読んだ時点でリニアに変換する。 |
| 数値を表すテクスチャ(ノーマル、ラフネス、メタリック、アンビエントオクルージョン、マスク、高さ) | 変換せずにそのまま読み込む。 |
| 画面に表示する直前や、画像ファイルに書き出すとき | sRGB に変換する。明るさが 1 を超える場合は、先にトーンマッピングで 0〜1 に収める。 |
| 光の計算ではなく、見た目で均等に変化してほしいグラデーション(UI の背景など) | リニアがよいとは限らない。黒から白をリニアで補間すると、明るい側に偏って見える。sRGB の値のまま補間するか、OKLab のような人の知覚に合わせた色空間で補間する。 |
こう見えたら
指示の例
赤と緑の境目が暗く濁って見える。混色とぼかしを、sRGB の値のままではなくリニアな値で計算して
画面全体が白っぽく、色が浅く見える。色のテクスチャが sRGB として読み込まれているか確認して
ラフネスマップやノーマルマップが sRGB として読み込まれていないか確認して。数値のテクスチャは変換なしにして
詳細
保存されている値は光の量ではない
人間の目は、明るい部分の差より暗い部分の差に敏感である。1 チャンネル 8 ビット(256 段階)の画像で光の量をそのまま等間隔に刻むと、暗部の段階が足りずに縞が見え、明部では見分けられない段階が無駄になる。そこで画像の値は、暗部に多くの段階を割り当てるよう、光の量に対して曲げてから保存する。この曲げ方をガンマと呼び、曲げる変換と戻す変換をガンマ補正と呼ぶ。
一般的な画像やモニターの標準である sRGB では、保存された値 と光の量 (どちらも 0〜1)が次の関係にある。
おおよそ と考えてよい。値 0.5(#808080)が表す光の量は、白の半分ではなく約 21% である。光の量が白のちょうど半分になる灰色は、sRGB の値では約 0.735(#BCBCBC)になる。デモの三つ目の項目で、白黒の縞と明るさが揃って見えるのが下段の #BCBCBC なのはこのためである。
値のまま計算すると何が起きるか
混色やぼかしは、光の量を足したり平均したりする計算である。ところが sRGB の値は光の量を曲げたものなので、値のまま平均すると、光の量で平均した場合より暗い結果になる。
赤 と緑 の中間を例にとる。値のまま平均すると になり、赤と緑の光はそれぞれ約 21% しかないので、くすんだ暗い黄土色になる。光の量で平均すると、赤と緑の光がそれぞれ 50% になり、sRGB の値に直すと の明るい黄色になる。デモのグラデーションとぼかしで、上段の境目に暗い帯が出るのはこの差である。
計算の流れ
リニアワークフローでは、色を次の順に扱う。
- 読み込み:sRGB で保存された色のテクスチャ(アルベドなど)を、リニアな値に変換する。
- 計算:ライティング、混色、ぼかし、アンチエイリアスの中間色は、すべてリニアな値で計算する。
- 出力:表示の直前に sRGB の値へ変換する。明るさが 1 を超える場合は、先にトーンマッピングで 0〜1 に収める。
読み込みと出力の変換は、GPU の sRGB 対応のテクスチャ形式や描画先を使えば、ハードウェアが自動で行う。
vec3 albedo = texture(albedoMap, uv).rgb; // sRGB 形式のテクスチャなら、読んだ時点でリニア
vec3 color = albedo * lighting; // 計算はリニアで行う
outColor = vec4(linearToSrgb(color), 1.0); // 表示の直前に sRGB へ(描画先が sRGB 形式なら自動)
よくある間違い
変換が必要なのは、色を表すデータだけである。ノーマルマップ、ラフネス、マスクのように数値そのものに意味があるデータは、変換せずにそのまま読む必要がある。これらを sRGB の色として読み込むと、値が曲がって法線の向きや質感が変わってしまう。
反対に、sRGB の値をリニアな値として計算に渡すと、中間の値が明るく持ち上がり、色が白っぽく浅くなる。リニアへの変換を二重にかけると暗く沈み、sRGB への変換を二重にかけると白っぽく浮く。明るさの具合がどことなくおかしいときは、どの段階で変換がかかっているかを確認するとよい。