トーンマッピング

Tone Mapping別名:トーンマップ

概要

明るさが 1 を超える HDR の画像を、画面に表示できる 0〜1 の範囲に収める変換。単純に切り捨てると明るい部分が白く飛び、色も変わるので、曲線でなだらかに圧縮する。

デモ

デモを読み込んでいます
白い線をドラッグすると、1 を超えた値を切り捨てた場合と、曲線で収めた場合を比べられます。上の灰色の見本は、左から明るさ ¼〜16 です。画像にカーソルを乗せると、その点が右下の曲線のどこにあたるかが表示されます。

使われる場面

  • 3D ゲームとリアルタイム描画

    ライティングは 1 を超える明るさのまま計算し、フレームの最後にトーンマッピングしてから表示する。太陽や爆発の明るさと、暗がりの細部を一枚の画面に収める。

  • 静止画や映像のレンダリング

    書き出しの設定で曲線を選ぶ。曲線によって、明るい色が白に近づく速さや、画面全体のコントラストが変わる。

  • 写真の HDR 合成

    露出を変えて撮った複数の写真を合成し、明るい空と暗い室内の両方が見える一枚にする。

  • 画面の見た目の基調

    露出と曲線の組み合わせで、全体の明るさとコントラストの基調を決める。細かな色の調整は、その後に行うことが多い。

判断基準

こういうときこうする
ライティングを 1 を超える値のまま計算している表示の直前にトーンマッピングする。しないと 1 を超えた部分が切り捨てられ、白く飛ぶ。
光源や空など、明るく色の付いた部分が黄色や水色にずれる色の成分ごとに切り捨てられて比率が崩れている。曲線で圧縮する手法に切り替える。
明るい部分を自然に白へ近づけたい(映画のような見た目)ACES 系のように、明るい色ほど彩度が下がり、白に近づく曲線を使う。
UI や、色を指定どおりに見せたいものトーンマッピングの後に重ねて描く。曲線を通すと色とコントラストが変わる。
画面全体が暗い、または明るすぎる曲線を変える前に露出を合わせる。トーンマッピングは、露出を合わせたうえで明るい部分をどう収めるかを決めるものである。

こう見えたら

  • 明るいところが白く飛んで、明るさの差がない色と明るさ
  • 強い光の中心の色が、黄色や水色に変わる色と明るさ
  • 画面全体が灰色っぽく、コントラストが弱い色と明るさ

指示の例

  • 太陽や明るい空が白く飛んで、明るさの差がなくなっている。トーンマッピングを入れて、明るい部分をなだらかに収めて

  • 強い光の芯の色が、黄色や水色に変わっている。1 を超えた値を切り捨てずに、ACES 系のトーンマッピングで収めて

  • 画面全体が暗い。トーンマッピングの曲線を変える前に、露出を 1 段(1 EV)上げて

  • 画面が灰色っぽく、コントラストが弱い。トーンマッピングが二重にかかっていないか確認して

詳細

光の明るさには上限がない

現実の光は、日なたと日陰で何百倍も明るさが違い、太陽そのものはさらに桁違いに明るい。リニアワークフローで光を計算すると、値は 1 を超えて大きくなる。1 を超える値まで持つ画像を HDR(ハイダイナミックレンジ)と呼ぶ。

一方、一般的な画面が表示できるのは 0〜1 の範囲だけである。1 を超えた値を切り捨てると、1 以上の明るさはすべて同じ白になり、明るさの違いが消える。色の付いた光では色も変わる。たとえば (4,1,0.2)(4, 1, 0.2) のオレンジの光は、成分ごとに切り捨てると (1,1,0.2)(1, 1, 0.2) の黄色になる。デモの左側で、赤い光の芯が黄色に、青い光の芯が水色になっているのはこのためである。HDR の値を曲線で 0〜1 に圧縮し、明るさの順序と色味をなるべく保つ変換をトーンマッピングと呼ぶ。

曲線の例

もっとも単純な曲線は Reinhard の式である。

Lout=L1+LL_\text{out} = \frac{L}{1 + L}

0 は 0 に、1 は 0.5 に、どれだけ大きな値も 1 の手前に収まる。暗い部分はほぼそのまま残り、明るい部分ほど強く圧縮される。ただし 1 には届かないので、真っ白にはならない。

ACES の曲線を近似した次の式もよく使われる。

f(x)=x(2.51x+0.03)x(2.43x+0.59)+0.14f(x) = \frac{x(2.51x + 0.03)}{x(2.43x + 0.59) + 0.14}

暗い部分をわずかに沈めて中間の明るさを持ち上げる S 字の形で、入力が 7 程度を超えると 1 に届くので、白が白として出る。色の成分ごとにこの曲線を通すと、明るい色ほど成分どうしの差が縮まり、彩度が落ちて白に近づく。

vec3 hdr = lighting * exposure;           // リニアな HDR の値。露出を先に掛ける
vec3 ldr = hdr / (1.0 + hdr);             // Reinhard。成分ごとに 0〜1 に収める
outColor = vec4(linearToSrgb(ldr), 1.0);  // sRGB への変換はトーンマッピングの後

見分け方と落とし穴

1 を超えた値の切り捨ては、明るい部分が平らな白い塊になることと、色の付いた強い光の芯が黄色や水色、紫に寄ることで見分けられる。デモの灰色の見本では、切り捨てると 1 以上がすべて同じ白になり、曲線では 16 まで段階が残る。

処理の順序は、露出を掛け、トーンマッピングし、最後に sRGB へ変換する。sRGB に変換した後の値にトーンマッピングをかけると、曲線が想定と違う値に掛かり、明るさの具合がおかしくなる。トーンマッピングを二重にかけると、白が灰色にくすみ、コントラストの低い眠い画面になる。

ブルームのように明るさを元にする効果は、トーンマッピングの前に HDR の値で計算する。トーンマッピングの後では 1 を超える明るさの情報が残っておらず、どこが本当に明るいかを区別できない。

関連