一般的な画像はモニタに合わせてガンマ補正した非線形なデータになっています。
そのまま演算に用いると正しい結果が得られないため、事前にリニア値への
変換が必要になります。
同様に出力時は元のモニタ用の色空間に戻します。
・入力時 sRGB → Linear 変換
・出力時 Linear → sRGB 変換
OpenGL (2.1/3.0) や OpenGL ES 3.0 以降は標準である sRGB 色空間に対応しており、
リニア値との相互変換ができるようになっています。
変換が行われるのは上記のようにテクスチャの読み込みと、フレームバッファへの
書き込みのタイミングです。
●入力時
intenal format に sRGB 形式であることを指定できます。
テクスチャフェッチで同時に Linear に展開されます。
圧縮フォーマットも同様です。
// Linear glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA8, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, data ); glCompressedTexImage2D( GL_TEXTURE_2D, 0, GL_COMPRESSED_RGB8_ETC2, w, h, 0, size, data ); glCompressedTexImage2D( GL_TEXTURE_2D, 0, GL_COMPRESSED_RGB_S3TC_DXT1_EXT, w, h, 0, size, data ); // sRGB glTexImage2D( GL_TEXTURE_2D, 0, GL_SRGB8_ALPHA8, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, data ); glCompressedTexImage2D( GL_TEXTURE_2D, 0, GL_COMPRESSED_SRGB8_ETC2, w, h, 0, size, data ); glCompressedTexImage2D( GL_TEXTURE_2D, 0, GL_COMPRESSED_SRGB_S3TC_DXT1_EXT, w, h, 0, size, data );
変換は RGB のみで Alpha は含まれません。
OpenGL 4.x RADEON/GeForce/Intel HD 4000、OpenGL ES 3.0 Mali-T604 で
動作を確認しています。
OpenGL ES 2.0 で用いられていた GPU 固有の圧縮フォーマットには sRGB 形式が
定義されていないものがあります。
現在わかっている情報からまとめると下記の通り。
OpenGL ES 3.0 での sRGB ---------------------------------------- ETC1 あり(ETC2) ETC2 あり PVRTC 無し (PowerVR のみ) PVRTC2 無し (PowerVR のみ) ATITC 無し (Adreno のみ) ASTC あり S3TC(DXT1) あり
ETC1 に対応する sRGB フォーマットがありませんが、ETC2 が上位互換なので
GL_COMPRESSED_SRGB8_ETC2 で読み込むことが出来ます。
OpenGL ES 3.0 GPU は ETC2/EAC に対応しているので、SRGB 形式が必要なら
ETC2 を選択することになるかと思います。
フィルタリングに関与できないので厳密ではありませんが、
シェーダーで Linear に変換することも可能です。
ガンマ補正はデータ圧縮の一種といえるので、sRGB 8bit を展開すると
リニア値は 8bit 以上の精度が必要となります。
●出力時
フレームバッファが sRGB 形式で作られている場合、書き込み時に変換が行われます。
自分で作る場合は簡単で、Texture 同様に internal format に sRGB 形式を指定します。
// Renderbuffer glGenRenderbuffers( 1, &ColorBuffer ); glBindRenderbuffer( GL_RENDERBUFFER, ColorBuffer ); glRenderbufferStorage( GL_RENDERBUFFER, GL_SRGB8_ALPHA8, w, h ); glBindRenderbuffer( GL_RENDERBUFFER, 0 ); glBindFramebuffer( GL_DRAW_FRAMEBUFFER, FrameBuffer ); glFramebufferRenderbuffer( GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, ColorBuffer ); glBindFramebuffer( GL_DRAW_FRAMEBUFFER, 0 ); // Texture glGenTextures( 1, &ColorTexture ); glBindTexture( GL_TEXTURE_2D, ColorTexture ); glTexImage2D( GL_TEXTURE_2D, 0, GL_SRGB8_ALPHA8, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL ); glBindTexture( GL_TEXTURE_2D, 0 ); glBindFramebuffer( GL_DRAW_FRAMEBUFFER, FrameBuffer ); glFramebufferTexture2D( GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, ColorTexture, 0 ); glBindFramebuffer( GL_DRAW_FRAMEBUFFER, 0 );
OS が用意する Default Framebuffer の場合は wgl や egl を用いて
SRGB 対応のフォーマットを選んでおく必要があります。
ただし Windows 7/8 + RADEON/GeForce では、特に何もしなくても SRGB 形式となっており
glEnable( GL_FRAMEBUFFER_SRGB ) だけで意図した結果が得られるようです。
逆に Intel HD 4000 (9.18.10.3165) では、下記の方法で SRGB バッファを選択しても
Default Framebuffer での変換がうまく出来ませんでした。
wgl を使った選択手段は下記のとおり。
1. wgl extension の wglChoosePixelFormatARB() を取得
2. 必要なパラメータ(attribute) を指定して wglChoosePixelFormatARB() で候補を選ぶ
3. 得られた Config 値を SetPixelFormat() に渡す
static const int PixelFormat_Attr[]= { WGL_DRAW_TO_WINDOW_ARB, TRUE, WGL_SUPPORT_OPENGL_ARB, TRUE, WGL_DOUBLE_BUFFER_ARB, TRUE, WGL_PIXEL_TYPE_ARB, WGL_TYPE_RGBA_ARB, WGL_COLOR_BITS_ARB, 32, WGL_RED_BITS_ARB, 8, WGL_GREEN_BITS_ARB, 8, WGL_BLUE_BITS_ARB, 8, WGL_ALPHA_BITS_ARB, 8, WGL_DEPTH_BITS_ARB, 24, WGL_STENCIL_BITS_ARB, 8, WGL_ACCELERATION_ARB, WGL_FULL_ACCELERATION_ARB, WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB, TRUE, 0 }; int ChooseConfig() { int pixelformat= 0; if( !p_wglChoosePixelFormatARB ){ // extension の確認 return ChoosePixelFormat( hDC, &PixelFormat_Default ); } const unsigned int FORMAT_ARRAY_SIZE= 16; int FormatArray[FORMAT_ARRAY_SIZE]; unsigned int array_size= 0; if( !wglChoosePixelFormatARB( hDC, PixelFormat_Attr, NULL, FORMAT_ARRAY_SIZE, FormatArray, &array_size ) ){ return 0; } return FormatArray[0]; } void SetPixelFormat() { int pixelformat= ChooseConfig(); if( !pixelformat ){ // error } SetPixelFormat( hDC, pixelformat, NULL ); }
Intel HD 4000 でも自分で Framebuffer を用意した場合は
glEnable( GL_FRAMEBUFFER_SRGB ) の設定が結果に影響を与えていることを確認できます。
ただし OpenGL 4.0 では結果をそのまま出力する手段が見つかりませんでした。
glBlitFramebuffer() や Texture としての読み込みは、ソースが sRGB の場合に
リニア変換されてしまうので、元が Linear か sRGB か区別出来ないからです。
よって Default Framebuffer に転送する場合にシェーダーでもう一度再エンコードが
必要となります。
OpenGL 4.2 以降ならメモリイメージとして転送できるので、もっと良い方法が
あるかもしれません。
OpenGL ES 3.0 (Mali-T604) では GL_FRAMEBUFFER_SRGB による切り替えが
できないので、Framebuffer に対して正しく変換されているかどうか確認できませんでした。
SRGB はブレンドの結果に影響を与えるので、ブレンド結果を比べれば
機能していること確認できるのではないかと思っています。
入力側は容易でしたが、出力側は必ずしも統一した挙動になってくれないようです。
Framebuffer が HDR の場合リニアのまま格納できるので、互換性を考えると
最終的にシェーダーで変換するのが一番なのかもしれません。
// GLSL const float SCALE_0= 1.0/12.92; const float SCALE_1= 1.0/1.055; const float OFFSET_1= 0.055 * SCALE_1; float SRGBToLinear_F( float color ) { if( color <= 0.04045 ){ return color * SCALE_0; } return pow( color * SCALE_1 + OFFSET_1, 2.4 ); } float LinearToSRGB_F( float color ) { color= clamp( color, 0.0, 1.0 ); if( color < 0.0031308 ){ return color * 12.92; } return 1.055 * pow( color, 0.41666 ) - 0.055; } vec3 LinearToSRGB_V( vec3 color ) { return vec3( LinearToSRGB_F( color.x ), LinearToSRGB_F( color.y ), LinearToSRGB_F( color.z ) ); } vec3 SRGBToLinear_V( vec3 color ) { return vec3( SRGBToLinear_F( color.x ), SRGBToLinear_F( color.y ), SRGBToLinear_F( color.z ) ); }
関連エントリ
・OpenGL 3.x/4.x のシェーダー GLSL とメモリアクセス命令
・OpenGL 4.x Program Pipeline Object (Separate Shader Object)
・OpenGL 4.2/4.3 Shader Resource と Buffer API
・OpenGL ES 3.0/OpenGL 4.x Uniform Block
・OpenGL の各バージョンと GLSL の互換性
・OpenGL のエラー判定と OpenGL 4.3 Debug Output
・OpenGL ES 3.0/OpenGL 4.4 Texture Object と Sampler Object
・OpenGL ES 3.0 と Vertex Array Object