눈팅하는 게임개발자 블로그
OpenGL Advanced Lighting 6-2 Gamma Correction 본문
원문 사이트
learnopengl.com/Advanced-Lighting/Gamma-Correction
번역 사이트
gyutts.tistory.com/166?category=755809
Gamma Correction
scene의 모든 최종 fragment(pixel)값을 계산한 이후 해당 값이 곧바로 스크린에 출력되어야 한다.
디지털 이미징의 예전 대부분의 모니터는 브라운관(CRT) 모니터였다.
이 모니터는 압력 전압이 두배가 되어도 밝기가 두배가 되지는 않는다는 물리적 특성을 가지고 있다.
압력 전압을 두 배로 하면 모니터의 감마라고도 알려진 약 2.2의 지수 관계와 같은 밝기가 나타난다.
이는 사람이 밝기를 측정하는 방법과 비슷하게 발생한다.
밝기는 비슷한 (역)전력 관계로 표시된다.
이것이 의미하는 바를 더 잘 이해하기 위해 다음 그림을 살펴보자.
윗줄은 사람의 눈에 맞는 밝기 크기이다. 밝기를 두 배로 늘리면 일관성있는 차이로 두 배 밝은 것처럼 보인다.
그러나 빛의 물리적 밝기에 대해 이야기할 때 광원을 떠나는 광자의 양은 실제로 정확한 밝기를 표시한다.
하단 스케일에서 밝기를 두 배로 하면 정확한 물리적 밝기가 반환되지만,
인간의 눈이 밝기를 다르게 인식하므로 이상하게 보인다.
인간의 눈은 최상위 크기로 밝기 색상을 보는 것을 선호하기 때문에 (현대의)모니터는 출력 색상을 표시하는 데
원래의 밝기 색상을 최고 눈금의 비선형 밝기 색상에 매핑한다. (사람의 눈이 선호하는 밝기를 출력하기 위해)
이 모니터의 비선형 매핑은 실제로 사람의 눈에서 밝기를 더 잘 보이게 하지만,
그래픽을 렌더링할 때 응용 프로그램에서 구성하는 모든 색상 및 밝기 옵션은 모니터에서 인식하는 것에 기반한다.
따라서 모든 옵션은 실제로 비선형 밝기/색상 옵션이다.
아래의 그래프를 살펴보자.
점선은 선형 공간의 색상/밝기 값을 나타내고, 실선은 모니터가 출력하는 색상 공간을 나타낸다.
선형 공간에서 색상을 두 배로 하면 결과는 실제로 두 배가 된다.
예를 들어 빛의 색 벡터 L = (0.5, 0.0, 0.0)을 취하면 semi-drak red light가 된다.
선형 공간에서 이 빛을 두 배로 하면 그래프에서 볼 수 있듯이(1.0, 0.0, 0.0)이 된다.
그러나 정의한 색상이 모니터 디스플레이에 출력되어야 하기 때문에 그래프에서 확인할 수 있듯이
색상이 모니터에 (0.218, 0.0, 0.0)으로 표시된다.
문제가 발생하기 시작하는 곳은 다음과 같다.
선형 공간에서 진한 빨간색 빛을 두 번 사용하면 실제로 모니터에서 4.5배 이상 밝아지는 것이다.
이 강좌 까지는 선형 공간에서 작업하고 있다고 가정했지만, 실제로는 모니터의 출력 색상 공간에서 정의한 색상 공간에서
작업했으므로 구성한 모든 색상과 조명 변수는 실제로 올바르지 않지만 단지 모니터에 출력했다.
이런 이유로 일반적으로 조명 값을 밝게 설정해 대부분의 선형 공간 계산을 부정확하게 만든다.
또한, 모니터 그래프와 선형 그래프는 모두 같은 위치에서 시작하고 끝난다.
이는 디스플레이에 의해 어두워지는 중간 색상이다.
모니터의 디스플레이에 따라 색상이 구성되기 때문에 선형 공간의 모든 중간 계산이 물리적으로 올바르지 않다.
이는 아래 이미지에서 확인할 수 있듯이 고급 조명 알고리즘이 사용됨에 따라 점점 더 분명해진다.
Gamma correction을 사용하면 색상 값이 더 잘 작동하고, 어두운 영역이 어두워지므로 자세한 내용을 볼 수 있다.
전반적으로 작은 수정으로 훨씬 좋은 이미지 품질을 제공한다.
이 모니터의 감마를 올바르게 수정하지 않으면 조명이 엉망으로 보이고 아티스트는 사실적이고 보기 좋은 결과를 얻는 데
어려움을 겪는다, 해결 방법은 Gamma correction을 적용하는 것이다.
Gamma Correction
Gamma correction의 개념은 모니터에 표시하기 전에 모니터 감마의 역수를 최종 출력 색상에 적용하는 것이다.
이 섹션의 앞부분에 있는 감마 곡선 그래프를 다시 살펴보면, 모니터의 감마 곡선의 반대인 또 다른 점선이 있다.
이 역 감마 곡선을 선형 출력 색상에 곱해(밝게 만들어) 모니터에 색상이 표시되자마자 모니터의 감마 곡선이
적용되고 결과 색상이 선형이 된다.
기본적으로 중간 색상을 밝게해 모니터가 어두워지면 모두 균형을 유지한다.
다른 예를 들어보자면 다시 진한 붉은 색(0.5, 0.0, 0.0)을 가지고 있다고 가정하고,
이 색상을 모니터에 표시하기 전에 먼저 Gamma correction 곡선을 색상 값에 적용한다.
모니터에 표시되는 선형 색상은 대략 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)으로 표시된다.
gamma correction을 사용하면 응용 프로그램에서 선형으로 설정한대로 모니터가 최종적으로 표시된다.
감마 값 2.2는 대부분의 디스플레이의 평균 감마를 대략적으로 추측한 기본 감마 값이다. 이 감마 2.2의 결과로서 색 공간을 sRGB 색 공간이라고 부른다. 각 모니터에는 자체 감마 곡선이 존재하지만, 감마 값 2.2는 대부분의 모니터에서 좋은 결과가 출력된다. 이러한 이유로 게임에서는 플레이어마다 게임의 감마 설정을 변경할 수 있지만 모니터마다 약간 씩 다르다. |
scene에 gamma correction을 적용하는 방법에는 두 가지가 있다.
1. OpenGL의 내장 sRGB 프레임 버퍼 지원을 사용한다.
2. 또는 fragment shader에서 gamma correction을 직접 수행한다.
첫 번째 옵션은 아마도 가장 쉬운 방법이지만 통제할 수 있는 범위도 적다.
GL_FRAMEBUFFER_SRGB를 활성화함으로써 각 후속 드로잉 명령이 컬러 버퍼에 저장하기 전에 sRGB 색 공간의 색을 먼저
gamma correction을 수행해야 함을 OpenGL에게 알린다. sRGB는 대략 2.2의 감마에 해당하는 색 공간이며
대부분의 디바이스에 대한 표준이다.
GL_FRAMEBUFFER_SRGB를 활성화한 후 OpenGL은 각 프레임 쉐이더가 기본 프레임 버퍼를 포함해 모든 후속 프레임
버퍼로 실행된 후 gamma correction을 자동으로 수행한다.
GL_FRAMEBUFFER_SRGB를 활성화하는 것은 glEnable함수를 통해 호출한다.
glEnable(GL_FRAMEBUFFER_SRGB);
이제부터는 렌더링된 이미지가 gamma correction이 수행되고 하드웨어에 의해 완료된다.
이 접근법에서 염두에 두어야 할 것은 gamma correction이 선형 공간에서 비선형 공간으로 색상을 변형하므로
마지막 단계에서만 gamma correction을 해야 한다는 것이다.
최종 출력 전에 색상에 gamma correction을 수행하면 해당 색상의 모든 후속 작업이 잘못된 값으로 작동한다.
예를 들어, 여러 프레임 버퍼를 사용하는 경우 프레임 버퍼 간 중간 결과를 전달해 선형 공간에 그대로 두고
모니터에 보내기 전에 마지막 프레임 버퍼에서만 gamma correction을 적용할 수 있다.
두 번째 접근법은 더 많은 작업이 필요하지만 gamma 작업을 더 완벽하게 제어할 수 있다.
각 관련 쉐이더가 끝날 때 gamma correction을 적용해 최종 색상이 모니터로 전송되기 전에 gamma correction을 끝낸다.
void main()
{
// do super fancy lighting in linear space
[...]
// apply gamma correction
float gamma = 2.2;
FragColor.rgb = pow(fragColor.rgb, vec3(1.0/gamma));
}
코드의 마지막 줄은 fragColor의 각 개별 구성 요소를 이 fragment shader 실행의 출력 색상을
1.0 / gamma로 효과적으로 올린다.
이 방법의 문제점은 일관성을 유지하려면 최종 출력물에 기여하는 각 fragment shader에 gamma correction을 적용해야 하므로
여러 오브젝트에 대해 fragment shader가 12개 있는 경우 각각 모든 fragment shader에 gamma correction 코드를 추가해야 한다.
더 쉬운 해결책은 렌더링 루프에 사후 처리 단계를 도입하고, 사후 처리된 쿼드(post-processing 등이 적용된프레임 버퍼 등)
에 대해 gamma correction을 최종 단계로 적용해 한 번만 수행하면 된다.
해당 한 줄은 gamma correction의 기술적 구현을 나타낸다.
인상적인 것은 아니지만 gamma correction을 수행할 때 고려해야 할 몇가지 추가 사항이 있다.
sRGB texture
모니터는 sRGB 공간에 gamma가 적용된 색상을 항상 표시하므로 컴퓨터에서 그림을 그리거나 편집하거나 페인트 할 때마다
모니터에 표시되는 것을 기준으로 색상을 선택한다.
이는 효과적으로 생성하거나 편집한 모든 그림이 선형 공간이 아니라 sRGB 공간에 있음을 의미한다.
인식 밝기에 따라 화면에서 진한 빨간색을 두 배로 늘리면 빨간색 구성요소의 두 배가 되지는 않는다.
결과적으로 텍스처 아티스트는 sRGB 공간에서 모든 텍스처를 생성한다.
따라서 렌더링 애플리케이션에서와 같이 텍스처를 사용하면 이를 고려해야 한다.
gamma correction을 적용하기 전에는 텍스처가 sRGB 공간에서 잘 보이고 gamma correction을 하지 않았기 때문에
sRGB 공간에서 작업된 텍스처가 정확하게 그대로 표시되어서 이는 문제가 되지 않았다.
하지만 선형 공간에서 모든 것을 표시했으므로 다음 이미지와 같이 텍스처 색상이 해제된다.
텍스처 이미지가 너무 밝아서 실제로 두 번 gamma correction이 적용되었기 때문에 발생한다.
생각해 보면 모니터에 표시되는 이미지를 기반으로 이미지를 만들 때 이미지의 색상 값을 효과적으로
gamma correction을 적용해야 모니터에서 올바르게 보인다.
렌더러에서 gamma correction을 다시하기 때문에 이미지가 너무 밝아진다.
이 문제를 해결하기 위해 텍스처 아티스트가 선형 공간에서 작동하는지 확인해야 한다.
그러나 대부분의 텍스처 아티스트는 gamma correction이 무엇인지 알지 못하므로 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, data);
텍스처에 알파 컴포넌트를 포함시키려면 텍스처의 내부 포맷을 GL_SRGB_ALPHA로 지정해야 한다.
모든 텍스처가 실제로 sRGB 공간에 있는 것은 아니므로 sRGB 공간에서 텍스처를 지정할 때는 주의해야 한다.
확산 텍스처와 같은 오브젝트 채색에 사용되는 텍스처는 거의 항상 sRGB 공간에 있다.
반사 맵과 노말 맵과 같은 조명 매개 변수를 검색하는 데 사용되는 텍스처는 거의 항상 선형 공간에 있으므로
이러한 텍스처를 sRGB 텍스처로 구성하면 조명이 고장난다.
sRGB로 지정한 텍스처를 조심해야 한다.
sRGB 텍스처로 지정된 diffuse 텍스처를 사용하면 다시 볼 수 있는 시각적 출력을 얻을 수 있지만
이번에는 모든 것이 한번만 gamma correction이 적용된 상태이다.
Attenuation
gamma correction과 다른 특징을 보이는 것이 lighting attenuation이다.
실제 물리적 세계에서 조명은 광원으로부터의 제곱거리에 반비례하게 감쇠한다.
보통 간단하게 광원 광도가 아래의 제곱과의 거리에 비해 감소한다는 의미이다.
float attenuation = 1.0 / (distance * distance);
그러나 해당 감쇠 방정식을 사용할 때는 감쇠 효과는 항상 너무 강해 물리적으로 올바르게 보이지 않는 작은 반경을 조명에 제공한다.
그러한 이유 때문에 훨씬 더 많은 제어를 하는 기본 조명 강좌에서 논의한 것처럼
다른 attenuation 함수가 사용되었거나 성형 등가가 사용되었다.
float attenuation = 1.0 / distance;
linear equivalent(선형 등가)는 gamma correction이 없는 이차 변형보다 훨씬 더 그럴듯한 결과를 제공하지만
gamma correction을 활성화하면 선형 감쇠가 너무나도 약해 보인다.
물리적으로 올바른 이차 감쇠가 갑자기 더 나은 결과를 제공한다.
이를 다음 이미지에서 확인할 수 있다.
이 차이의 원인은 light attenuation 함수가 밝기를 변경한다는 것이고,
선형 공간에서 장면을 시각화하지 않았기 때문에 모니터에서 가장 잘 보이는 attenuation 기능을 선택했지만
물리적으로 올바르지는 않았다.
제곱 감쇠 함수를 생각해보자. gamma correction 없이 이 함수를 사용하면 attenuation 함수는 모니터에
표시될 때 효과적으로 (1.0/distance * distance)^2.2가 된다.
이는 gamma correction 없이 훨씬 더 큰 attenuation 효과를 만든다.
이는 또한 성형 등가가 감마 보정 없이 훨씬 더 의미가 있는 이유를 설명한다.
이는 실제로 (1.0/distnace)^2.2 = 1.0/distance^2.2가 되어 물리적으로 훨씬 더 유사하다.
기본 조명에서 설명한 고급 감쇠 함수는 gamma correction 장면에서 여전히 유용하다. gamma correction 장면에서는 정확한 attenuation을 훨씬 더 많이 제어할 수 있다. (물론 gamma correction 장면에서는 다른 매개변수가 필요하다.) |
요약하자면 gamma correction을 사용하면 선형 공간에서 렌더링을 작업/시각화할 수 있다.
선형 공간은 물리적인 세계에서 의미가 있기 때문에 대부분의 물리 방정식은 실제로 attenuation과 같은 좋은 결과를 제공한다.
조명이 고급화되면 될수록 gamma correction을 통해 보기 좋고 사실적인 결과를 얻는 것이 더 쉬워진다.
그래서 gamma correction을 하는 즉시 조명 매개 변수를 조정하는 것이 좋다.
'공부한거 > OpenGL' 카테고리의 다른 글
OpenGL Advanced Lighting 6-3-2 Point Shadows (0) | 2020.12.13 |
---|---|
OpenGL Advanced Lighting 6-3-1 Shadow Mapping (0) | 2020.12.13 |
OpenGL Advanced Lighting 6-1 Advanced Lighting (0) | 2020.12.12 |
OpenGL Advanced 5-11 Anti aliasing (0) | 2020.12.12 |
OpenGL Advanced 5-10 Instancing (0) | 2020.12.12 |