RGB カラーを扱っているとしましょう。各カラーは 3 つの強度または明るさで表されます。「リニア RGB」と「sRGB」のどちらかを選択する必要があります。ここでは、3 つの異なる強度を無視して単純化し、強度が 1 つだけであると仮定します。つまり、グレーの色合いだけを扱っているとします。
線形色空間では、格納する数値とそれらが表す強度との関係は線形です。実際には、これは数値を 2 倍にすると、強度 (グレーの明度) が 2 倍になることを意味します。2 つの強度を一緒に加算したい場合 (2 つの光源の寄与に基づいて強度を計算している、または不透明なオブジェクトの上に透明なオブジェクトを追加しているなどの理由で)、単純に2 つの数字を一緒に。あらゆる種類の 2D ブレンディングや 3D シェーディング、またはほぼすべての画像処理を行っている場合は、強度をリニア カラー スペースにする必要があります。、したがって、数値を加算、減算、乗算、および除算するだけで、強度に同じ効果を与えることができます. ほとんどの色処理およびレンダリング アルゴリズムでは、すべてに重みを追加しない限り、線形 RGB でのみ正しい結果が得られます。
それはとても簡単に聞こえますが、問題があります。光に対する人間の目の感度は、高強度よりも低強度の方が優れています。つまり、識別できるすべての強度のリストを作成すると、明るい強度よりも暗い強度のほうが多いということです。別の言い方をすれば、暗いグレーの色合いは、明るいグレーの色合いよりもよく区別できます。特に、強度を表すために 8 ビットを使用していて、これを線形色空間で行う場合、最終的に明るい色合いが多すぎて、暗い色合いが不十分になります。暗い領域ではバンディングが発生しますが、明るい領域では、ユーザーが区別できないさまざまな色合いの白に近いビットが無駄になっています。
この問題を回避し、これらの 8 ビットを最大限に活用するために、sRGBを使用する傾向があります。sRGB 標準は、色を非線形にするために使用する曲線を示します。曲線は下の方が浅いので濃いグレーが多く、上の方が急なので明るいグレーが少なくなります。数値を 2 倍にすると、強度が 2 倍以上になります。これは、sRGB カラーを一緒に追加すると、必要以上に明るい結果になることを意味します。最近では、ほとんどのモニターが入力カラーを sRGB として解釈します。そのため、画面に色を配置したり、チャネルごとに 8 ビットのテクスチャに色を保存したりする場合は、色を sRGB として保存して、 8 ビットを最大限に活用します。
ここで問題があることに気付くでしょう。色を線形空間で処理したいのですが、sRGB に保存したいのです。つまり、読み取り時に sRGB から線形への変換を行い、書き込み時に線形から sRGB への変換を行うことになります。線形の 8 ビット強度には十分な暗さがないと既に述べたように、これは問題を引き起こすため、もう 1 つの実用的なルールがあります。回避できる場合は、8 ビットの線形カラーを使用しないでください。8 ビット カラーは常に sRGB であるという規則に従うのが慣例になりつつあるため、強度を 8 ビットから 16 ビットに、または整数から浮動小数点に拡大すると同時に、sRGB から線形への変換を行います。同様に、浮動小数点処理が終了したら、sRGB への変換と同時に 8 ビットに絞り込みます。これらのルールに従えば、
sRGB 画像を読み取っていて、線形強度が必要な場合は、次の式を各強度に適用します。
float s = read_channel();
float linear;
if (s <= 0.04045) linear = s / 12.92;
else linear = pow((s + 0.055) / 1.055, 2.4);
逆に、画像を sRGB として書き込みたい場合は、次の式を各線形強度に適用します。
float linear = do_processing();
float s;
if (linear <= 0.0031308) s = linear * 12.92;
else s = 1.055 * pow(linear, 1.0/2.4) - 0.055; ( Edited: The previous version is -0.55 )
どちらの場合も、浮動小数点の値の範囲は 0 から 1 であるため、8 ビット整数を読み取る場合は最初に 255 で割り、8 ビット整数を書き込む場合は 255 を掛けます。最後に、通常と同じ方法で。sRGB を使用するために知っておく必要があるのはこれだけです。
これまでは 1 つの強度だけを扱ってきましたが、色にはもっと賢い方法があります。人間の目は、さまざまな色合いよりもさまざまな明るさを区別することができます (より技術的には、クロミナンスよりも輝度解像度の方が優れています)。そのため、色合いとは別に明るさを保存することで、24 ビットをさらに有効に活用できます。これは、YUV、YCrCb などの表現がやろうとしていることです。Y チャネルは色の全体的な明るさであり、他の 2 つのチャネルよりも多くのビットを使用します (または空間解像度が高くなります)。この方法では、RGB 強度の場合のように曲線を適用する必要は (常に) ありません。YUV は線形色空間であるため、Y チャネルの数値を 2 倍にすると、色の明度が 2 倍になりますが、RGB 色のように YUV 色を加算または乗算することはできません。
これであなたの質問に答えられると思いますので、簡単な歴史的なメモで終わります。sRGB が登場する前は、古い CRT には非線形性が組み込まれていました。ピクセルの電圧を 2 倍にすると、強度は 2 倍以上になります。モニターごとにどれだけ異なるか、このパラメーターはガンマと呼ばれていました。この動作は、明るい部分よりも暗い部分をより多く取得できることを意味するため便利でしたが、最初に調整しない限り、ユーザーの CRT で色がどの程度明るいかがわからないことも意味していました。ガンマ補正は、最初の色 (おそらく線形) を変換し、ユーザーの CRT のガンマに合わせて変換することを意味します。OpenGL はこの時代に生まれたため、その sRGB の動作が少し混乱することがあります。しかし、GPU ベンダーは現在、上で説明した規則に従って作業する傾向があります。テクスチャまたはフレームバッファーに 8 ビットの強度を格納する場合は sRGB であり、色を処理する場合はリニアです。たとえば、OpenGL ES 3.0 では、各フレームバッファとテクスチャに「sRGB フラグ」があり、オンにすると、読み取りおよび書き込み時に自動変換を有効にできます。sRGB 変換やガンマ補正を明示的に行う必要はまったくありません。