HDR
밝기와 컬러값은 한 프레임버퍼에 저장될 때 기본적으로 0.0과 1.0사이에 있다. 이 처음에 그럴듯한 순수한 문장은 우리가 항상 빛과 컬러를 이 범위 어딘가에서 명시하게끔 만든다. 그리고 이것은 그것들이 scene에 맞게 할려고 한다. 이것은괜찮게 작동하고, 멋진 결과들을 주지만, 만약 우리가 합쳐서 총 1.0을 초과하는 수 많은 밝은 광원이 있는 특정히 밝은 지역에 걸어간다면 무엇이 발생할까? 그 답은 1.0 이상의 밝기 또는 color합을 가진 모든 fragments들은 1.0으로 clamped되고, 그것은 보기에 좋지않다:
많은 fragments들의 컬러값이 1.0으로 clamped되었기 때문에, 그 밝은 fragments들 넓은 지역에서 각각은 정확히 같은 하얀색을 가지고있고, 상당한 양의 디테일을 잃고, 가짜의 look을 주게 된다.
이 문제에 대한 해결책은 광원의 세기를 줄이고 너의 scene에 있는 어떠한 fragments들의 구역도 1.0보다 밝게 되지 않도록 하는 것이다; 이것은 너가 비현실적인 lighting parameters를 사용하도록 하기 때문에 좋은 솔루션이 아니다. 더 좋은 접근법은 컬러 값이 일시적으로 1.0을 초과하도록 하고 그것들을 마지막 단계로 0.0 ~ 1.0 사이의 원래 범위로 돌아오도록 변환하는 것이다. 하지만 디테일을 잃지 않게끔.
모니터들은 0.0 ~ 1.0의 범위의 컬러들을 보여주도록 제한되어 있따. 그러나 lighting equations에서는 그러한 제한이 없다. fragment colors가 1.0을 넘는 것을 허용하여, 우리는 high dynamic range (HDR)라고 알려진 작업하기에 가능한 컬러 값은 더 넓은 범위를 가지게 된다. high dynamic range와함꼐, 밝은 것들은 쉽게 밝아지고 어두운 것들은 정말 어둡고 디테일들은 둘 다에서 보일 수 있다.
High dynamic range는 원래 사진을 위해 사용되었다. 거기에서 사진가는 다양한 노출 레벨을 가진 같은 scene의 수 많은 사진을 찍는다. 이것은 수 많음 범위의 컬러 값을 포착하게 된다. 이러한 결합된 이미지들은 HDR image를 형성한다. 거기에서 수 많은 범위의 세부사항이 결합된 노출 정도 또는 그것이 보여지는 특별한 노출을 기반으로 보여질 수 있다. 예를들어, 아래에 있는 이미지는 낮은 노출도로 (창문을 보아라) 밝게 비춰진 지역의 많은 세부사항을 보여주지만, 이러한 세부사항은 높은 노출도에서는 사라진다. 그러나 높은 노출도는 이제 이전에 보이지 않았던 더 어두운 영역의 수 많은 세부사항을 보여준다.
이것은 또한 어떻게 인간의 눈이 작동하는 가와 유사하고, high dynamic range rendering의 기반이 된다. 작은 빛이 있을 때, 인간의 눈은 그 것 자체에 적응한다. 그래서 더 어두운 부분은 더 잘 보이고, 밝은 부분에 대해서 유사하게 된다. 인간의 눈은 scene의 밝기를 기반으로 자동 노출 slider를 가지고 있다.
High dynamic range rendering은 이렇게 작동한다. 우리는 scene의 어둡고 밝은 세부사항의 넓은 범위를 모아 렌더링하기 위해 더 넓은 범위의 컬러 값을 허용한다. 그리고 끝에서, 우리는 모든 HDR values를 다시 low dynamic range (LDR) [0.0, 1.0]으로 변환한다. HDR values에서 LDR values로 변환하는 것의 과정은 tone mapping이라고 불리고, 변환 과정 동안에 대부분의 HDR 세부사항을 보존하는 것을 목표로 하는 수 많은 tone mapping 알고리즘 콜렉션이 존재한다. 이러한 tone mapping 알고리즘은 종종 개별적으로 어둡거나 밝은 지역을 선호하는 exposure parameter를 포함한다.
real-time rendering에 대해서, high dynamic range는 우리가 [0.0, 1.0]의 범위의 LDR range를 초과하게 하고 좀 더 디테일 보존하도록 할 뿐만 아니라, 우리에게 그것들의 실제 강도에 의한 광원의 강도를 명시할 수 있는 능력을 준다. 예를들어, 태양은 flashlight같은 것보다 훨씬 더 높은 강도를 가진다. 그래서 태양을 그렇제 설정하는 것이 왜 안되는가 (10.0의 diffuse brightness 같이) 이것은 우리에게 좀 더 적절히 scene의 lighting을 좀 더 현실적인 lighting parameters를 가지고 설정하도록 하게 해준다. 그 현실적인 lighting parameter는 LDR rendering으로는 가능하지 않을 것이다. 그것들이 직접적으로 1.0으로 clamped되기 때문이다.
모니터들은 오직 0.0과 1.0 사이 범위의 컬러들을 보여주기 때문에, 우리는 현재 HDR 컬러들은 다시 모니터의 범위로 변환할 필요가 있다. 간단히 그 컬러들을 간단한 평균갑으로 재변환하는 것은 여전히 우리에게 좋지 않다. 더 밝은 지역들이 더 우세하기 때문이다. 그러나 우리가 할 수 있는 것은 HDR values를 LDR로 변환하는 우리에게 scene의 밝기에 대해 완전히 통제력을 주는 다른 방정식/or curve를 사용하는 것이다. 이것은 tone mapping으로 이전에 표기된 과정이고 HDR rendering의 마지막 단계이다.
Floating point framebuffers
HDR rendering을 구현하기 위해, 우리는 컬러 값들이 각 fragment가 작동한 후에 clamped 되는 것을 방지할 방법이 필요하다. frambuffers는 그것들의 colorbuffer의 internal format으로서 normalized fixed-point color format (GL_RGB같은)을 사용할 때, OpenGL은 프레임버퍼에 값을 저장하기 전에 자동으로 0.0 ~ 1.0 사이의 값으로 clamp한다. 이 연산은 대부분의 framebuffer foramts의 유형에 유효하다. 확장된 범위 값을 위해 사용되는 floating point formats을 제외하고.
한 프레임버퍼의 컬러버퍼의 내부 포맷이 GL_RGB16F, GL_RGBA16F, GL_RGB32F 또는 GL_RGBA32F로 명시된다면, 그 프레임버퍼는 기본 0.0 ~ 1.0 범위 밖의 floating point values를 저장할 수 있는 floating point framebuffer로 알려지게 된다. 이것은 HDR rendering에 완벽하다.
floating point framebuffer를 만들기 위해, 우리가 바꿀 필요가 있는 유일한 것은 그것의 colorbuffer의 internal format parameter이다:
glBindTexture(GL_TEXTURE_2D, colorBuffer);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGBA, GL_FLOAT, NULL);
OpenGL의 디폴트 프레임버퍼는 (기본적으로) 8 bit color component를 취한다. color component마다 32비트를 가진 floating point framebuffer로 (GL_RGB32F or GL_RGBA32F를 사용할 때), 우리는 컬러 값을 저장하는 데 네 배 이상의 더 많은 메모리를 사용한다. 32 bits는 만약 너가 높은 수준의 정확도가 필요하지 않다면 정말 필수적이지 않기 때문에, GL_RGBA16F를 사용하는 것이 충분할 것이다.
floating point colorbuffer를 framebuffer에 붙여서, 우리는 이제 scene을 컬러값이 0.0 ~ 1.0사이로 calmped되지 않을 것을 아는 이 framebuffer에 렌더링할 수 있다. 이 튜토리얼의 예제 데모에서, 우리는 처음에 lighted scene을 floating point framebuffer에 렌더링하고, 그러고나서 framebuffer의 colorbuffer를 screen-filled quad에 보여니다; 그것은 이것처럼 보일 것이다:
여기에서 scene의 컬러 값은 floating point colorbuffer에 채워진다. 그리고 그것은 어떤 임의의 컬러 값을 포함하는데, 아마 1.0을 초과할 것이다. 이 튜토리얼에서 간단한 데모 scene은 4개의 point lights가 있는 터널같이 역할을 하는 크게 펼쳐진 큐브로 만들어졌다. 그리고 그 빛 하나는 터널의 끝에서 위치하여 매우 밝게 빛난다:
floating point framebuffer에 렌더링하는 것은 우리가 보통 framebuffer에 렌더링하는 것과 정확히 같다. 새로운 것은, floating point colorbuffer texture가 부착된 최종 2D quad를 렌더링하는 hdrShader의 fragment shader이다. 간단히 절단하는 fragmetn shader를 정의해보자:
여기에서 우리는 직접적으로 floating point colorbuffer를 샘플링하고, 그것의 컬러값을 fragment shader의 output으로서 사용한다. 그러나, 그 2D quad의 output은 직접적으로 기본 framebuffer로 렌더링 되기 때문에, 모든 fragment shader의 output values는 0.0 ~ 1.0사이로 calmped 된다. 비록 우리가 1.0을 초과하는 floating point color texture의 몇 가지 값을 갖더라도.
터널의 끝에서 강렬한 빛의 값이 1.0으로 clamped 된다는 것이 명백하다. 그것의 큰 부분이 완전히 하얗게 되기 때문에. 이것은 효과적으로 1.0을 초과하는 프로세스의 모든 라이팅 디테일들을 ㅇ맇는다. 우리는 직접적으로 HDR values를 LDR values로 변환하기 때문에, 그것은 마치 우리가 첫 번째 장소에서 어떠한 HDR로 활성화하지 않은 것처럼 보인다. 우리가 이것을 고치기 위해 할 필요가 있는 것은, 모든 floating point color values를 어떤 디테일도 손실하지 않고, 0.0 ~ 1.0의 범위로 변환시키는 거싱다. 우리는 tone mapping이라는 프로세스를 적용할 필요가 있다.
Tone mapping
Tone mapping은 floating point color values를 예상되는 LDR이라고 알려진 [0.0, 1.0]의 범위로 너무 많은 디테일을 손실하지 않고 변환하는 프로세스이다. 이것은 종종 특정한 스타일이 있는 컬러 균형과 함께 온다.
가장 간단한 tone mapping은 Reinhard tone mapping으로 알려져있고, 그것은 전체 HDR color values를 LDR color values로 나누는 것이다. 이것은 그것들을 균등하게 균형을 맞춘다. Reinhard tone mapping algorithm은 균일하게 모든 밝기 값을 LDR로 퍼뜨린다. 우리는 Reinhard tone mapping을 이전 fragment shader에 포함시키고 또한 좋은 측정을 위해 gamma correction을 더할 수 있다. (SRGB textures를 사용하는 것을 포함해서):
void main() { const float gamma = 2.2; vec3 hdrColor = vec3(texture(screenTexture, TexCoords)); // reinhard tone mapping vec3 mapped = hdrColor / (hdrColor + vec3(1.0)); // gamma correction mapped = pow(mapped, vec3(1.0/gamma)); FragColor = vec4(mapped, 1.0); }
Reinhard tone mapping을 적용해서, 우리는 더 이상 우리의 scene에서의 밝은 부분에서 어떠한 디테일도 잃지 않는다. 그것은 다소 더 밝은 지역을 선호하는 경향이 있고, 더 어두운 부분이 덜 디테일해지고 덜 분간되게 만든다:
여기에서 너는 또 다시 터널의 끝에서 세부사항을 볼 수 있다. 이 상대적으로 간단한 tone mapping algorithm으로 우리는 적절히 floating point framebuffer에 저장된 HDR values의 전체 범위를 볼 수 있다. 이것은 세부사항을 잃는 것 없이 scene의 lighting에 대한 정확한 통제력을 준다.
Green Box
우리는 또한 우리의 lighting shader의 끝에 tone map을 직접적으로 할 수 있다. floating point framebuffer를 필요하지 않고! 그러나, scens이 점점 더 복잡해져감에 따라, 너는 자주 중간 HDR 결과를 floating point buffers로서 저장할 필요성을 발견하게 될 것이다. 그래서 이것이 좋은 연습이다.
또 다른 tone mapping의 흥미로운 사용은 노출 파라미터의 사용이다. 너는 아마도 도입부로부터 HDR 이미지들이 다른 노출 수준에서 보이는 많은 세부사항을 포함한다는 것을 기억할지도 모른다. 만약 우리가 낮과 밤의 cycle을 갖는 scene을 가진다면, daylight에서 더 낮은 노출을 사용하고, night time에는 더 높은 노출을 사용하는 것이 말이 된다. 이것은 인간의 눈이 적응하는 방식과 유사하다. 그러한 노출 파라미터로, 그것은 우리가 다른 조명 상황하에서, 낮과 밤 둘 다에 작동하는 lighting parameters를 설정하게 하는 것을 허용한다. 왜냐하면 우리는 오직 노출 파라미터만 바꾸기 때문이다.
상대적으로 간단한 exposure tone mapping algorithm은 다음처럼 보인다:
void main() { const float gamma = 2.2; const float exposure = 0.1; vec3 hdrColor = vec3(texture(screenTexture, TexCoords)); // Exposure tone mapping vec3 mapped = vec3(1.0) - exp(-hdrColor * exposure); // gamma correction mapped = pow(mapped, vec3(1.0 / gamma)); FragColor = vec4(mapped, 1.0); }
여기에서 우리는 exposure uniform을 1.0을 기본으로 정의했다. 그리고 우리가 HDR 컬러 값의 어둡거나 밝은 영역에 좀 더 집중하고 싶은지를 좀 더 정확히 명시하도록 한다. 예를들어, 높은 노출 값으로, 그 터널의 더 어두운 부분은 상당히 더 세부사항을 보여준다. 대조적으로 낮은 노출은 크게 어두운 부분의 세부사항을 제거하지만, 우리가 scene의 밝은 지역의 세부사하응ㄹ 더 보도록 한다. 다양한 노출도로 터널을 보기 위해서 아래의 이미지를 봐보자:
이 이미지는 명백히 high dynamic range rendering의 장점을 보여준다. 노출도를 바꿔서, 우리는 우리의 scene의 많은 세부사항을 얻는다. 이것은 그렇지 않다면 low dynamic range rendering으로는 잃어질 것이였다. 터널의 끝을 예를들어 보자. 일반적인 노출도로 그 wood structure는 간신히 보인다, 그러나 낮은 노출도로 그 세부적인 나무 패턴이 명백히 보인다. 그 같은 것이 높은 노출도일 때 더 잘 보이는 가까이에 있는 나무 패턴에도 적용된다.
너는 그 데모의 소스코드를 여기에서 볼 수 있다.
More HDR
보여진 두 개의 tone mapping 알고리즘은 (좀 더 고급의) tone mapping algorithms의 큰 collections 중의 몇 개일 뿐이다. 거기에는 각각 그것들의 장단점이 있다. 몇 가지 tone mapping algorithms은 다른 것들보다 어떤 컬러/강도를 선호한다. 그리고 몇 가지 알고리즘은 낮은 노출도 컬러와 높은 노출도 컬러 둘 다를 동시에 보여준다. 좀 더 컬러풀하고 디테일한 이미지를 만들어내기 위해서이다. 또한 automatic exposure adjustment 또는 eye adaptation 기법이라고 알려진 techniques의 모음이 있다. 이것은 이전 프레임에서 scene의 밝기를 결정하고 어두운 지역에서는 scene이 더 밝아지고 밝은 지역에서는 더 어둡게 하도록 해서 인간의 눈을 따라하여 (느리게) exposure parameter를 적응시키는 기법이다.
HDR rendering의 진짜 이득은 정말로 heavy한 라이팅 알고리즘을가진 크고 복잡한 scenes에서 그것 자체를 보여준다는 것이다. 가르치는 목적으로 그러한 복잡한 데모 scene을 만드는 것은 어렵기 때문에, 그것을 접근가능하게 두면서, 튜토리얼의 데모 scene은 작고 디테일이 부족하다. 그 scene은 상대적으로 간단할지라도, HDR의 장점 몇몇을 보여준다: 어떠한 디테일들도 high and dark 지역에서 잃어지지 않는다. 그것들이 tone mapping으로 다시 얻어지기 때문이다. 수 많은 빛들이 더해도 calmped regions를 만들지 않고, light values는 그것들의 원래 밝기 값으로 명시될 수 있따. LDR 값에 의해 제한되지 않고. 게다가, HDR rendering은 또한 몇 가지 흥미로운 효과들을 좀 더 그럴듯하고 현실적으로 만든다; 이러한 효과들 중 하나는 우리가 다음 튜토리얼에서 이야기할 bloom이다.
Additional resources
- HDR rendering은 bloom이 적용되지 않는다면 어떤 이득을 갖는가? : HDR rendering의 장점을 묘사하는 훌륭하고 긴 답변의 특징을 가진 stackexchange question
- tone mapping이 무엇인가? 그것은 HDR과 어떻게 관련이 있나? : tone mapping을 설명하는 훌륭한 레퍼런스 이미지를 가진 또 다른 흥미로운 질문.
=========================================
Gamma Correction과 후처리에 함께 반드시 들어가야할 HDR이다. 위의 추가 자료의 이미지를 보면 HDR로 이미지가 우리 눈에 정말 보기 편하고 사실적으로 묘사되는 것을 볼 수가 있다.
PBR까지 끝나게되면, 프로그램의 loop 단계를 나누고 각 stage마다 어떤 기법을 넣어서 scene의 디테일을 살릴 수 있는지 정리를 하고, 클래스들의 구조를 짜서 해야겠다.
댓글 없음:
댓글 쓰기