눈팅하는 게임개발자 블로그

OpenGL Advanced Lighting 6-6 HDR 본문

공부한거/OpenGL

OpenGL Advanced Lighting 6-6 HDR

Palamore 2020. 12. 14. 14:57

원문 사이트

learnopengl.com/Advanced-Lighting/HDR

 

LearnOpenGL - HDR

HDR Advanced-Lighting/HDR Brightness and color values, by default, are clamped between 0.0 and 1.0 when stored into a framebuffer. This, at first seemingly innocent, statement caused us to always specify light and color values somewhere in this range, tryi

learnopengl.com

번역 사이트

gyutts.tistory.com/180?category=755809

 

LearnOpenGL - Advanced Lighting : HDR

link : https://learnopengl.com/Advanced-Lighting/HDR HDR(High Dynamic Range)  기본적으로 밝기와 색상 값은 프레임 버퍼에 저장 될 때 0.0에서 1.0 사이로 고정된다. 이것은 처음에는 겉으로 보기에 문제가..

gyutts.tistory.com

HDR(High Dynamic Range)

기본적으로 밝기와 색상 값은 프레임 버퍼에 저장될 때 0.0에서 1.0 사이로 고정된다.

이는 처음에는 겉으로 보기에 문제가 없어 보이지만, 이 범위 어딘가에서 항상 빛과 색체 값을 지정해 장면에 적합하도록 했다.

이는 좋은 결과를 냈지만, 그러나 합계가 1.0을 초과하는 여러 개의 밝은 광원을 사용해 특별히 밝은 지역을 보면

1.0 이상의 밝기 또는 색상 합계를 갖는 모든 fragment가 1.0으로 고정되기 때문에 보기 흉하게 바뀐다.

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

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

 

이 문제의 해결 방법은 광원의 강도를 줄이고 장면의 파편 영역을 1.0보다 밝게 만드는 것이다.

이는 비현실적인 라이팅 매개 변수를 사용해야 하므로 좋은 해결책은 아니다.

보다 나은 방법은 색상 값이 일시적으로 1.0을 초과하도록 허용하고 최종 단계로 원래의 범위인

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

 

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

fragment 색상을 1.0이상으로 허용함으로써 HDR(높은 동적 범위)로 작동할 수 잇는 색상 값의 범위가

훨씬 더 넓어졌다. HDR을 사용하면 밝고 선명한 화면을,

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

 

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

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

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

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

이는 인간의 눈이 어떻게 작동하는지, 그리고 HDR 렌더링의 기초와도 매우 유사하다.

빛이 거의 없을 때 인간의 눈은 스스로 적응하므로 어두운 부분은 밝은 영역에서 더 잘 보이고 유사하게 보이며

인간의 눈에는 장면의 밝기에 따라 자동 노출 슬라이더가 존재한다.

 

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을 구현하려면 각 fragment shader가 실행된 후에 컬러 값이 클램프되는 것을 막기 위한 방법이 필요하다.

프레임 버퍼가 컬러 버퍼의 내부 형식으로 정규화된 고정 소수점 색상 포맷을 사용하는 경우 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 (lit) scene 
glBindFramebuffer(GL_FRAMEBUFFER, 0);

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

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

이 강좌에서는 네 개의 point light가 있는 터널로 작동하는 커다란 뻗어있는 큐브를 사용해 간단한 데모 장면을 만들었다.

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

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의 fragment shader로 부동 소수점 컬러 버퍼 텍스처가 첨부된 최종 2D 쿼드를 렌더링하는 것이다.

먼저 간단한 pass-through fragment shader를 정의한다.

#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);
}  

여기서는 부동 소수점 색상 버퍼를 직접 샘플링하고 색상 값을 fragment shader의 출력으로 사용한다.

그러나 2D 쿼드의 출력이 기본 프레임 버퍼로 직접 렌더링되므로 부동 소수점 색상 텍스처의 값이

1.0을 초과하더라도 모든 fragment shader의 출력 값은 0.0과 1.0 사이에서 고정된다.

터널 끝의 강력한 조명 값은 1.0으로 클램핑된다.

이 부분의 대부분이 완전한 흰색이므로 1.0을 초과하는 모든 조명 세부 정보가 손실된다.

HDR 값을 LDR 값으로 직접 변환할 때 처음에는 HDR을 사용할 수 없는 것처럼 보인다.

이 문제를 해결하기 위해 해야할 일은 모든 부동 소수점 색상 값을 0.0~1.0 범위로 다시 변환하는 것이다.

톤 매핑이라는 프로세스를 적용해야 한다.

 

Tone Mapping

톤 매핑은 부동 소수점 색상 값을 낮은 동적 범위라고 하는 [0.0, 1.0]범위로 변환해 너무 자세하게 손실되지 않고

특정 스타일의 색상 균형을 수시로 동반하는 프로세스이다.

 

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

균등하게 분산하여 분산시킨다. Reinhard 톤 매핑 알고리즘은 모든 밝기 값을 LDR에 골고루 퍼뜨린다.

Reinhard 톤 매핑을 이전 fragment shader에 포함시키거나 좋은 측정을 위한 gamma correction 필터를 추가한다.

(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 톤 매핑을 적용해 더 이상 scene의 밝은 영역에서 세부사항을 잃지 않는다.

더 밝은 영역을 약간 선호하는 경향이 있어 어두운 영역이 덜 세밀하고 덜 뚜렷하게 보이기는 한다.

나무 텍스처 패턴이 다시 보일 수 있게 되면 여기에서 다시 터널 끝의 세부 사항을 볼 수 있다.

이 비교적 단순한 톤 매핑 알고리즘을 사용하면 부동 소수점 프레임 버퍼에 저장된 전체 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 색상 값의 어둡거나 밝은 영역에 더 집중할 것인지에 대한

여부를 보다 정확하게 지정할 수 있다. 예를 들어, 노출 값이 높을 수록 어두운 부분이 훨씬 더 세밀해진다.

대조적으로, 노출이 낮으면 어두운 영역의 세부 사항이 크게 제거되지만

장면의 밝은 영역에서 더 자세하게 볼 수 있다.

아래 이미지는 여러 노출 수준에서의 터널의 모습이다.

이 이미지는 HDR의 이점을 명확하게 보여준다.

노출 레벨을 변경하면 장면의 세부 사항을 많이 볼 수 있다.

그렇지 않으면 낮은 HDR로 인해 손실될 수 있다.

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

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

 

More HDR

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

일부 톤 매핑 알고리즘은 특정 색상/강도를 다른 색상보다 우선시하며 일부 알고리즘은 더 낮고 높은 노출 색상을 동시에 표시해 더 화려하고 자세한 이미지를 만든다.

또한, 이전 프레임에서 장면의 밝기를 결정하고 노출 매개 변수를 조정해 장면이 어두운 영역에서 더 밝거나

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

 

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

강좌의 데모 장면은 접근하기 쉬운 상태에서 교육 목적으로 복잡한 데모 장면을 만드는 것은 어렵기 때문에

세부 사항이 부족하다. 이는 상대적으로 단순하지만 HDR 렌더링의 장점 중 일부를 보여준다.

톤 매핑을 사용하고 높은 어두운 영역에서 세부 정보가 손실되지 않는다.

여러 조명을 추가해도 클램프 영역이 생기지 않으며 조명 값은 다음과 같이 지정할 수 있다.

원래 밝기 값은 LDR 값에 의해 제한되지 않는다.

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