본문 바로가기

Game/Graphics

LearnOpenGL - Advanced Lighting : HDR

link : https://learnopengl.com/Advanced-Lighting/HDR


HDR(High Dynamic Range)


 기본적으로 밝기와 색상 값은 프레임 버퍼에 저장 될 때 0.0에서 1.0 사이로 고정된다. 이것은 처음에는 겉으로 보기에 문제가


없어보이지만, 우리는 이 범위의 어딘가에서 항상 빛과 색채 값을 지정해 장면에 적합하도록 했다. 이것은 괜찮았고 좋은 결과를 줬다.


그러나 합계가 1.0을 초과하는 여러 개의 밝은 광원을 사용해 특별히 밝은 지역을 보면 어떻게 될까?


1.0 이상의 밝기 또는 색상 합계를 갖는 모든 조각이 1.0으로 고정되어 보기 흉하게 바뀐다:


Color values clamped in bright areas


 많은 수의 단편 색상 값이 1.0으로 클램핑되기 때문에 각 밝은 단편은 넓은 영역에서 정확히 동일한 흰색 색상을 가지므로


많은 양의 디테일을 잃어버리고 가짜 모양을 갖게 된다.



 이 문제의 해결 방법은 광원의 강도를 줄이고 장면의 파편 영역을 1.0보다 밝게 만드는 것이다. 이는 비현실적인 라이팅 매개 변수를


사용해야하므로 좋은 해결책은 아니다. 보다 나은 방법은 색상 값이 일시적으로 1.0을 초과하도록 허용하고 최종 단계로 원래의 범위인


0.0과 1.0으로 다시 변환하지만 세부 사항은 잃지 않는 것이다.



 모니터는 0.0과 1.0의 범위에서 색상을 표시하도록 제한되지만 조명 방정식에는 이러한 제한이 없다. 조각 색상을 1.0 이상으로


허용함으로써 HDR (높은 동적 범위)으로 작동 할 수 있는 색상 값의 범위가 훨씬 넓어졌다. HDR을 사용하면 밝고 선명한 화면을,


어두운 화면은 정말 어둡고 세부적인 화면을 모두 볼 수 있다.



 HDR은 원래 사진가가 노출 범위가 다른 동일한 장면을 여러 장 찍고 넓은 범위의 색상 값을 캡처하는 용도로만 사용되었다.


이 결합된 이미지는 HDR 이미지를 형성하며 결합된 노출 수준 또는 특정 노출을 기준으로 광범위한 세부 묘사가 표시된다.


예를 들어, 아래 이미지는 노출이 적은 밝은 조명 영역에서 세부 사항을 많이 보여주지만, 이러한 세부 사항은


높은 노출로 사라진다. 그러나 노출이 높으면 이전에는 볼 수 없었던 어두운 영역에서 많은 양의 세부 묘사가 나타난다.


HDR image showing multiple exposure levels and their respective details


 이는 인간의 눈이 어떻게 작동하는지, 그리고 높은 동적 범위 렌더링의 기초와도 매우 유사하다. 빛이 거의 없을 때 인간의 눈은 스스로


적응하므로 어두운 부분은 밝은 영역에서 더 잘 보이고 유사하게 보이며 인간의 눈에는 장면의 밝기에 따라 자동 노출 슬라이더가 있다.



 HDR은 이와 비슷하게 작동한다. 훨씬 더 넓은 범위의 색상 값을 사용해 장면의 어두운 부분과 밝은 부분을 광범위하게 수집할 수 있으며


끝으로 모든 HDR 값을 [0.0, 1.0]의 낮은 동적 범위 (LDR)로 다시 변환한다. HDR 값을 LDR 값으로 변환하는 이 프로세스를 톤 매핑이라고


하며, 변환 프로세스 중에 대부분의 HDR 세부 정보를 보존하는 것을 목표로 하는 많은 톤 매핑 알고리즘 컬렉션이 존재한다.


이러한 톤 맵핑 알고리즘은 어두운 영역 또는 밝은 영역을 선택적으로 선호하는 노출 매개 변수를 종종 포함한다.



 리얼 타임 렌더링의 경우 다이내믹 레인지가 높기 때문에 [0.0, 1.0]의 LDR 범위를 초과할 수 있고, 세부 사항을 보존할 수 있을 뿐만 


아니라 실제 강도로 광원의 강도를 지정할 수 있다. 예를 들어, 태양은 손전등과 같은 것보다 훨씬 높은 강도를 가지므로 태양을 그런


식으로 구성하는 것은 좋지 않다. (예: 10.0의 확산 밝기) 이를 통해 보다 현실적인 라이팅 매개 변수를 사용해 장면의 라이팅을 보다


적절하게 구성 할 수 있다. LDR 렌더링에서는 가능하지 않지만 직접 1.0으로 클램핑된다.



 모니터는 0.0과 1.0 사이 범위의 색상만 표시하므로 현재 동적 범위가 넓은 색상 값을 모니터 범위로 다시 변환해야한다.


단순한 평균으로 색상을 다시 변형하는 것만으로도 밝은 영역이 훨씬 더 우세해지기 때문에 좋지 않다.


그러나 우리가 할 수 있는 것은 HDR 값을 다시 LDR로 변환해 장면의 밝기를 완벽하게 제어 할 수 있는 다른 방정식 또는 


곡선을 사용하는 것이다. 이것은 이전에 톤 맵핑 및 HDR 렌더링의 마지막 단계로 표시된 프로세스이다.



Floating point framebuffers


 HDR를 구현하려면 각 조각 쉐이더가 실행 된 후에 컬러 값이 클램프 되는 것을 막기 위한 방법이 필요하다.


프레임 버퍼가 컬러 버퍼의 내부 형식으로 정규화된 고정 소수점 색상 포맷을 사용하는 경우 OpenGL은 프레임 버퍼에 값을


저장하기 전에 0.0에서 1.0 사이의 값을 자동으로 고정한다. 이 연산은 확장된 범위의 값에 사용되는 부동 소수점 형식을 제외하고


대부분의 형식의 프레임 버퍼 형식에 적용된다.



 프레임 버퍼의 컬러 버퍼 내부 형식이 GL_RGB16F, GL_RGBA16F, GL_RGB32F, GL_RGBA32F로 지정되면 프레임 버퍼는 기본 범위인


0.0과 1.0 이외의 부동 소수점 값을 저장할 수 있는 부동 소수점 프레임 버퍼로 알려져 있다. HDR을 렌더링하기에 적합하다.



 부동 소수점 프레임 버퍼를 만드려면 colorbuffer의 내부 형식 매개 변수를 변경해야한다:

glBindTexture(GL_TEXTURE_2D, colorBuffer);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGBA, GL_FLOAT, NULL);  

 기본적으로 OpenGL의 기본 프레임 버퍼는 색상 구성 요소당 8비트를 차지한다. GL_RGB32F 또는 GL_RGBA32F를 사용할 때


색상 구성 요소당 32비트의 부동 소수점 프레임 버퍼를 사용하면 색상 값을 저장하는데 4배 더 많은 메모리가 사용된다.


GL_RGBA16F를 사용해 높은 수준의 정밀도가 필요하지 않으면 32비트가 필수적이지 않다.



 부동 소수점 색상 버퍼를 프레임 버퍼에 연결하면 색상 값이 0.0과 1.0 사이에 고정되지 않는다는 것을 알고 이 프레임 버퍼로 장면을


렌더링 할 수 있다. 이 튜토리얼의 예제 데모에서는 먼저 조명이 적용된 장면을 부동 소수점 프레임 버퍼에 렌더링 한 다음


screen-filled 쿼드에 프레임 버퍼의 색상 버퍼를 표시한다. 다음과 같이 보일 것이다:

glBindFramebuffer(GL_FRAMEBUFFER, hdrFBO);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);  
    // [...] render (lighted) scene 
glBindFramebuffer(GL_FRAMEBUFFER, 0);

// now render hdr colorbuffer to 2D screen-filling quad with different shader
hdrShader.use();
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, hdrColorBufferTexture);
RenderQuad();

 여기서 장면의 색상 값은 1.0을 초과 할 수 있는 임의의 색상 값을 포함 할 수 있는 부동 소수점 색상 버퍼로 채워진다.


이 튜토리얼에서는 네 개의 점 광원이 있는 터널로 작동하는 커다란 뻗어있는 큐브를 사용해 간단한 데모 장면을 만들었다.


이 중 하나는 터널 끝 부분에 매우 밝게 배치되어 있다:

std::vector<glm::vec3> lightColors;
lightColors.push_back(glm::vec3(200.0f, 200.0f, 200.0f));
lightColors.push_back(glm::vec3(0.1f, 0.0f, 0.0f));
lightColors.push_back(glm::vec3(0.0f, 0.0f, 0.2f));
lightColors.push_back(glm::vec3(0.0f, 0.1f, 0.0f));  

 부동 소수점 프레임 버퍼로 렌더링하는 것은 일반적으로 프레임 버퍼에 렌더링하는 것과 동일하다. 새로운 것은 hdrShader의


조각 쉐이더로 부동 소수점 컬러 버퍼 텍스처가 첨부된 최종 2D 쿼드를 렌더링하는 것이다. 먼저 간단한 pass-through 조각 쉐이더를


정의하겠다:

#version 330 core
out vec4 FragColor;
  
in vec2 TexCoords;

uniform sampler2D hdrBuffer;

void main()
{             
    vec3 hdrColor = texture(hdrBuffer, TexCoords).rgb;
    FragColor = vec4(hdrColor, 1.0);
}  

 여기서는 부동 소수점 색상 버퍼를 직접 샘플링하고 색상 값을 조각 쉐이더의 출력으로 사용한다. 그러나 2D 쿼드의 출력이 기본 프레임 버퍼로


직접 렌더링되므로 부동 소수점 색상 텍스처의 값이 1.0을 초과하더라도 모든 조각 쉐이더의 출력 값은 0.0과 1.0 사이에서 고정된다.


Direct rendering of floating point color values to the default framebuffer without tone mapping.


 터널 끝의 강렬한 조명 값은 1.0으로 클램핑된다. 이 부분의 대부분이 완전히 흰색이므로 1.0을 초과하는 모든 조명 세부 정보가 손실된다.


HDR값을 LDR값으로 직접 변환 할 때 처음에는 HDR을 사용할 수 없는 것처럼 보인다. 이 문제를 해결하기 위해 해야 할 일은 모든 부동 소수점


색상 값을 0.0 - 1.0 범위로 다시 변환하는 것이다. 톤 맵핑이라는 프로세스를 적용해야한다.




Tone mapping


 톤 맵핑은 부동 소수점 색상 값을 낮은 동적 범위라고 하는 예상 [0.0, 1.0] 범위로 변환해 너무 자세하게 손실하지 않고 특정 스타일의


색상 균형을 수시로 동반하는 프로세스입니다.



 가장 간단한 톤 맵핑 알고리즘은 Reinhard 톤 맵핑이라고 알려져 있으며 전체 HDR 생상 값을 LDR 색상 값으로 모두 균등하게


분산하여 분산시킨다. Reinhard 톤 맵핑 알고리즘은 모든 밝기 값을 LDR에 골고루 퍼뜨린다. Reinhard 톤 맵핑을 이전 조각 쉐이더에


포함시키거나 좋은 측정을 위한 감마 보정 필터를 추가한다. (SRGB 텍스처 사용 포함)

void main()
{             
    const float gamma = 2.2;
    vec3 hdrColor = texture(hdrBuffer, TexCoords).rgb;
  
    // 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 톤 맵핑을 적용해 우리는 더 이상 장면의 밝은 영역에서 세부 사항을 잃지 않는다. 더 밝은 영역을 약간 선호하는 경향이 있어


어두운 영역이 덜 세밀하고 덜 뚜렷하게 보인다.


Reinhard tone mapping algorithm applied with HDR rendering in OpenGL


 나무 텍스처 패턴이 다시 보일 수 있게 되면 여기에서 다시 터널 끝의 세부 사항을 볼 수 있다. 이 비교적 단순한 톤 맵핑


알고리즘을 사용하면 부동 소수점 프레임 버퍼에 저장된 전체 HDR 값 범위를 제대로 볼 수 있으므로 세부 사항을 잃지 않고


장면의 조명을 정밀하게 제어 할 수 있다.

조명 쉐이더의 끝 부분에서 직접 톤 맵을 만들 수 있다. 부동 소수점 프레임 버퍼가 전혀 필요하지 않다! 그러나 장면이 복잡해짐에 따라 중간 HDR 결과를 부동 소수점 버퍼로 저장해야 할 필요성을 자주 느낄 수 있으므로 좋은 연습이다.

 톤 맵핑의 또 다른 흥미로운 사용법은 노출 매개 변수의 사용을 허용하는 것이다. 소개에서부터 HDR 이미지에는 다양한 노출 수준으로


볼 수 있는 많은 세부 정보가 포함되어 있음을 기억할 것이다. 낮고 ㅏ밤의 사이클을 특징으로 하는 장면이 있다면, 인간의 눈이 적응하는


것과 같이 낮과 밤의 노출을 낮게 사용하는 것이 좋다. 이러한 노출 매개 변수를 사용하면 노출 매개 변수만 변경하면 되므로 조명 조건이


다른 낮과 밤 모두에서 작동하는 매개 변수를 구성할 수 있다.



 상대적으로 간단한 노출 톤 맵핑 알고리즘은 다음과 같다:

uniform float exposure;

void main()
{             
    const float gamma = 2.2;
    vec3 hdrColor = texture(hdrBuffer, TexCoords).rgb;
  
    // 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);
}  

 여기에서는 노출 균일성을 기본값 1.0으로 정의해 HDR 색상 값의 어둡거나 밝은 영역에 더 집중할 것인지 여부를 보다 정확하게


지정할 수 있다. 예를 들어, 노출 값이 높을수록 터널의 어두운 부분이 훨씬 더 세밀해진다. 대조적으로, 노출이 낮으면 어두운 영역의


세부 사항이 크게 제거되지만 장면의 밝은 영역에서 더 자세하게 볼 수 있다. 여러 노출 수준에서 터널을 보려면 아래 이미지를 보아라:


Multiple exposure levels of HDR tone mapping in OpenGL


 이 이미지는 HDR의 이점을 명확하게 보여준다. 노출 레벨을 변경하면 장면의 세부 사항을 많이 볼 수 있다. 그렇지 않으면 낮은 HDR로


인해 손실 될 수 있다. 예를 들어, 터널이 끝날 때까지 목재 구조는 거의 보이지 않지만 노출이 적으면 자세한 나무 패턴이 선명하게 보인다.


나무 무늬가 가까이에 있어도 높은 노출로 훨씬 잘 볼 수 있다.



More HDR


 표시된 2가지 톤 맵핑 알고리즘은 각기 자신의 강점과 약점이 있는 톤 맵핑 알고리즘의 대규모 컬렉션 중 일부에 지나지 않는다.


일부 톤 맵핑 알고리즘은 특정 색상/강도를 다른 색상보다 우선시하며 일부 알고리즘은 더 낮고 높은 노출 색상을 동시에 표시해


더 화려하고 자세한 이미지를 만든다. 또한, 이전 프레임에서 장면의 밝기를 결정하고 노출 매개 변수를 조정해 장면이 어두운 영역에서


더 밝거나 밝은 영역에서 더 어두워 지도록 자동 노출 조정 또는 눈 적응 기술로 알려진 기술 모음이 있다.



 HDR 렌더링의 진정한 이점은 무거운 조명 알고리즘을 사용하는 크고 복잡한 장면에서 실제로 나타난다. 튜토리얼의 데모 장면은


접근하기 쉬운 상태에서 교육 목적으로 복잡한 데모 장면을 만드는 것은 어렵기 때문에 세부 사항이 부족하다. 상대적으로 단순하지만


HDR 렌더링의 장점 중 일부를 보여준다. 톤 맵핑을 사용하고 높고 어두운 영역에서 세부 정보가 손실되지 않는다. 여러 조명을 추가해도


클램프 영역이 생기지 않으며 조명 값은 다음과 같이 지정할 수 있다. 원래 밝기 값은 LDR 값에 의해 제한되지 않는다.


또한, HDR 렌더링은 몇 가지 흥미로운 효과를 보다 현실적이며 현실적으로 만든다.