본문 바로가기

Game/Graphics

Learn OpenGL - Advanced Lighting : Gamma Correction

link : https://learnopengl.com/Advanced-Lighting/Gamma-Correction


Gamma Correction


 장면의 모든 최종 픽셀 색을 계산하자마자 모니터에 표시해야한다. 디지털 이미징의 옛날 대부분의 모니터는 브라운관 (CRT)


모니터였다. 이 모니터는 입력 전압이 두 배가 되어도 밝기의 두 배가 되지 않는다는 물리적 특성을 가지고 있다.


입력 전압을 두 배로 하면 모니터의 감마라고도 알려진 약 2.2의 지수 관계와 같은 밝기가 나타난다.


이것은 인간이 밝기를 측정하는 방법과 비슷하게 발생한다. 밝기는 비슷한 (역) 전력 관계로 표시된다.


이것이 무엇을 의미하는지 더 잘 이해하기 위해 다음 그림을 살펴보아라:


Linear encodings of display with and without gamma correction


 맨 윗줄은 사람의 눈에 맞는 밝기 크기이다. 밝기를 두 배로 늘리면 일관성 있는 차이로 두 배 밝은 것처럼 보인다.


그러나 빛의 물리적 밝기에 대해 이야기 할 때 광원을 떠나는 광자의 양은 실제로 정확한 밝기를 표시한다.


하단 스케일에서 밝기를 두 배로 하면 정확한 물리적 밝기가 반환되지만, 우리 눈이 밝기를 다르게 인식하므로 이상하게 보인다.



 인간의 눈은 최상위 크기로 밝기 색상을 보는 것을 선호하기 때문에 모니터는 출력 색상을 표시하는 데 전원 관계를 사용해 원래의


밝기 색상을 최고 눈금의 비선형 밝기 색상에 매핑한다. 기본적으로 이것이 더 좋아 봉니다.



 이 모니터의 비선형 매핑은 실제로 우리의 눈에서 밝기를 더 잘 보이게 하지만, 그래픽을 렌더링 할 때 우리가 응용 프로그램에서 구성하는


모든 색상 및 밝기 옵션은 모니터에서 인식하는 것에 기반한다. 따라서 모든 옵션은 실제로 비선형 밝기/색상 옵션이다. 아래의 그래프를 보자:


Gamme curves


 점선은 선형 공간의 색상/밝기 값을 나타내고, 실선은 디스플레이를 모니터하는 색상 공간을 나타낸다. 선형 공간에서 색상을 두 배로 하면


결과는 실제로 두 배가 된다. 예를 들어, 빛의 색 벡터 L¯ = (0.5, 0.0, 0.0)을 취하면 semi-dark red light가 된다.


선형 공간에서 이 빛을 두 배로 하면 그래프에서 볼 수 있듯이 (1.0, 0.0, 0.0)이 된다. 그러나 정의한 색상이 모니터 디스플레이에 출력되야하기


때문에 그래프에서 볼 수 있듯이 색상이 모니터에 (0.218, 0.0, 0.0)으로 표시된다. 문제가 발생하기 시작하는 곳은 다음과 같다.


선형 공간에서 진한 빨간색 빛을 두 번 사용하면 실제로 모니터에서 4.5배 이상 밝아진다!



 이 튜토리얼까지는 선형 공간에서 작업하고 있다고 가정했지만, 모니터의 출력 색상 공간에서 정의한 색상 공간에서 실제로 작업했으므로


구성한 모든 색상과 조명 변수는 실제로 올바르지 않지만 단지 모니터에 출력했다. 이러한 이유로 우리는 일반적으로 조명 값을 밝게 설정해


대부분의 선형 공간 계산을 부정확하게 만든다. 또한, 모니터 그래프와 선형 그래프는 모두 같은 위치에서 시작하고 끝난다.


그것은 디스플레이에 의해 어두워지는 중간 색상이다.



 모니터의 디스플레이에 따라 색상이 구성되기 때문에 선형 공간의 모든 중간 계산이 물리적으로 올바르지 않다. 아래 이미지에서 볼 수 있듯이


고급 조명 알고리즘이 사용됨에 따라 점점 더 분명해진다:


Example of gamma correction w/ and without on advanced rendering


 감마 보정을 사용하면 색상 값이 더 잘 작동하고, 어두운 영역이 어두워지므로 자세한 내용을 볼 수 있다. 전반적으로 작은 수정으로 훨씬


좋은 이미지 품질을 제공한다.



 이 모니터의 감마를 올바르게 수정하지 않으면 조명이 엉망으로 보이고 아티스트는 사실적이고 보기 좋은 결과를 얻는데 어려움을 겪는다.


해결 방법은 감마 보정을 적용하는 것이다.




Gamma correction


 감마 보정의 개념은 모니터에 표시하기 전에 모니터 감마의 역수를 최종 출력 색상에 적용하는 것이다. 이 섹션의 앞부분에 있는


감마 곡선 그래프를 다시 보면, 모니터의 감마 곡선의 반대인 또 다른 점선이 있다. 이 역 감마 곡선을 선형 출력 색상에 곱해 (밝게 만들어)


모니터에 색상이 표시되자마자 모니터의 감마 곡선이 적용되고 결과 색상이 선형이 된다. 기본적으로 중간 색상을 밝게해 모니터가 어두워지면


모두 균형을 유지한다.



 다른 예를 들어보자. 우리는 다시 진한 붉은 색 (0.5, 0.0, 0.0)을 가지고 있다고 가정해보자. 이 색상을 모니터에 표시하기 전에 먼저 감마 보정


곡선을 색상 값에 적용한다. 모니터에 표시되는 선형 색상은 대략 2.2의 배율로 크기가 조정되므로 역상은 1/2.2의 배율로 색상을 조정해야한다.


따라서 감마 보정된 암적색은 (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는 대부분의 디스플레이의 평균 감마를 대략적으로 추정하는 기본 감마 값이다. 이 감마 2.2의 결과로서의 색 공간을 sRGB 색 공간이라고 부른다. 각 모니터에는 자체 감마 곡선이 있지만, 감마 값 2.2는 대부분의 모니터에서 좋은 결과를 제공한다. 이러한 이유로 게임에서는 플레이어마다 게임의 감마 설정을 변경할 수 있지만 모니터마다 약간씩 다르다.

 장면에 감마 보정을 적용하는 방법에는 두 가지가 있다:


1) OpenGL의 내장 sRGB 프레임 버퍼 지원을 사용한다.


2) 또는 조각 쉐이더에서 감마 보정을 직접 수행한다.


 첫 번째 옵션은 아마도 갖강 쉬운 방법이지만 제어력이 떨어진다. GL_FRAMEBUFFER_SRGB를 활성화하면 각 후속 드로잉 명령이 컬러 버퍼에


저장하기 전에 sRGB 색 공간의 색을 먼저 감마 보정해야 함을 OpenGL에 알린다. sRGB는 대략 2.2의 감마에 해당하는 색 공간이며 대부분의


가정용 장치에 대한 표준이다. GL_FRAMEBUFFER_SRGB를 활성화 한 후 OpenGL은 각 프레임 쉐이더가 기본 프레임 버퍼를 포함해 모든 후속


프레임 버퍼로 실행된 후 감마 보정을 자동으로 수행한다.



 GL_FRAMEBUFFER_SRGB를 활성화하는 것은 glEnable을 호출하는 것처럼 간단하다:

glEnable(GL_FRAMEBUFFER_SRGB); 

 이제부터는 렌더링된 이미지가 감마 보정되고 하드웨어에 의해 완료되므로 완전히 자유롭다. 이 접근법에서 염두에 두어야 할 것은 감마 보정이


선형 공간에서 비선형 공간으로 색상을 변형하므로 마지막 단계에서만 감마 보정을 해야 한다는 것이다.


최종 출력 전에 색상을 감마 보정하면 해당 색상의 모든 후속 작업이 잘못된 값으로 작동한다. 예를 들어, 여러 프레임 버퍼를 사용하는 경우


프레임 버퍼간에 중간 결과를 전달해 선형 공간에 그대로 두고 모니터에 보내기 전에 마지막 프레임 버퍼에서만 감마 보정을 적용할 수 있다.



 두 번째 접근법은 더 많은 작업이 필요하지만 감마 작업을 완벽하게 제어 할 수 있다. 각 관련 쉐이더가 끝날 때 감마 보정을 적용해 최종 색상이


모니터로 전송되기 전에 감마 보정을 마친다:

void main()
{
    // do super fancy lighting 
    [...]
    // apply gamma correction
    float gamma = 2.2;
    FragColor.rgb = pow(fragColor.rgb, vec3(1.0/gamma));
}

 코드의 마지막 줄은 fragColor의 각 개별 색상 구성 요소를 이 조각 쉐이더 실행의 출력 색상을 1.0 / gamma로 효과적으로 올린다.



 이 방법의 문제점은 일관성을 유지하려면 최종 출력물에 기여하는 각 조각 쉐이더에 감마 보정을 적용해야하므로 여러 오브젝트에 대해


조각 쉐이더가 12개 있는 경우 각각에 감마 보정 코드를 추가해야한다. 더 쉬운 해결책은 렌더링 루프에 사후 처리 단계를 도입하고, 사후 처리된


쿼드에 대해 감마 보정을 최종 단계로 적용해 한 번만 수행하면 된다.



 이 한 줄은 감마 보정의 기술적 구현을 나타낸다. 모두 인상적인 것은 아니지만 감마 보정을 수행 할 때 고려해야 할 몇 가지 추가 사항이 있다.



sRGB texture


 모니터는 sRGB 공간에 감마가 적용된 색상을 항상 표시하므로 컴퓨터에서 그림을 그리거나 편집하거나 페인트 할 때마다 모니터에 표시되는


것을 기준으로 색상을 선택한다. 이것은 효과적으로 생성하거나 편집한 모든 그림이 선형 공간이 아니라 sRGB 공간에 있음을 의미한다.


귀하의 인식 밝기에 따라 화면에서 진한 빨간색을 두 배로 늘리면 빨간색 구성 요소의 두 배가 되지는 않는다.



 결과적으로 텍스처 아티스트는 sRGB 공간에서 모든 텍스처를 생성한다. 따라서 렌더링 애플리케이션에서와 같이 텍스처를 사용하면 이를


고려해야한다. 감마 보정을 적용하기 전에는 텍스처가 sRGB 공간에서 잘 보이고 감마 보정을 하지 않았기 때문에 sRGB 공간에서도


작업했으므로 텍스처가 정확하게 그대로 표시되어서 이것은 문제가 되지 않았다. 그러나 선형 공간에서 모든 것을 표시 했으므로 다음 이미지와


같이 텍스처 색상이 해제된다:


Comparrison between working in linear space with sRGB textures and linear-space textures


 텍스처 이미지가 너무 밝아서 실제로 두 번 감마 보정 되었기 때문에 발생한다. 생각해 보면 모니터에 표시되는 이미지를 기반으로


이미지를 만들 때 이미지의 색상 값을 효과적으로 감마 보정해야 모니터에서 올바르게 보인다. 렌더러에서 감마 보정을 다시하기 때문에


이미지가 너무 밝아진다.



 이 문제를 해결하기 위해 텍스처 아티스트가 선형 공간에서 작동하는지 확인해야한다. 그러나 대부분의 텍스처 아티스트는 감마 보정이


무엇인지 알지 못하므로 sRGB 공간에서 작업하는 것이 쉽다. 이는 아마도 선호되는 해결책이 아니다.



 다른 해결책은 색상 값에 대한 계산을 수행하기 전에 이러한 sRGB 텍스처를 선형 공간으로 다시 수정하거나 변형하는 것이다.


우리는 다음과 같이 할 수 있다:

float gamma = 2.2;
vec3 diffuseColor = pow(texture(diffuse, texCoords).rgb, vec3(gamma));

 그래도 sRGB 공간의 텍스처마다 이 작업을 수행하는 것은 상당히 번거로운 작업이다. 운 좋게도 OpenGL은 우리에게 GL_SRGB와 GL_SRGB_ALPHA


내부 텍스처 형식을 제공함으로써 문제에 대한 또 다른 해결책을 제시한다.



 이 두 가지 sRGB 텍스처 포맷을 사용해 OpenGL에서 텍스처를 생성하면 OpenGL은 사용하는 즉시 선형 공간에서 색상을 자동으로 교정하므로


모든 색상 값을 추출한 선형 공간에서 올바르게 작업 할 수 있다. 다음과 같이 텍스처를 sRGB 텍스처로 지정할 수 있다:

glTexImage2D(GL_TEXTURE_2D, 0, GL_SRGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image);  

 텍스처에 알파 컴포넌트를 포함시키려면 텍스처의 내부 포맷을 GL_SRGB_ALPHA로 지정해야한다.



 모든 텍스처가 실제로 sRGB 공간에 있는 것은 아니므로 sRGB 공간에서 텍스처를 지정할 때는 주의해야한다. 확산 텍스처와 같은 오브젝트 채색에


사용되는 텍스처는 거의 항상 sRGB 공간에 있다. 반사 맵과 노멀 맵과 같은 조명 매개 변수를 검색하는데 사용되는 텍스처는 거의 항상 선형


공간에 있으므로 이러한 텍스처를 sRGB 텍스처로 구성하면 조명이 고장난다. sRGB로 지정한 텍스처를 조심해라.



 sRGB 텍스처로 지정된 diffuse 텍스처를 사용하면 다시 볼 수 있는 시각적 출력을 얻을 수 있지만 이번에는 모든 것이 한 번만 감마 보정된다.



Attenuation


 감마 보정과 다른 점은 조명 감쇠이다. 실제 물리적 세게에서 조명은 광원으로부터의 제곱 거리에 반비례하게 감쇠한다.


보통의 영어에서는 간단하게 광원 강도가 아래의 제곱과의 거리에 비해 감소한다는 것을 의미한다:

float attenuation = 1.0 / (distance * distance);

 그러나 이 감쇠 방정식을 사용할 때 감쇠 효과는 항상 너무 강해 물리적으로 올바르게 보이지 않는 작은 반경을 조명에 제공한다.


그러한 이유 때문에 훨씬 더 많은 제어를 하는 기본 조명 튜토리얼에서 논의한 것처럼 다른 감쇠 함수가 사용되었거나 선형 등가가 사용되었다:

float attenuation = 1.0 / distance;  

 linear equivalent(선형 등가)는 감마 보정이 없는 이차 변형보다 훨씬 더 그럴듯한 결과를 제공하지만 감마 보정을 활성화하면 선형 감쇠가


너무 약해보인다. 물리적으로 올바른 이차 감쇠가 갑자기 더 나은 결과를 제공한다. 아래 이미지는 차이점을 보여준다:


Attenuation differences between gamma corrected and uncorrected scene.


 이 차이의 원인은 light attenuation 함수가 밝기를 변경한다는 것이고, 선형 공간에서 장면을 시각화하지 않았기 때문에 모니터에서 가장


잘 보이는 감쇠 기능을 선택했지만 물리적으로 올바르지는 않았다. 제곱 감쇠 함수를 생각해보자. 감마 보정없이 이 함수를 사용하면


감쇠 함수는 모니터에 표시 될 때 효과적으로 (1.0/distance2)2.2 가 된다. 이것은 감마 보정없이 훨씬 더 큰 감쇠 효과를 만든다.


이는 또한 선형 등가가 감마 보정없이 훨씬 더 의미가 있는 이유를 설명한다. 이 것은 실제로 (1.0/distance)2.2 = 1.0/distance2.2가 되어


물리적으로 훨씬 더 유사하다.

기본 조명에서 설명한 고급 감쇠 함수는 감마 보정 장면에서 여전히 유용하다. 감마 보정 장면에서는 정확한 감쇠를 훨씬 더 많이 제어 할 수 있다. (물론 감마 보정 장면에서는 다른 매개 변수가 필요하다)

 여기서 간단한 소스 코드를 찾을 수 있는 간단한 데모 장면을 만들었다. 스페이스 바를 누르면 감마 보정된 장면과 수정되지 않은 장면이


텍스처와 감쇠 등가물을 사용해 두 장면으로 전환된다. 가장 인상적인 데모는 아니지만 모든 기술을 실제로 적용하는 방법을 보여준다.



 요약하면 감마 보정을 사용하면 선형 공간에서 렌더링을 작업/시각화 할 수 있다. 선형 공간은 물리적인 세계에서 의미가 있기 때문에


대부분의 물리적 방정식은 실제로 실제 감쇠와 같은 좋은 결과를 제공한다. 조명이 고급화되면 할수록 감마 보정을 통해 보기 좋고 사실적인


결과를 얻는 것이 더 쉬워진다. 그래서 감마 보정을 하는 즉시 조명 매개 변수를 조정하는 것이 좋다.