안드로이드 기기에서만 화면이 어둡게 나와서 안드로이드 쪽 sRGB 렌더링 관련 기능을 끄고 있었는데 이번에 고쳐보려고 관련 OpenGL spec 문서도 다시 읽어보고 깨달은 바가 있어 기록으로 남긴다.
크게 sRGB 컬러 스페이스를 고려한 렌더링은 두가지로 나눠서 보면 되는데, 첫째는 컬러 스페이스의 디코딩 (sRGB -> Linear) 과정이고, 둘째는 인코딩 (Linear -> sRGB) 과정이다.
우선 일반적인 이미지 파일은 sRGB 색공간에 있다고 가정하므로 이를 linearize 하는 과정 즉, 디코딩 과정이 필요하다. 입력 이미지는 텍스쳐화 되므로 단순히 OpenGL 에게 “이 텍스쳐는 sRGB 색공간을 사용하고 있으니 fetch 할때 알아서 linearize 해 주세요”. 하고 알려주기만 하면 된다. 텍스쳐의 internal format 에 sRGB 포맷이라고 넣어주면 끝. 그러면 shader 에서 texel 을 fetch 할때 자동으로 디코딩된 값을 얻는다. (sRGB 포맷은 채널 당 8비트를 넘지 않기 때문에 아마도 GPU 내부적으로는 연산 속도를 위해 테이블 매핑으로 되어있을 것으로 생각된다)
이제 디코딩된 컬러값을 프레임버퍼에 write 해주어야 하는데 HDR 렌더링시에는 float 버퍼이므로 상관없지만, 결국에는 채널당 8 비트를 사용하는 디폴트 프레임버퍼에 그려야 하므로 다시 인코딩 과정이 필요하다. 이때,
glEnable(GL_FRAMEBUFFER_SRGB)
로 인코딩을 켜주면 된다.
여기까지가 원래 Desktop OpenGL 에서 사용하던 방법이고, OpenGL ES 로 가면 약간 달라진다.
원래 spec 에는 프레임버퍼의 컬러 인코딩 방식을 쿼리할 수 있는데,
glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_BACK, GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING, &encoding)
AMD/Intel 계열 그래픽 카드에서는 디폴트 프레임 버퍼가 sRGB 로 나오지만 nVidia 에서는 Linear 로 나온다. Desktop OpenGL 에서는 이것과 상관없이 sRGB write 를 켜거나 끌 수 있다. 그러나 OpenGL ES 에서는 프레임 버퍼의 컬러 인코딩이 sRGB 이어야만 sRGB write 를 켜거나 끌 수 있다. (GL_EXT_sRGB_write_control 확장을 통해서 on/off 가 가능하다. 기본적으로는 항상 켜져있다) 즉, 프레임 버퍼의 컬러 인코딩이 Linear 라면 glEnable(GL_FRAMEBUFFER_SRGB) 는 아무런 영향을 주지 않는다.
여기서 iOS 와 Android 의 sRGB capable 프레임버퍼를 만드는 방식이 다른데.. 길어지니 그건 생략하고, 어쨋든 엔진에서 문제가 되는쪽은 Android 쪽이었다. (iOS 쪽은 제대로 작동 중이었다) spec 문서를 토대로 다시 문제의 원인을 쭈욱 살펴보는데 아무리 봐도 Android 쪽 버그로 보였다. 무슨 짓을 해도 sRGB write 가 계속 무시되고 있었다. 디폴트 프레임버퍼의 컬러 인코딩 방식이 Linear 인게 문제였다.
Android 소스를 찾아봤더니 프레임버퍼의 컬러 포맷에 alpha 비트가 0 이라면 sRGB 프레임버퍼 생성은 실패하도록 되어있더라. 결국 프레임버퍼에 alpha 비트를 추가하여 해결했다.
참고.