リニアワークフロー

Linear Workflow別名:リニア空間、線形色空間

概要

画像に保存されている色の値は、光の量に比例していない。混色やぼかし、ライティングの計算は、いったん光の量に比例する値(リニア)に戻してから行い、表示の直前に保存用の値へ変換し直す。この手順をリニアワークフローと呼ぶ。

デモ

デモを読み込んでいます
各項目の上段は sRGB の値のまま、下段はリニアに戻してから計算した結果です。色の組み合わせとぼかしの強さを変えて比べてください。

使われる場面

  • 3D のレンダリング全般

    物理ベースのライティングは、光の量が足し算で合成されることを前提にしている。現在の 3D 制作ツールやゲームエンジンの多くは、既定で内部の計算をリニアな値で行う。

  • テクスチャの読み込み設定

    テクスチャごとに、色のデータ(sRGB)か数値のデータ(変換なし)かを指定する。制作者がリニアワークフローを意識する場面の多くはここである。

  • 画像編集での合成とぼかし

    画像編集ソフトには、描画モードやぼかしを sRGB の値のまま計算するものがある。色の境目が暗く濁るときは、リニアで計算する設定があるか確認する。

  • 実写と CG の合成

    映像の合成では、実写の素材も CG もリニアな値にそろえてから重ねる。光の足し算として合成しないと、明るさの境目が不自然になる。

判断基準

こういうときこうする
ライティング、混色、ぼかし、アンチエイリアスなど、光の量を足したり平均したりする計算リニアな値で行う。
色を表すテクスチャ(ベースカラー、発光色、UI の画像)sRGB として読み込み、読んだ時点でリニアに変換する。
数値を表すテクスチャ(ノーマル、ラフネス、メタリック、アンビエントオクルージョン、マスク、高さ)変換せずにそのまま読み込む。
画面に表示する直前や、画像ファイルに書き出すときsRGB に変換する。明るさが 1 を超える場合は、先にトーンマッピングで 0〜1 に収める。
光の計算ではなく、見た目で均等に変化してほしいグラデーション(UI の背景など)リニアがよいとは限らない。黒から白をリニアで補間すると、明るい側に偏って見える。sRGB の値のまま補間するか、OKLab のような人の知覚に合わせた色空間で補間する。

こう見えたら

指示の例

  • 赤と緑の境目が暗く濁って見える。混色とぼかしを、sRGB の値のままではなくリニアな値で計算して

  • 画面全体が白っぽく、色が浅く見える。色のテクスチャが sRGB として読み込まれているか確認して

  • ラフネスマップやノーマルマップが sRGB として読み込まれていないか確認して。数値のテクスチャは変換なしにして

詳細

保存されている値は光の量ではない

人間の目は、明るい部分の差より暗い部分の差に敏感である。1 チャンネル 8 ビット(256 段階)の画像で光の量をそのまま等間隔に刻むと、暗部の段階が足りずに縞が見え、明部では見分けられない段階が無駄になる。そこで画像の値は、暗部に多くの段階を割り当てるよう、光の量に対して曲げてから保存する。この曲げ方をガンマと呼び、曲げる変換と戻す変換をガンマ補正と呼ぶ。

一般的な画像やモニターの標準である sRGB では、保存された値 VV と光の量 LL(どちらも 0〜1)が次の関係にある。

L={V/12.92(V≤0.04045)(V+0.0551.055)2.4(V>0.04045)L = \begin{cases} V / 12.92 & (V \le 0.04045) \\[4pt] \left(\dfrac{V + 0.055}{1.055}\right)^{2.4} & (V > 0.04045) \end{cases}

おおよそ L≈V2.2L \approx V^{2.2} と考えてよい。値 0.5(#808080)が表す光の量は、白の半分ではなく約 21% である。光の量が白のちょうど半分になる灰色は、sRGB の値では約 0.735(#BCBCBC)になる。デモの三つ目の項目で、白黒の縞と明るさが揃って見えるのが下段の #BCBCBC なのはこのためである。

値のまま計算すると何が起きるか

混色やぼかしは、光の量を足したり平均したりする計算である。ところが sRGB の値は光の量を曲げたものなので、値のまま平均すると、光の量で平均した場合より暗い結果になる。

赤 (1,0,0)(1, 0, 0) と緑 (0,1,0)(0, 1, 0) の中間を例にとる。値のまま平均すると (0.5,0.5,0)(0.5, 0.5, 0) になり、赤と緑の光はそれぞれ約 21% しかないので、くすんだ暗い黄土色になる。光の量で平均すると、赤と緑の光がそれぞれ 50% になり、sRGB の値に直すと (0.735,0.735,0)(0.735, 0.735, 0) の明るい黄色になる。デモのグラデーションとぼかしで、上段の境目に暗い帯が出るのはこの差である。

計算の流れ

リニアワークフローでは、色を次の順に扱う。

  1. 読み込み:sRGB で保存された色のテクスチャ(アルベドなど)を、リニアな値に変換する。
  2. 計算:ライティング、混色、ぼかし、アンチエイリアスの中間色は、すべてリニアな値で計算する。
  3. 出力:表示の直前に 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 への変換を二重にかけると白っぽく浮く。明るさの具合がどことなくおかしいときは、どの段階で変換がかかっているかを確認するとよい。

関連