Gamma Correction
우리가 scene의 모든 최종 pixel colors들을 연산하자마자, 우리는 그것들을 monitor에 보일 것이다. digital imaging의 옛날 시절에, 대부분의 모니터들은 cathode-ray tube (CRT) 모니터였다. 이러한 모니터들은 input voltage가 밝기의 양을 두 배를 만들어내지 못했던 물리적 특성을 가졌다. input voltage를 두 배하는 것은 monitor의 gamma로서 알려진 대강 2.2의 지수 관계와 동일한 밝기를 만들어냈다. 이것은 동시에 또한 인간이 어떻게 밝기를 측정하는지와 부합한다. 밝기는 또한 유사한 (역의) 힘의 관계와함계 보여지기 때문이다. 이것이 무슨 의미인지 더잘 이해하기 위해서 다음의 그림을 보아라:
top line은 정확히 인간에 눈에 맞는 밝기 크기이다. 이것은 밝기를 두 배한 것이다. (예를들어 0.1에서 0.2)로 그리고 이것은 정말로 좋은 일관된 차이로 밝기가 두 배씩 되는 것처럼 보인다. 그러나, 우리가 빛의 물리적 밝기에 대해 말할 때, 예를들어 광원을 떠나는 광자의 양은 실제로 정확한 밝기를 보여준다. bottom scale에서 밝기를 두 배하는 것은 정확한 물리적 밝기를 반환하지만, 우리의 눈은 밝기를 다르게 인식하기 때문에 (어두운 색에서의 변화에 더욱 민감하다), 그것은 이상하게 보인다.
인간의 눈은 top scale에 따라 밝기의 색을 보는 것을 선호하기 때문에, 모니터들은 (오늘날 여전히) output colors들을 보여주는데 power relationship을 쓴다. 이것은 원래 physical brightness colors들이 top sacle에서 비선형 밝기 컬러에 매핑되도록 하기 위해서이다; 기본적으로 그것이 더 보기 좋기 때문이다.
이 모니터의 비선형 매핑은 참으로 밝기가 우리의 눈에서 더 잘보이게 만든다. 그러나, 그래픽스를 렌더링하는 것에 대해, 한 가지 문제점이 있다: 우리가 프로그램들에서 설정하는 모든 컬러와 밝기 옵션은 우리가 모니터로부터 인지하는 것으로부터 기반으로하고, 따라서 모든 옵션들은 실제로 비선형 밝기/컬러 옵션이라는 것이다. 밑에서 그래프를 한 번 봐보자.
그 점으로 된 선은 선형 공간에서의 컬러/빛의 값을 나타낸다. 그 solid line은 디스플레이를 monitors하는 color space를 나타낸다. 만약 우리가 linear space에서 한 컬러를 두 배 한다면, 그것의 결과는 참으로 그 값을 두 배 한 것이다. 예를들어, 빛의 컬러벡터가 L = (0.5, 0.0, 0.0)인 것을 예로들어보자. 그리고 그것은 semi-dark red light를 나타낸다. 만약 우리가 이 빛을 linear space에서 두 배 한다면, 그것은 너가 그래프에서 볼 수 있듯이 (1.0, 0.0, 0.0)이 될 것이다. 그러나, 우리가 정의했던 컬러들이 여전히 monitor display에 대해 output을 만들어야 하기 때문에, 그 컬러는 너가 그래프에서 볼 수 있듯이 모니터에서 (0.218, 0., 0.0)으로서 모니터에 보일 것이다. 여기에서 문제가 발생하기 시작한다: 우리가 linear space에서 dark-red light를 두 배 한다면, 그것은 실제로 그 모니터에서 4.5배 더 밝게 될 것이다!
이 튜토리얼까지 우리는 linear space에서 작동하고 있다고 가정했지만, 우리는 실제로 monitor의 output color space에 의해 정의된 color space에서 작동하고 있다. 그래서 우리가 설정한 모든 컬러와 lighting variables는 물리적으로 정확하지 않지만, 단순히 우리의 모니터에서는 옳아 보였다. 이 이유 때문에, 우리는 (그리고 예술가들은) 일반적으로 lighting values를 그것들이이 그래야 하는 것보다 더 밝게 설정한다. (모니터가 그것들을 더 어둡게 하기 때문에) 그리고 이것은 결과적으로 대부분의 linear-space calculations을 부정확하게 만든다. 또한 monitor graph와 linear graph가 둘 다 같은 위치에서 시작하고 끝나는 것에 주목해라; 디스플레이에의해 더 어두워지는 것은 중간 컬러들이다.
컬러들이 모니터의 디스플레이를 기반으로 설정되기 때문에, linear-space에서 모든 intermediate (lighting) calculations은 물리적으로 잘못된다. 이것은 좀 더 고급 라이팅 알고리즘이 사용되었을 때 더욱 명백해진다. 너가 아래에서 그 이미지를 볼 수 있다.
너는 gamma correction과 함께 (그 업데이트 딘) 컬러 값이 좀 더 좋게 함께 작동하고, 그 더 어두운 지역들이 덜 어둡고 따라서 좀 더 세부사항을 보여주는 것을 볼 수 있다. 전반적으로 오직 작은 수정만으로 좀 더 좋은 이미지 퀄리티가 나온다.
적절히 이 모니터 gamma를 수정하는 것 없이, 그 라이팅은 잘못되어 보이고, 예술가들은 현실적이고 좋게 보이는 결과를 얻는데 어려움을 겪을 것이다. 그 해결책은 gamma correction을 적용하는 것이다.
Gamma correction
gamma correction 아이디어는 모니터의 gamma에 역수를 최종 output color에 적용하는 것이다. 모니터에 보이기전에. gamma curve graph를 다시 보자면, 우리는 다른 그 모니터의 감마 커브에 역인 또 다른 dashed line을 본다. 우리는 linear output colors의 각각을 이 역의 gamma curve로 곱한다 (그것들을 더욱 밝게 만들며) 그리고 그 컬러들이 모니터에 보이자마자, 그 모니터의 감마 커브가 적용되고 그 최종 컬러들은 선형이 된다. 기본적으로 우리는 intermediate colors를 더 밝게 만든다. 모니터가 그것들을 어둡게 하자마자 그것을 균형있게 하기 위해서이다.
또 다른 예를 들어보자. 우리가 가령 다시 dark-red color (0.5, 0.0, 0.0)을 가진다고 해보자. 이 컬러를 모니터에 보이기전에, 우리는 처음에 gamma correction curve를 그 컬러값에 적용한다. 모니터에의해 보여지는 Linear colors들은 대강 2.2의 지수승으로 scaled 된다. 그래서 그 역이 그 컬러들을 1/2.2의 승으로 스케일링 하는 것을 요구한다. 그 gamma-corrected 된 dark-red color는 따라서 (0.5, 0.0, 0.0)^(1/2.2) = (0.5, 0.0, 0.0)^(0.45) = (0.73, 0.0, 0.0)이 된다. 그 보정된 컬러들은 그러고나서 모니터에게 주어지고, 결과적으로 그 컬러는
(0.73, 0.0, 0.0)^ 2.2 = (0.5, 0.0, 0.0)으로서 보여진다. 너는 gamma correction을 사용하여, 그 모니터가 이제 마지막으로 그 컬러들을 우리가 프로그램에서 linearly하게 설정한대로 보여주고 있다는 것을 알 수 있다.
Green Box
2.2의 gamma value는 대부분의 디스플레이의 평균 gamma를 대강 측정하는 기본 감마 값이다. 이 2.2 감마값의 결과로 color space는 sRGB color space로 불려진다. 각 모니터는 그것 자신의 gamma curves를 가지고 있지만, 2.2의 감마값은 대부분의 모니터들에게 좋은 결과를 준다. 이 이유로, 게임들은 종종 플레이어들이 모니터마다 조금씩 다르듯이 게임의 감마 세팅을 변경하는 것을 허용한다.
너의 scene에 gamma correction을 적용하는 두 가지 방법이 있다:
- OpenGL의 내장 sRGB framebuffer 지원을 사용
- 또는 fragment shaders에서 우리 자신의 gamma correction을 하는 것
첫 번째 옵션이 아마도 가장 쉽지만 또한 너에게 덜 통제력을 준다. GL_FRAMEBUFFER_SRGB를 활성화하여, 너는 OpenGL에게 각 차후의 drawing commands들이 처음에 sRGB color space로부터 컬러 버퍼에 저장하기 전에 컬러들을 감마 보정해야 한다고 말할 것이다. sRGB는 대강 2.2의 감마에 대응되는 color space이고, 대부분의 home devices에 대해 표준이다. GL_FRAMEBUFFER_SRGB를 활성화 한 후에, OpenGL은 자동적으로 각 fragment shader가 작동한 후에, 모든 subsequent framebuffers에 감마 보정을 수행한다. default framebuffer도 포함하여.
GL_FRAMEBUFFER_SRGB를 활성화 하는 것은 glEnable를 호출하는 것만큼 간단하다:
glEnable(GL_FRAMEBUFFER_SRGB);
지금부터 너의 렌더링 된 이미지들은, 감마 보정될 것이고, 이것이 하드웨어에 의해 처리되기 때문에, 그것은 완전히 무료이다. 너가 이 접근법으로 (그리고 다른 접근법으로) 명심해야 할 것은 감마 보정은 또한 선형 공간의 컬러들을 비선형 공간으로 바꾼다는 것이다. 그래서 너가 감마보정을 마지막 그리고 최종 단계에서 하는 것이 중요하다. 만약 너가 최종 output 전에 컬러들을 감마보정 한다면, 그러한 컬러들에 대해 모든 차후의 연산들은 부정확한 값으로 작동할 것이다. 예를들어, 만약 너가 여러 프레임 버퍼들을 사용한다면, 너는 아마도 프레임버퍼 사이에서 linear-space로 남아있도록 넘겨진 중간값을 원하고, 오직 그 모니터에 보내지기전에 gamma correction을 마지막 프레임버퍼만이 적용하기를 원한다.
두 번째 접근이 좀 더 일을 요구하지만, 또한 우리에게 gamma opertaions에 대해 완전한 통제를 준다. 우리는 gamma correction을 각 관계있는 fragment shader 작동의 끝에서 적용한다. 그래서 최종컬러들은 모니터에 보내지기전에, 감마보정 되어진다:
void main() { FragColor = texture(screenTexture, TexCoords); float gamma = 2.2; FragColor.rgb = pow(FragColor.rgb, vec3(1.0/gamma)); }
코드의 마지막 줄은 효과적으로 fragColor의 각 컬러 컴포넌트를 1.0/gamma의 지수승한다. 이것은 이 fragment shader 동작의 output color를 보정한다.
이 접근법이 가진 한 문제는 일관되게 하기 위해서, 너는 감마보정을 최종 output에 기여하는 각 fragment shader에 적용시켜야 한다는 것이다. 그래서 만약 너가 다양한 오브젝트들에 작동하는 여러 fragment shaders를 가진다면, 너는 이러한 쉐이더 각각에 감마보정 코드를 추가해야만 한다. 더 쉬운 솔루션은 너의 render loop에서 후처리 단계를 도입하는 것이고, gamma correction을 post-processed quad에서 너가 오직 한번 해야할 최종 단계로서 적용하는 것이다.
이러한 one-liners는 감마 보정의 기술적 구현을 나타낸다. 인상적이지 않지만, 감마보정을 할 때 너가 고려해야할 몇 가지 추가의 것들이 있다.
sRGB textures
모니터들을 항상 sRGB space에서 적용된 gamma로 컬러들을 보여주기 때문에, 너가 draw, eidt 또는 한 그림을 너의 컴퓨터에 그릴 때 마다, 너는 너가 모니터에서 보는 것으 ㄹ기반으로 컬러들을 고른다. 이것은 효과적으로 너가 만들거나 수정하는 모든 사진들이 linear space가 아닌 sRGB space에 있다는 것을 의미한다. 예를들어, 너의 인지된 밝기에 기반으로 스크린에 있는 dark-red color를 두 배하는 것은 그 red component를 두 배 하는 것과 같지 않다.
결과적으로 텍스쳐 예술가들은 sRGB 공간에서 모든 너의 텍스쳐들을 만든다. 그래서 만약 우리가 그러한 텍스쳐들을 사용한다면 그것들이 우리의 렌더링 프로그램에 있듯이) 우리는 이것을 고려해야만 ㅎ나다. 우리가 감마 보정을 고려하기 전에, 이것은 문제가 아니였다. 왜냐하면 텍스쳐들은 sRGB 공간에서 좋게 보이기 때문이다. 그리고 감마보정 없이, 우리는 또한 sRGB space에서 작업했다. 그래서 그 텍스쳐들을 정확히 그 자체로 보였따. 그리고 이것은 괜찮았다. 그러나, 우리가 linear space에서 모든 것을 보여주고있기 때문에, 그 텍스쳐 컬르들은 다음의 이미지에서 보여주듯이 조금 달라진다:
그 텍스쳐 이미지들은 너무 밝아진다. 이것은 실제로 그것들이 두 번 감마 보정되었기 때문에 발생한다. 생각해보아라. 우리가 우리가 모니터에서 보이는 것을 기반으로 한 이미지를 만들었을 때, 우리는 효과적으로 한 이미지의 컬러 값을 감마 보정한다. 그것이 모니터에 잘 보이도록 하기 위해서. 우리는 또 다시 renderer에서 감마 보정을 하기 떄문에, 그 이미지들은 너무 밝아 질 것이다.
이 문제를 해결하기 위해서, 우리는 텍스쳐 예술가들이 linear space에서 작업하도록 해야한다. 하지만, 텍스쳐 아티스트들은 심지어 감마보정이 무엇인지 모르고, sRGB 공간에서 작업하는 것이 더 쉽기 때문에, 이것은 선호되는 솔루션이 아니다.
다른 솔루션은 이러한 sRGB 텍스쳐들을 선형공간으로 재 보정 또는 변환하는 것이다. 그것들의 컬러 값에 대해 연산하기 전에. 우리는 이것을 다음과 같이 할 수 있다:
float gamma = 2.2; vec3 diffuseColor = pow(texture(diffuse, texCoords).rgb, vec3(gamma));
sRGB 공간에 있는 각 텍스쳐에 대해 이것을 하는 것은 그래도 꽤 문제가 있는 것이다. 운 좋게도 OpenGL은 우리에게 GL_SRGB와 GL_SRGB_ALPHA 내부 텍스쳐 포맷들을 주어서 우리의 문제에 대한 해결책을 준다.
만약 우리가 OpenGL에서 이러한 sRGB texture foramts으로 명시된 텍스쳐를 만든다면, OpenGL은 자동적으로 그 컬러들은 우리가 그것들을 사용하자마자, 선형 공간으로 보정할 것이다. 이것은 우리가 모든 컬러값들이 추출되는 linear-space에서 적절히 작업하게 할 수 있게 한다. 우리는 한 텍스쳐를 sRGB 텍스쳐로서 다음과 같이 명시한다:
glTexImage2D(GL_TEXTURE_2D, 0, GL_SRGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, imgae);
만약 너가 또한 너의 텍스쳐에서 alpha componets를 포함하기를 원한다면, 너는 텍스쳐의 내부 포맷을 GL_SRGB_ALPHA로서 명시해야 할 것이다.
너는 sRGB space에서 너의 텍스쳐를 명시하려할 때 주의해야 한다. 왜냐하면 모든 텍스쳐가 sRGB 공간에 있는 것은 아닐 것이기 때문이다. diffuse textures같이 오브젝트를 색칠하는데 사용되는 텍스쳐들은 거의 항상 sRGB에 있다. specular maps과 normal maps와 같은 lighting parameters를 가져오기 위해 사용되는 텍스쳐들을 거의 항상 linear space에 있다. 그래서 만약 너가 이러한 것들을 sRGB로서 명시한다면, 그 lighintg은 망가질 것이다. 너가 sRGB로 명시할 텍스쳐들을 주의해라.
우리의 diffuse textures가 sRGB textures로 명시한것과 함께, 너는 너가 기대한 visual output을 얻지만, 이번에 모든 것이 오직 한 번에 gamma corrected 된다.
Attenuation
감마보정과 다른 또 다른 것은 lighting attenuation이다. 실제 물리 세계에서, 빛은 광원으로부터 제곱 거리에 비례하여 역으로 감쇠한다. 일반 영어에서, 그것은 간단히 빛의 세기가 아래에서 처럼 광원으로 부터의 거리에 제곱으로 줄어든다는 것을 의미한다:
float attenuation = 1.0 / (distance * distance);
그러나, 이 attenuation equation을 사용할 때, attenuation effect는 항상 너무 강해서, 빛에게 물리적으로 좋게 보이지 않는 작은 반경을 준다. 그러한 이유로 다른 attenuation functions이 좀 더 통제력을 주고 또는 linear과 동등하게 사용된 우리가 basic lighting tutorials에서 다루었던 것처럼 사용된다.
float attenuation = 1.0 / distance;
그 1차식은 감마 보정 없이 그것의 2차식보다 좀 더 그럴듯한 결과를 준다. 그러나 우리가 감마 보정을 활성화 할 때, 그 일차 감쇠는 너무 약해보이고, 물리적으로 옳은 2차 감쇠가 값자기 더 좋은 결과를 준다. 그 아래의 이미지는 차이를 보여준다:
이 차이의 원인은 light attenuation 함수들이 밝기를 바꾸기 때문이다, 그리고 우리가 linear space에서 우리의 scene을 시각화하지 않을 떄, 우리는 우리의 모니터에서 가장 좋게 보이는 attenuation functions을 사용했다. 그러나 물리적으로 옳지 않앗다. 2차 감쇠 함수를 생각해라. 만약 우리가 감마 보정없이 이 함수를 사용하려 한다면, 그 감쇠 함수는 효과적으로 (1.0 / distance^2)^2.2 가 된다. 모니터에 보여질 때. 이것은 감마 보정없이 좀 더 큰 감쇠 효과를 만든다. 이것으 또한 왜 1차식이 감마 보정없이 좀 더 그럴듯한지에 대해 이유를 설명한다. 왜냐하면 이것은 효과적으로 (1.0/distance)^2.2 = 1.0 / distance^2.2가 되고, 그것의 물리적 식과 더 많이 비슷하다.
Green Box
우리가 basic lighting에서 다루었떤 좀 더 고급 attenuation function은 여정히 감마 보정된 scene에서 유용하다. 왜냐하면 그것은 정확한 감쇄에 대해 좀 더 많은 통제력을 주기 때문이다. (그러나물론 감마 보정된 scene에서 다른 파라미터들이 요구한다).
나는 너가 그것의 소스코드를 여기에서 볼 수 있는 간단한 demo scene을 만들었다. spacebar를 눌러서 우리는 감마보정 scene과 보정이 안된 scene을 스위치 할 수 있다. 두 scenes들은 그것들의 텍스쳐오 ㅏ감쇠를 사용한다. 그것은 가장 인상적인 데모는 아니지만, 그것은 모든 기법들을 어떻게 적용해야할지 보여준다.
요약해서, 감마 보정은 너가 선형 공간에서 너의 renders를 작업하고 시각화할 수 있게 해준다. 선형 공간이 물리적 세계에서 좀 더 말이 되기 때문에, 대부분의 물리적 함수들은 이제 실제 빛의 감쇠같은 좋은 결과를 준다. 너의 lighting이 좀 더 고급이 되어갈수록, 감마 보정과 함꼐 (현실적인) 좋게 보이는 결과를 얻게된다. 또한 종종 너가 감마 보정을 갖자마자 lighting parameters를 조정해보는 것이 충고되는 이유이다.
Additional resources
- 모든 코더들이 gamma에 대해 알아야 하는 것 : John Novak에 의해 쓰여진 감마 보정에 대해 잘 쓰여진 깊은 자료
- www.cambridgeincolour.com : gamma와 감마보정에 대해 더 많은 것
- blog.wolfire.com : 그래픽스 렌더링에서 감마 보정의 이득에 대한 David Rosen에 쓰여진 블로그 글
- renderwonk.com : 몇 가지 추가 실용적인 고려들
댓글 없음:
댓글 쓰기