TECHARTNOMAD TECHARTFLOW.IO

TECH.ART.FLOW.IO

[번역]Tone Mapping

jplee 2023. 12. 4. 17:57

역자의 말.
종종 여러 프로젝트에 참여 하다 보면 아트디렉터의 다양한 스타일 해석을 위해서 렌더링 파트는 여러 측면에서 생각을 해야 하고 개선 점을 찾아야 하며 또한 달성해야 하는 시대가 되었죠. 10년 전만해도 국외 특히 매우 전통적인 사실적인 그래픽을 추구하던 EA 스포츠나 엑티비전의 일부 AAA 프로젝트 그리고 언차티드나 라스트오브어스 등에서나 관심을 갖던 이슈라고 할 수 있어요. 10년 전만해도 대부분의 아시아권 회사들은 스타일라이즈 위주였을지도 모르니까요. ACES 톤맵핑이나 컬러그레이딩에 대한 부분은 지속적으로 탐구 해야할 영역인것 같습니다. 최근에도 새로운 프로젝트에 어떤 형식의 톤맵핑이 가장 적절한지에 대한 탐구를 하고 있어요. 예전에 살펴봤던 Bruno Opsenica 의 잘 정돈 된 기사를 번역해서 공유 해 봅니다. 


 

 

원문

Tone Mapping | Bruno Opsenica's Blog (bruop.github.io)

 

Tone Mapping

A guide to adding tone mapping to your physically based renderer

bruop.github.io

소개
지난 글에서 물리 기반 렌더링에서 생성되는 것과 같은 HDR 이미지의 노출을 계산하는 방법을 설명했습니다. 이 노출을 통해 기본적으로 이미지를 '보정'하여 휘도 값을 톤 커브를 적용할 수 있는 범위로 조정할 수 있습니다. 포스팅을 읽지 못한 분들을 위해 워크플로우를 설명하는 다이어그램을 준비했습니다:

Tone mapping pipeline

이 글에서는 세 번째 박스(위 도식의 가운데), 톤 매핑 연산자에 대해 알아보겠습니다. 톤 매핑 연산자는 휘도 값(또는 개별 색상 채널)을 입력으로 받아 디스플레이가 예상하는 [0, 1] 사이의 값을 출력하는 커브/함수입니다. 또한 현실을 더 잘 표현하는 이미지를 생성하기 위한 것입니다. 표준 다이나믹 레인지(SDR) 디스플레이에서는 실제로 현실을 재현할 수 없으므로 시청자에게 거의 동일한 인상을 주는 이미지를 만드는 데 만족해야 하기 때문에 엄밀히 말해 정답이 있는 것은 아닙니다. 완벽한 재현을 달성하기에는 한계가 너무 많지만(그리고 우리가 원하는 것이 무엇인지도 명확하지 않습니다), 클램핑이라는 순진한 접근 방식보다는 훨씬 더 나은 작업을 할 수 있습니다!

이 기능을 수행하는 커브는 여러 가지가 있지만 대부분 다음과 같은 특징을 가지고 있습니다:

  • " shoulder "는 더 큰 휘도 값을 1에 가까운 값에 매핑하기 위한 것이지만 이 수렴은 일반적으로 점근적입니다.
  • " foot "은 낮은 범위의 휘도가 표시되는 방식을 제어하기 위한 것입니다. 풋은 숄더만큼 일반적이거나 필요하지는 않지만 이미지의 어두운 부분이 너무 어두워지는 것을 방지하는 데 도움이 될 수 있습니다.
  • 선형 또는 대부분 선형 부분으로, "중간 톤"의 스케일링 방식을 제어합니다.

가장 간단한 예는 아마도 라인하르트 곡선(방정식 3, 4)일 것입니다.

Reinhard Curves

사람들이 수년 동안 사용해온 라인하르트 곡선은 기본적으로 두 가지가 있습니다. 첫 번째, 더 간단한 곡선은 다음과 같습니다:

여기서 Ld는 최종 디스플레이 휘도이고, L′는 노출 조정된 입력 휘도입니다:

이는 휘도가 최대 값인 1.0을 초과하는 것을 효과적으로 방지하는 동시에 낮은 휘도 값(여기서 L<<1)을 선형적으로 스케일링(예: Ld = L)하는 매우 쉬운 방법입니다. 하지만 이렇게 하면 실제로 휘도 값이 1.0이 될 수 없기 때문에 더 높은 휘도 값을 조금 더 제어할 수 있는 두 번째 수정된 방정식이 있습니다:

여기서 Lwhite는 채도를 설정할 수 있는 사용자 제어 변수입니다. 이 데스모스 그래프를 통해 Lwhite 는 이 데스모스 그래프를 사용하여 함수를 변경합니다.

이러한 톤 연산자를 이미지에 적용하려면 HDR PBR 출력, 평균 광도(1x1 텍스처에 저장)를 가져와 백버퍼에 출력을 쓰는 간단한 프래그먼트 셰이더를 작성하면 됩니다:

$input v_texcoord0

#include "common.sh"

SAMPLER2D(s_texColor, 0);
// This is the output from the compute shaders in the last post
SAMPLER2D(s_texAvgLum, 1);

// This will store whatever params you need for the curve
// (whitePoint in this case)
uniform vec4 u_params;

void main()
{
  vec3 rgb = texture2D(s_texColor, v_texcoord0).rgb;
  float avgLum = texture2D(s_texAvgLum, v_texcoord0).r;

  // Yxy.x is Y, the luminance
  vec3 Yxy = convertRGB2Yxy(rgb);

  float whitePoint   = u_tonemap.y;

  float lp = Yxy.x / (9.6 * avgLum + 0.0001);

  // Replace this line with other tone mapping functions
  // Here we applying the curve to the luminance exclusively
  Yxy.x = reinhard2(lp, whitePoint);

  rgb = convertYxy2RGB(Yxy);

  gl_FragColor = toGamma(vec4(rgb, 1.0) );
}

코드는 매우 간단하며 프로세스도 간단합니다:

1. RGB 값을 CIE xyY 색 공간으로 변환합니다. 여기서 Y는 광도입니다.
2. 노출 조정
3. 톤 커브를 사용하여 휘도(휘도만)를 조정합니다.
4. 역변환(xyY -> RGB)을 수행합니다.
5. 감마 보정 적용 및 결과를 백버퍼에 쓰기

개별 커브 코드인 reinhard(Y) 및 reinhard2(Y, whitePoint)는 이 셰이더 토이를 확인할 수 있어요.

라인하드 커브를 사용하면 채도가 높은 검정색이 생성되므로 RGB 채널에 연산자를 개별적으로 적용하지 않는 것이 좋습니다. 하지만 자유롭게 시도해보고 출력이 어떻게 비교되는지 확인해 보세요.

참고로 다음은 세 가지 커브를 사용한 (감마 보정) 결과를 보여주는 세 가지 이미지입니다: 선형(예: 연산자 미적용), 단순 라인하드, 수정된 라인하드:

Linear tone mapping
Simple Reinhard
Modified Reinhard

위에서 아래쪽으로: 선형, 단순 라인하드, 수정 라인하드

 

우리가 해결하고자 하는 것이 무엇인지 명확히 하기 위해 선형 예제의 문제점을 먼저 짚어볼 필요가 있습니다. 토끼에 반사된 하이라이트를 보세요. 

하이라이트는 실제로는 매우 밝은 파란색인 하늘이 반사된 것입니다. 하지만 하늘의 휘도가 높기 때문에 토끼가 흰색으로만 보입니다. 

마찬가지로, 모든 창문이 날아가고 균일하게 흰색으로 보이는 반면 태양의 플레어는 엄청나케 크고 이미지에서 검은색은 실제로 거의 없으며, 검은색으로 보이는 대부분의 사물은 실제로는 회색입니다.

즉, 토끼 밑이나 나뭇잎의 내부와 같이 매우 가려진 풍경이 있음에도 불구하고 [0, 1]의 전체 범위를 사용하지 않았다는 뜻입니다. 이는 톤 매핑을 통해 해결하고자 하는 미묘한 문제 중 일부입니다.

한편 라인하드 커브는 이러한 문제 중 일부를 해결하지만 전부는 아닙니다. 
단순한 라인하드를 사용하면 토끼의 대부분의 하이라이트에서 흰색이 제거되고 대신 짙푸른 하늘이 나타납니다. 
그러나 왼쪽의 나뭇잎은 이제 옅은 녹색이 되어 실제로 빛을 많이 반사하는 것처럼 보이지 않습니다(실제로는 그렇지 않습니다). 더 눈에 띄는 것은 태양의 플레어가 이제 거의 완전히 흰색이지만 실제로 창 오른쪽 상단에 하늘의 작은 부분이 보이는 반면, 선형 예제에서는 플레어가 너무 크지만 예상치 못한 색상 패치가 나타나지 않는다는 점입니다.

조정된 라인하드는 Lwhite 선택으로 인해 훨씬 더 나은 하이라이트를 제공합니다. 값이 너무 낮으면 선형 버전과 같이 점점 더 많은 기능이 날아갈 수 있지만, Lwhite( 가 커지면 단순한 라인하드와 매우 유사한 모양이 됩니다. 다음 세 이미지는 다음과 같습니다.

White point of 1.5
White point of 3
White point of 5

위쪽부터: 1.5, 3.0, 5.0의 흰색 포인트로 캡처한 이미지

Filmic Curves

라인하드가 없는 것보다는 분명 낫지만, 업계에서는 특히 극단에 대한 동작 방식 측면에서 더 많은 사용자 정의가 가능한 다른 커브로 이동하는 것으로 보입니다. 로맹 가이는 이러한 다양한 커브에 대한 훌륭한 셰이더 토이 데모를 만들었고, 저는 이를 약간 수정하여 두 개의 라인하드 커브를 추가했습니다.

저는 셰이더토이에 사용된 각 커브를 시험해보기로 했습니다:

나르코비차가 구현한 ACES 곡선 그리고 

언리얼 엔진의 톤 커브로, ACES 커브를 기반으로 합니다. ( 이 구현에 대한 자세한 내용은 찾을 수 없었지만 ACES 커브와 매우 유사합니다. )

우치무라 하지메의 그란 투리스모 곡선 그리고 티모시 로츠가 제시한 커브 입니다.

ACES
Unreal

 

Lottes curve per channel
Uchimura curve per channel

위부터 아래방향으로: ACES, 언리얼, 롯데, GT 순입니다.

각 톤 커브는 최종 이미지에 약간씩 다른 "모양"을 만들어내지만 필름 커브가 다른 커브와 크게 다른 이미지를 만들어낸다는 것은 분명합니다. 필름 커브는 라인하드 커브보다 덜 "씻겨나간" 이미지를 생성합니다.

또한 각 RGB 채널에 개별적으로 커브를 적용하지 않고 휘도 값에만 커브를 적용하는 것은 선택 사항이라는 점에 유의해야 합니다. 존 헤이블은 후자의 접근 방식을 옹호하지만, 이 경우 색조에 변화가 생길 수 있습니다. Frostbite 팀의 이 프레젠테이션에서는 색조를 보존하는 톤 매핑과 색조를 보존하지 않는 톤 매핑의 예시를 제공합니다.

저는 두 가지 방법을 모두 시도해 보았는데, 아마도 존 헤이블의 생각이 맞을 것 같습니다. 휘도에만 적용하면 최종 이미지의 색조가 바뀌지 않기 때문에 특히 태양과 하이라이트 주변에서 이상한 동작이 발생합니다. 하늘의 매우 밝은 파란색은 고정되어 파란색으로 유지되지만 실제로는 픽셀을 "포화"시켜서 흰색으로 유지하는 것이 더 나을 수 있습니다. 다음 그림은 이전 세 가지 커브의 차이를 보여줍니다: 에이스, 우치무라, 롯데.

ACES curve
ACES curve per channel
Uchimura curve
Uchimura curve per channel
Lottes curve
Lottes curve per channel

각 카테고리 마다 위 이미지는 휘도에만 커브가 적용되고, 아래쪽 에서는 RGB 채널별로 커브가 적용됩니다.

A note on Gamma Correction

감마 보정에 대해 자세히 설명하지는 않겠지만, 감마 보정은 톤 매핑뿐만 아니라 일반적으로 컴퓨터 그래픽에서 색상 값으로 작업할 때 필수적인 부분입니다. 

하지만 톤 매핑 후에도 이미지가 올바르게 표시되려면 디스플레이가 선형 값을 기대하지 않기 때문에 감마 보정을 적용해야 합니다. 그 가장 큰 이유는 밝기에 대한 인간의 인식이 선형적이지 않기 때문입니다. 

감마에 대해 잘 모르시거나 제가 무슨 말을 하는지 잘 모르신다면 여기 몇 가지 좋은 글을 읽어보세요:

Conclusion

이 게시물에는 다양한 커브가 제시되었으므로 어떤 커브를 사용할지 예술적으로 선택하면 됩니다. 개인적으로 저는 GT 커브의 모양이 가장 마음에 듭니다. 하지만 ACES 곡선은 지난 몇 년 동안 많은 인기를 얻었습니다.

이 주제에서 살펴볼 다른 사항도 있습니다:

현지화된 톤 매핑. 이 게시물에서는 단일 노출 값을 사용하여 전체 이미지를 매핑했지만 사람의 눈은 이보다 조금 더 복잡합니다. 글로벌 톤 매핑 연산자를 사용하면 어떤 한계에 부딪히게 되는지 자세히 설명하는 좋은 포스팅을 Bart Wronski가 작성했습니다.
기술적으로는 장면 참조 휘도에 머티리얼의 알베도를 곱한 후 노출을 계산합니다. 휘도 사전 알베도를 사용하는 것이 기술적으로 더 정확하겠지만, 이 경우 해당 값에 대한 추가 프레임 버퍼가 필요하기 때문에 이 기법은 거의 사용하지 않는 것으로 알고 있습니다.
이미 리니어 HDR 공간에서 씬을 렌더링한 후 노출을 적용했지만, 이는 HDR 버퍼가 극단적인 휘도 값을 처리할 수 있는 충분한 범위를 가져야 함을 의미합니다. 최대값이 65,504이기 때문에 RGBA16F 버퍼에서는 이 작업이 어려울 수 있습니다. HDR 버퍼에서 "무한한" 광도를 캡처할 수 있는 가능성을 처리하는 대신, DICE 등은 마지막 프레임의 휘도를 기반으로 조명을 "사전 노출"합니다.
이 게시물은 특히 SDR 디스플레이에 렌더링하기 위한 것입니다. HDR 디스플레이는 서로 다른 인코딩을 사용하며, 아직 업계 표준은 없지만 워크플로우가 약간 다르다는 것을 의미합니다. Krzysztof Narkowicz의 블로그 게시물에서 HDR을 지원하기 위한 첫 단계와 관련 추가 리소스를 설명합니다.

또한 지난 포스팅에서 톤 매핑 없이 장난감 물리 기반 렌더러의 출력으로 다음 이미지를 제시했던 것을 기억하실 것입니다:

Renderer output without tone mapping

이 이미지에는 감마 보정 기능이 없기 때문에 특히 나빠 보이지만 대부분의 손상은 허용 가능한 [0, 1] 범위를 벗어난 휘도 값으로 인해 발생합니다. 기술적으로는 물리적으로 정확하지만 디스플레이는 이를 신경 쓰지 않습니다. 다음은 수정된 라인하드 커브와 감마 보정을 사용하여 톤 매핑을 적용한 동일한 장면입니다:

Renderer output with tone mapping

이 극명한 차이를 통해 조명에 실제 물리적 값으로 PBR 셰이딩을 구현하려는 경우 톤 매핑이 필요하다는 것을 알 수 있기를 바랍니다. 위의 토끼 이미지에서는 하이라이트가 날아간 것을 그래도 이해할 수 있었지만, 여기서는 물리적으로 의미 있는 조명을 포기하지 않고는 제대로 된 이미지를 얻을 수 없습니다.

Code Sample

토끼의 이미지는 여기에서 찾을 수 있는 BGFX 스타일의 예제를 사용하여 만들었습니다. 여기에는 이미 살펴본 셰이더뿐만 아니라 씬과 GPU 리소스의 설정이 포함되어 있습니다.

 

References

 

참고 문헌에 대한 링크를 부지런히 연결하려고 노력했지만, 톤 매핑에 대한 일반적인 링크 모음도 여기에 있습니다.

 

GitHub - BruOp/3d_programming_references: Just a list of references for 3D programming

Just a list of references for 3D programming. Contribute to BruOp/3d_programming_references development by creating an account on GitHub.

github.com