可視スペクトルを正確にレンダリングする:色科学とAbney効果の補正
Brandon Li氏が、可視スペクトルの各波長をディスプレイで正確に表示するための技術的な課題と解決策をまとめた記事が面白かったので、関連する色科学の話題も含めて掘り下げてみた。
「虹色のグラデーションを正確に表示する」だけの話なのに、やってみると色科学の深淵にハマるタイプの問題だ。
基本的な変換パイプライン
可視光の各波長(380nm〜780nm)をディスプレイのRGB値に変換するには、以下のステップを踏む。
1. CIE等色関数で波長をXYZに変換
等色関数(Color Matching Functions)は、特定の波長の光を再現するために赤・緑・青の原色がどれだけ必要かを定義する関数だ。
Brandon Li氏の記事で注目すべきは、1931年の古典的なCIE CMFではなくCIE 2012の「生理学的に妥当なCMF」を使っている点。これは錐体細胞の実験データから導出されたもので、1931版とは結果がかなり異なる。1931年のCMFは当時の実験手法の制約から、短波長域(青〜紫)での精度に問題があることが知られている。
2. XYZ色空間
波長をCIE XYZ座標に変換する。輝度(Y)と色度(x, y)が分離され、明るさと色相を独立に制御できるようになる。
3. sRGBへの変換
XYZからsRGBへの線形マトリクス変換を行う。sRGB規格(IEC 61966-2-1)で定義されている標準の変換行列だ。
この行列の値はsRGBの原色座標とD65白色点から導出される。その後ガンマ補正 を適用してsRGB値にする。ここまでは教科書通りだ。
sRGBガマット問題 - 表現できない色がある
ここで最初の壁にぶつかる。純粋なスペクトル色(単色光)はsRGBのガマット(色域)の外側に位置する。RGB値は0〜1の範囲に収める必要があるため、変換結果に負の値が出てくる波長が存在する。
つまり「この波長の色はsRGBディスプレイでは物理的に再現できない」ということだ。
対処法としてはいくつかのアプローチがある。
- クリッピング: 負の値を0にクランプ。簡単だが色相が狂う
- 固定比率でのグレー混合: スペクトル色にグレーを混ぜてガマット内に引き込む
- 波長依存のグレー調整: 波長ごとにグレーの混合量を変える
- 色度図上での投影: ガマット境界に向かって投影する
San Diego State Universityのガイドでも指摘されている通り、ガマット外の色を境界に引き戻す際に「定主波長の線に沿って投影する」のが一般的なアプローチだが、これだけでは知覚的に正しくならない。
Abney効果 - 青にグレーを混ぜると紫になる
最も興味深いのがAbney効果の補正だ。
Abney効果とは、彩度を変化させると色相が知覚的にシフトする現象。具体的には「青い光にグレー(白色光)を混ぜたら薄い青ではなく紫に見える」ということが起きる。CIE色度図上では直線的に混合しているのに、人間の目にはそう見えない。
これはCIE XYZ色空間の根本的な限界に起因する。XYZは色の「再現」のために設計されたものであり、混合光条件下での「知覚的な正確さ」を保証するものではない。
手動補正アプローチ
Brandon Li氏は、実際のガス放電管のスペクトルを回折格子で分光し、コンピュータ生成の色と比較して手動で補正するアプローチを取った。各波長に対して「有効波長(effective wavelength)」を特定し、脱彩度時の色相シフトを打ち消す調整を行う。
地道だが実用的な方法だ。スペクトルデータはNISTの原子スペクトルデータベースから取得し、イオン化状態の分布はボルツマン因子でモデル化している。
CIECAM02による本格的補正
本来の正攻法は色の見えモデル(Color Appearance Model)を使うことだ。CIECAM02は人間の色知覚を数学的にモデル化したもので、Abney効果を含む知覚現象を補正できる。
手順としては:
- XYZからCIECAM02の座標系(J, C, h)に変換
- CIECAM02空間内で色の混合・操作を行う
- XYZに戻してからsRGBに変換
CIECAM02空間内での操作は知覚的に均一なため、グレーとの混合で色相がシフトしない。ただし計算コストが高く、実装も複雑だ。
OkLCh - CSSの色指定を変えた色科学の議論
このAbney効果とガマットマッピングの問題は、実はCSSの色指定にも直結している。
なぜHSLではだめなのか
CSSで長年使われてきた hsl() はRGBを円筒座標に変換しただけで、知覚的な均一性がない。HSLで lightness: 50% の青と黄色を並べると、青の方がはるかに暗く見える。「同じ数値なのに見た目が揃わない」問題は、デザインシステムでカラーパレットを構築する際に厄介だ。
CIELCh → OkLChへの変更
CSS Color Level 4の仕様策定では、当初CIELCh(CIELAB由来の円筒座標系)がガマットマッピングの基準色空間として提案されていた。CIELAbは1976年に「知覚的に均一」を目指して設計された色空間だが、実際には青色域で紫方向への色相シフトを起こす。彩度を下げると青が紫に寄る。Abney効果と同根の問題だ。
この問題を受けて、CSSWGはOkLChベースのガマットマッピングに変更した。OkLChはBjörn Ottosson氏が2021年に提案した色空間で、CIELAbの青域での色相シフトを改善している。
CSS oklch() の実用
oklch() は L(明度)、C(彩度)、H(色相)の3軸で色を指定する。
/* oklch(明度 彩度 色相) */
.brand { color: oklch(0.6 0.15 250); }
.brand-light { color: oklch(0.8 0.10 250); } /* 同じ色相で明度だけ上げる */
.brand-muted { color: oklch(0.6 0.05 250); } /* 同じ色相で彩度だけ下げる */
HSLと違い、明度や彩度を変えても色相がシフトしない。デザイントークンとして「色相を固定して明度・彩度のバリエーションを作る」運用が素直にできる。
color-mix() と組み合わせると、ホバー状態やアクセントカラーの派生も簡潔に書ける。
.button:hover {
background: color-mix(in oklch, var(--brand) 80%, white);
}
OkLChの空間内で混合するため、グレーを混ぜても色相が紫に寄らない。CIELAbベースの混合だとここで青→紫シフトが起きる。
ブラウザ対応状況
oklch() は Chrome 111+、Firefox 113+、Safari 15.4+、Edge 111+ で対応済み。2026年時点でグローバルサポート率は93%前後。color-mix() も同等の対応状況で、実務で使える段階に入っている。
CSSで oklch() が使えるようになった背景に、ここまで扱ってきたAbney効果やガマットマッピングの議論がそのまま繋がっている。
ACES 2.0 - 映像制作での広色域レンダリング
映像制作の世界では、同様の問題がACES(Academy Color Encoding System)の文脈で議論されている。
ACES 1.xでは3×3マトリクスによる色空間変換が直線的な変換しかモデルできず、Abney効果に類似した色相シフトが発生する問題があった。ACES 2.0ではガマットマッピングのアルゴリズムが刷新され、この問題に対処している。
ACEScgやBT.2020のような広色域空間を使うことで、sRGBでは表現できないスペクトル色に近い領域までカバーできる。ただしディスプレイがsRGBの場合、最終的にはガマットマッピングが必要になる点は同じだ。
実装に使えるツール
スペクトルレンダリングを自分で実装する場合、Pythonのcolour-scienceライブラリが便利だ。BSD-3-Clauseライセンスで、色空間変換、スペクトルデータ処理、ガマットマッピングなど、色科学関連のアルゴリズムとデータセットが網羅的に揃っている。
pip install colour-science
CIE等色関数のデータ、XYZ↔sRGB変換、CIECAM02の実装まで含まれているので、スペクトルレンダリングのプロトタイプを作る際の出発点として使える。
虹色を正確に表示したいだけなのに、1931年の測定誤差やら人間の目の癖やらCSSの仕様変更やらが全部絡んでくる。沼だ。Brandon Li氏がガス放電管と回折格子で手動補正してるのが一番面白かった。
自分は色弱で、色相が一般的な見え方と違う。信号も割と困るし、昔の電球の方がまだ判別しやすかった。電気工事士も配線や抵抗の色が判別できなくて諦めた。だから「人間の脳を騙して正しく色を見せる」技術には個人的に期待してしまう。知覚補正がもう少し進めば、せめてスペクトルくらいは見分けられるようになるかもしれない。
参照: