Post Lists

2018년 8월 21일 화요일

Physically Based Rendering - Diffuse irradiance (3)

https://learnopengl.com/PBR/IBL/Diffuse-irradiance

Diffuse irradiance
IBL 또는 Image Based Lighting은 light objects에 대한 기법들의 집합이다. 이것은 이전 튜토리얼에서 direct analytical lights에 의해서가 아닌 하나의 큰 광원으로서 주변  환경을 다루어서 된다. 이것은 일반적으로 cuebemap 환경 맵을 조작하여 얻어진다 (실제 세계에서 취해지거나 또는 3D scene으로 생성된), 이것은 우리가 직접적으로 그것을 우리의 lighting equations에서 사용할 수 있도록 하기 위해서이다 : light emitter로서 각 cubemap pixel을 다루는 것이다. 이 방식으로 우리는 효과적으로 한 광겨의 global lighting과 일반적인 느낌을 포착하고, 오브젝트들에게 그것들의 환경에 좀 더 속한 느낌을 주게 된다.

image based lighting algorithms이 어떤 (global) 환경의 라이팅을 포착하기 때문에, 그것의 입력은 좀 더 정확하 ambient lilghting의 형태로 고려된다, 심지어 global illumination의 조잡한 근사이다. 이것은 PBR에 대해 IBL을 흥미롭게 만드는 것이다. 오브젝트들은 상당히 물리적으로 더 정확해 보이기 때문이다. 우리가 환경의 라이팅을 고려할 때면.

IBL을 우리의 PBR 시스템에 도입하기 위해서, reflectance equation을 다시 봐보자:



이전에 묘사되었듯이, 우리의 주된 목표는 모든 들어오는 빛의 방향 w_i를 반구 Ω에 대해 적분을 푸는 것이다. 이전 튜토리얼에서 적분을 푸는 것은 우리가 미리 적분에 기여하는 정확한 몇몇의 빛의 방향 w_i를 알았기 때문에 쉬웠다. 그러나 이번에, 주변 환경으로부터 모든 들어오는 빛의 방향 w_i는 잠재적으로 적분을 푸는데 자명하지 않게 만드는 몇 가지 radiance를 가질 수 있다. 이것은 우리에게 적분을 푸는데 두 가지 주된 요구사항을 준다:


  • 우리는 어떤 방향 vector w_i가 주어지면 scene의 radiance를 얻을 방법이 필요하다
  • 이 적분을 푸는 것은 빠르고 실시간일 필요가 있다.
이제 그 첫 번째 방법은 상대적은 쉽다. 우리는 이미 그것의 힌트를 주었지만, 한 환경을 나타내는 방식 또는 scene의 irradiance는 (가공된) environment cubemap의 형태에 있다. 그러한 큐브맵이 주어진다면, 우리는 그 큐브맵의 모든 texel을 하나의 빛을 내뿜는 광원으로 시각화 할 수 있다. 이 큐브맵을 어떤 방향 벡터 w_i로 샘플링하여, 우리는 그 방향으로부터 scene의 radiance를 어든ㄴ다.

어떤 방향 w_i가 주어질 때 scene의 radiance를 얻는 것은 간단하다 :

  vec3 radiance = texture(_cubemapEnvironment, w_i).rgb;

그러나, 적분을 푸는 것은 우리에게 한 방이 아닌 반구 Ω에 대해 모든 가능한 방향의 w_i 으로부터 environment map을 샘플링하는 것을 요구한다. 그리고 이것은 각 fragment shader invocation에 대해 너무 비싸다. 좀 더 효율적인 방식으로 적분을 풀기 위해, 우리는 그것의 연산 대부분을 pre-process or pre-compute하길 원할 것이다. 이것 때문에, 우리는 reflectance equation을 좀 더 깊게 팔 것이다:

reflectance euqation을 잘 봐보면, 우리는 BRDF의 diffuse k_d와 specular k_s 항이서로 독립적인 것을 발견하고, 우리는 적분을 두 개로 나눌 수 있다:


그 적분을 두 부분으로 나누어서, 우리는 diffuse와 specular 항에 개별적으로 집중할 수 있다; 이 튜토리얼의 diffuse 적분에 집중한다.

diffuse 적분을 자세히 봐보면, 우리는 diffuse lambert term이 상수항이고 (color c, 굴절 비율 k_d 그리고 pi는 적분에 대한 상수이다) 그리고 어떠한 적분 변수와도 의존적이지 않다라는 것을 알게된다. 이것을 고려하면 우리는 그 상수항을 diffuse integral에서 빼낼 수 있다:


이것은 우리에게 w_i에 의존적인 적분을 준다 (p가 environment map의 중심이라고 가정하자). 이 지식으로, 우리는 각 sample 방향 (또는 텍셀) w_o에 convolution에 의한 diffuse integral의 결과를 저장하는 새로운 cubemap을 계산하거나 또는 미리 계산할 수 있다.

Convolution은 data set에서 모든 다른 entries를 고려하는 data set에서 각 entry에 대해 몇 가지 연산을 적용하는 것이다; 데이터 셋은 그 scene의 radiance or environment map이다. 따라서 그 큐브맵에서 모든 sample direction에 대해, 우리는 반구 Ω에 대해 모든 다른 샘플 방향을 고려한다.

한 환경 맵을 convolute하기 위해, 우리는 각 output w_o sample 방향에 대해 이산적으로 많은 수의 방향 w_i를 반구 Ω에 대해 샘플링하고 그것들의 radiance를 평균화하여 적분을 푼다. 우리가 우리가 sample directions w_i를 만드는 반구는 우리가 convoluting하는 output w_o sample direction에 대해 향해 있다.

각 sample direction w_o에 대해 적분 결과를 저장하는 이 미리 계산된 cubemap은 방향 w_o를 따라 정렬된 어떤 표면을 scene이 닿는 것의 모든 간접 diffuse light의 미리 계싼된 합계로 생각되어질 수 있다. 그러한 cubemap은 irradiance map으로 알려져있고, convoluted cubemap은 효과적으로 우리가 직접적으로 scene의 irrandiance를 어느 방향 w_o로부터든지 샘플링할 수 있게 한다.

 Green Box
 그 radiance 방정식은 또한 위치 p에 의존한다. 우리는 그것이 irradiance map의 중앙에 있다고 가정한다. 이것은 모든 diffuse indirect light가 단일의 environment map으로부터 들어온다는 것을 의미한다. 그리고 이 map은 현실감의 환각을 깰지도 모른다 (특히 실내에세ㅓ). 렌더링 엔진들은 이것을 scene에 대해 reflection probes를 두어서 해결한다. 그리고 거기에서 각 reflection probes는 주변환경의 그것 자신의 map을 계산한다. 이 방식으로, position p에서 그 irradiance (그리고 radiance)는 그것의 가장 가까운 reflection probes 사잉의 보간된 irradiance이다. 지금은, 우린느 항상 그 environment map이 그것의 중심으로부터 보간된다고 가정하고, reflection probes는 나중의 튜토리얼에서 이야기한다.

아래에, cubemap environment map의 예제가 있고, 그것의 최종 irradiance map이 있다 (wave engine 제공), 이것은 모든 방향 w_o에 대해, 그 scene의 radiance를 평균화 한 것이다.

convoluted result를 각 cubemap texel에 저장하여 (w_o 방향으로), 그 irradiance map은 어느정도 평균 컬러 같은 것을 보여주거나 환경의 lighting display를 보여준다. 이 환경 맵으로부터 어떤 방향을 샘플링하는 것은 우리에게 그 특정한 방향으로부터의 scene의 irradiance를 준다.

PBR and HDR
우리는 lighting tutorial에서 그것에 대해 간단하게 다루었다: PBR pipeline에서 너의 scene의 라이팅의 HDR을 고려하는 것은 엄청 중요하다. PBR이 그것의 입력의 대부분을 실제 물리적 특징과 측정치에 기반을 두기 때문에, 들어오는 빛의 값을 그것들의 물리적으로 동일한 것과 매치시키는 것이 말이 된다. 우리가 각 light의 radiant flux에 대해 교육적 추측을 하거나 그것들의 direct physical equivalent를 사용하든, simple light bulb 또는 태양 사이의 차이는 둘 중 한 방식에 대해 중요하다. HDR render environment에서 작업하는 것 없이는, 정확히 각 빛의 상대적 강도를 명시하는 것이 불가능하다.

그래서 PBR과 HDR은 손잡고 가지만, 어떻게 그것이 IBL과 연관되는가? 우리는 이전 튜토리얼에서 PBR을 HDR에서 작업시키는게 상대적으로 쉬운것을 보았다. 그러나, IBL에 대해 우리가 환경의 간접 빛 강도를 한 환경 큐브맵의 컬러갑에 기반을 둔다고 본다면, 우리는 그 lighting의 HDR을 환경 맵에 저장할 필요가 있다.

우리가 이제까지 큐브맵으로서 사용한 그 환경 맵은 (예를들어 skyboxes로 사용된) LDR에 있었다. 우리는 직접적으로 그 각각의 면 이미지로부터 그것들의 컬러를 사용했다. 그 값은 0.0 ~1.0 사이의 값이고, 그런식으로 처리했었다. 이것이 시각적 결과물에 대해 잘 작동할 지도 모르지만, 그것들을 물리적 입력 파라미터로 받아들일 때, 작동하지 않을 것이다.

The radiance HDR file format
radiance file format에 들어가자. radiance file format은 (.hdr 확장자가 있는) 모두 6개의 면이 있는 전체 큐브맵을 floating point data로 저장하고, 이것은 어떤 사람이 컬러 값을 0.0 ~ 1.0으로 명시하게 한다. lights에 그것들의 정확한 컬러 강도를 주기위해서. 그 파일 포맷은 또한 각 floating point value를 채널당 32 bit value가 아닌 8비트로 저장하려는 똑똑한 트릭을 사용한다. 이 때 color의 알파채널을 exponent로 사용한다 (이것은 정확도의 손실이 있다). 이것은 꽤 잘 작동하지만, 각 컬러를 그것들의 floating point와 동일한 것으로 재 변환시키는 파싱 프로그램을 요구한다.

sIBL archive같은 소스들로부터 무료로 이용가는ㅇ한 꽤 몇 가지 radiance HDR environment maps이 있다. 너는 한 예제를 아래에서 볼 수 있다.

이것은 정확히 너가 기대하고 있는것이 아닐지도 모른다. 이미지가 왜곡되어 나타나고 우리가 이전에 보았던 환경 맵의 각 큐브맵 면들의 어떤 것도 보여주지 않는다. 이 환경맵은 한 구에서 평평한 면으로 투영되었는데, 우리가 좀 더 쉽게 그 환경을 equirectangular map으로 알려진 단일의 이미지로 저장할 수 있게 하기 위해서이다. 이것은 작은 caveat과 함께 오는데, 시각적 해상도의 대부분이 수평의 view drirection으로 저장되었기 때문이다. 반면에 bottom과 top directions에서는 덜 보존된다. 대부분의 경우에, 이것은 멋진 타협이다. 거의 어던 렌더러로, 너는 대부분의 흥미로운 라이팅과 주변환경을 수평으로 보는 방향에서 찾을 것이기 때문이다.

HDR and stb_image.h
radiance HDR 이미지들을 직접적으로 불러오는 것은 너무 어렵않지만 그럼에도 불구하고 귀찮은 파일 포맷의 지식을 요구한다. 운좋게도, 인기있는 헤더 라이브러리 stb_image.h는 radiance HDR 이미지를 직접적으로 우리의 필요에 완벽히 맞게 floating point values의 배열로 불러오는 것을 지원한다. stb_image가 너의 프로젝트에 더해져, HDR 이미지를 불러오는 것은 이제 다음과 같이 간단하다:

stb_image.h는 자동으로 HDR 값들을 floating point values로 매핑시킨다: 채널당 32비트이고, 컬러당 3개 채널이 기본이다. 이것은 equirectangular HDR 환경맵을 2D floating point texture로 저장하기위해 우리가 필요한 모든 것이다.

From Equirectangular to Cubemap
environment lookup을 위해 직접적으로 equirectangular map을 사용하는 것은 가능하지만, 이러한 연산들은 상대적으로 비싸다. 그 경우에, 직접적인 큐브맵 샘플은 좀 더 성능기준에 맞다. 그러므로, 이 튜토리얼에서 우리는 처음에 그 equirectangular image를 나중 처리를 위해 큐브맵으로 변환할 것이다. 그 프로세스에서, 우리는 또한 한 equirectangular map을 어떻게 샘플링하는지를 보여준다는 것을 주목해라. 마치 그것이 3D environment map인 것처럼. 그경우에 너는 너가 원하는 솔루션이 무엇인든지를 고르는데 자유롭다.

equirectangular image를 한 큐브맵으로 바꾸기 위해서, 우리는 (단위) 큐브를 렌더링 할 필요가 있고, 그 equirectangular map을 그 내부로부터 모든 큐브의 면에 사영해야 하고, 그 큐브의 면들 각각의 6개의 이미지를 큐브맵의 면으로 취한다. 이 큐브의 vertex shader는 간단히 그 자체로 큐브를 렌더링하고, 그것의 로컬 포지션을 fragment shader에 3D sample vector로 넘긴다:

fragment shader에 대해, 우리는 그 큐브의 각 부분을 색칠한다. 마치 우리가 깔끔히 그 equirectangular map을 그 큐브의 각면에 사영하는 것 처럼. 이것을 달성하기 위해서, 우리는 큐브의 로컬 포지션으로부터 보간된 fragment의 sample direction을 취해서, 그러고나서 이 방향벡터를 사용한다. 그리고 몇 가지 삼각법이 equirectangular map을 샘플링한다. 마치 그것이 자체로 큐브맵인 것처럼. 우리는 직접적으로 그 결과를 cube-face의 fragment에 저장한다. 이것은 우리가 할 필요가 있는 모든 것이다:

만약 너가 HDR equirectangular map이 주어진 scene의 중앙에 큐브를 렌더링한다면, 너는 이것처럼 보이느 ㄴ어떤 것을 얻을 것이다:

이것은 우리가 효과적으로 equirectangular image를 cubic space에 매핑했다는 것을 보여주지만, 아직 우리가 그 source HDR image를 cubemap texture로 바꾸는데 도움이 되지 않았다. 이것을 달성하기 위해, 우리는 같은 큐브를 큐브의 각 면을 바라보는 6번씩 렌더링 해야만한다. 그리고 그것의 시각적 결과를 framebuffer object에 저장해야한다:

물론, 우리는 그러고나서 대응되는 큐브맵을 생성하고, 그것의 6개의 면에 대해 각각 메모리를 미리할당해야한다:

그 다음에 할 것은 equirectangular 2D texture를 cubemap faces들로 캡쳐하는 것이다.

나는 framebuffer와 point shadows 튜토리얼에서 이전데 다뤄진 코드 세부사항에 대해서는 깊게 다루지 않을 것이지만, 그것은 효과적으로 큐브의 각 면을 바라보는 6개의 다른 view matrices를 설정한다. 그 전체 면을 capture하는 90도의 fov가 있는 projection matrix가 주어지면, 그리고 그 결과를 floating point framebuffer에 저장하여 6번 한 큐브를 렌더링한다:

우리는 framebuffer의 color attachment를 취하고 그거스이 texture target을 그 큐브맵의 모든 면에 대해 바꾼다. 그리고 직접적으로 scene을 cubemap의 페이스들 중 하나에 렌더링한다. 이 루틴이 완료되기만 한다면 (우리가 오직 한 번만 하는), 그 큐브맵 envCubemap은 우리의 오리지널 HDR 이미지의 cubemapped된 environment 버전이다.

그 cubemap을 아주 간단한 skybox shader를 써서 cubemap을 전시해보자.

xyww trick은 여기에서 렌더링된 큐브 fragmentㄴ가 항상 최대 depth value인 1.0으로 끝나게 보장한다. cubemap tutorial에서 설명되었듯이. 우리가 depth comparison function을 GL_LEQUAL로 바꿀 필요가 있는것에 우의해라:

  glDepthFunc(GL_LEQUAL);

그 fragment shader는 큐브의 local fragment position을 사용하여 직접적으로 cubemap environment map을 샘플링한다.

우리는 샘플하기에 직접적으로 정확한 방향벡터와 대응되는 그것의 보간된 vertex cube positions을 사용하여 환경 맵을 샘플링한다. 카메라의 translation components가 무시되기 때문에, 이 쉐이더를 한 큐브에 대해 렌더링하는 것은 너에게 움직이지 않는 배경으로서 환경 맵을 준다. 또한, 우리가 직접적으로 environment map의 HDR 값을 default LDR 프레임버퍼에 output하기 때문에, 우리는 적절히 컬러 값을 tone map하기를 원한다. 게다가, 거의 모든 HDR maps은 기본적으로 linear color space에 있다. 그래서 우리는 기본 framebuffer에 쓰기 전에 gamma correction을 적용할 필요가 있다.

이제 샘플링된 환경 맵을 이전에 렌더링된 스피어들에 대해 렌더링하는 것은 이것처럼 보일 것이다:


음, 여기까지 오는데 꽤 조금 설정이 필요했지만, 우리는 성공적으로 HDR 환경맵을 읽어들이고, 그것을 equirectangular mapping에서 cubemap으로 변환하고, HDR cubemap을 스카이박스로서 scene에 렌더링했다. 게다가, 우리는 cubemap의 모든 6면을 렌더링 하는 작은 시스템을 설정했다. 그리고 우리는 그 환경맵을 convoluting할 때 그것이 다시 필요할 것이다. 너는 여기에서 전체 변환 프로세스의 소스코드를 여기에서 찾을 수 있다.

Cubemap convolution
튜토리얼의 시작에서 설명되었듯이, 우리의 주된 목표는 모든 diffuse indirect lighting에 대 해 적분을 푸는 것이다. cubemap environment map의 형태로 scene의 irradiance가 주어진다면. 우리는 w_i 방향으로 HDR 환경맵을 샘플링하여 특정한 방향으로 우리가 그 scene의 radiance L(p, w_i)를 얻을 수 있다. 그 적분을 풀기위해서, 우리는 각 fragment의 반구 Ω 내에서 모든 가능한 방향의 scene의 radiance를 샘플링해야만 한다.

그러나, Ω에서 모든 가능한 방향으로부터 환경의 lighting을 샘플링하는 것은 불가능하다. 가능한 방향의 수는 이론적으로 무한이기 때문이다. 그러나, 꽤 정확한 irradiance의 근사를 얻기위해 균일하게 공간이 구분되거나 반구 내에서 랜덤하게 취해지는 유한 개수의 방향 또는 샘플들을 취하여 방향의 개수를 근사할 수 있다. 그리고 이것은 효과적으로 적분을 이산적으로 해결한다.

그러나 real-time으로 모든 fragment에 대해 이것을 하는 것은 너무 비싸다. 샘플들의 개수는 멋진 결과를 위해 여전히 상당히 클 필요가 있기 때문이다. 그래서 우리는 이것을 pre-compute하기를 원한다. 반구의 방향은 우리가 irradiance를 캡쳐하는 장소를 결정하기 떄문에, 우리는 모두 나가는 방향 w_o를 향하는 모든 가능한 반구 방향에 대해 irradiance를 미리 계산할 수 있다:



어떤 방향벡터 w_i가 주어진다면, 우리는 방향 w_i로부터의 총 diffuse irradiance를 얻기위해 미리 계산된 irradiance map을 샘플링할 수 있다. fragment surface에서 indirect diffuse (irradiant) light의 양을 결정하기 위해, 우리는 그것의 surface's normal를 향하는 반구의 total irradiance를 얻는다. 그 scene의 irradiance 얻는 것은 간단하다:

  vec3 irradiance = texture(irradianceMap, N);

이제, irradiance map을 생성하기 위해, 우리는 cubemap으로 변환했듯이 environment의 lighting을 convolute할 필요가 있다. 각 fragment에 대해, 그 표면의 반구가 법선 벡터 N을 향한다는 것을 고려한다면, cubemap을 convoluting하는 것은 N을 향하는 반구 Ω안에서 각 방향 w_i의 총 평균화된 radiance를 계산하는 것과 같다.

고맙게도, 이 튜토리얼의 모두 성가신 설정은 아무것도 아니다. 우리는 ㄴ이제 직접적으로 그 변환된 cubemap을 사용할 수 있고 그것을 fragment shader에서 convolute하고, 그것의 결과를 모든 6개의 face diretions에 렌더링하는 framebuffer를 사용하여 새로운 큐브맵에 넣을 수 있다. 우리가 이미 equirectangular environment map을 cubemap으로 변환하는 설정을 했으니, 우리는 정확히 같은 접근법이지만, 다른 fragment shader를 쓴다:

environmentMap이 HDR cubemap이고, equirectangular HDR environment map에서 변환된 것이다.

environment map을 convolute하는 많은 방법들이 있지만, 이 튜토리얼에서, 우리는 샘플 방향을 향하는 반구 Ω를 따라 각 큐브맵 texel에 대해 고정된 양의 sample vectors를 생성할 것이고, 그 결과를 평균화한다. 고정된 양의 샘플 벡터들은 균일하게 반구내에서 퍼뜨려질 것이다. 고정된양의 샘플 벡터가 근사라는 것을 고려한다면, 적분은 연속된 함수이고, 이산적으로 그것의 함수를 샘플링 한다는 것에 주목해라. 우리가 사용하는 샘플 벡터가 많으면 많을수록 우리는 그 적분을 더 잘 근사한다.

reflectance equation의 적분 ∫은 작업하기 오히려 어려운 solid angle dw의 주변을 회전한다. solid angle dw에 대해 적분하는 대신에, 우리는 그것의 동일한 spherical coordinates θ와 ɸ에 대해 적분할 것이다.

구면좌표계 떠올리기.
우리는 polar azimuth ɸ각을 0 ~ 2π 사이의 반구의 고리 주변을 샘플링하기 위해 사용한다.
그리고 그 반구의 증가하는 고리를 샘플링하기 위해 0과 1/2π사이의 inclination zenith θ각을 사용한다. (내가 올린 자료에서 theta와 phi가 바뀐 것이다. 내가 올린 좌표는 좌표계도 또한 다르니 유의하자) 이것은 우리에게 업데이트된 reflectance integral을 줄 것이다:




그 적분을 푸는 것은 반구 Ω내에서 고정된 양의 이산 샘플을 취하는 것을 요구하고, 그것들의 결과를 평균화한다. 이것은 적분을 리만 합을 기반으로 다음의 이산 버전으로 바꾼다. 각 구면좌표계에 개별적으로 n1과 n2개의 discrete samples들이 주어진다면:



우리가 두 spherical values를 이산적으로 샘플링하기 때문에, 각 샘플은 위에있는 이미지가 보여주듯이 반구내에서 한 지역을 근사하거나 또는 평균화할 것이다. (구면체의 일반적인 특성 때문에) 반구의 이산 샘플 면적은 zenith angle θ가 커질수록 더 작아진다. 샘플 지역들이 중앙 꼭대기에 수렴하기 때문이다. 그 더 작은 지역들을 보상하기 위해, 우리는 추가된 sin을 명확히하여 sinθ로 그 지역을 스케일링하여 그것의 보상에 가중치를 준다.

각 fragment invocation에 대해 적분의 구면 좌표계가 주어졌다면 이산적으로 반구를 샘플링하는 것은 다음의 코드로 바뀐다:


#version 330 core
out vec4 FragColor;
in vec3 localPos;

uniform samplerCube environmentMap;
const float PI = 3.14159265359;

void main()
{
 // the sample direction equals the hemisphere's orientation
 vec3 noraml = normalize(localPos);
 
 vec3 irradiance = vec3(0.0);

 vec3 up = vec3(0.0, 1.0, 0.0);
 vec3 right = cross(up, normal);
 up = cross(normal, right);

 float sampleDelta = 0.025;
 int nrSamples = 0.0;
 for(float phi = 0.0; phi < 2.0 * PI; phi += sampleDelta)
 {
  for(float theta = 0.0; theta < 0.5 * PI; theta += sampleDelta)
  {
   // spherical to cartesian (in tangent space)
   vec3 tangentSample = vec3(sin(theta) * cos(phi), sin(theta) * sin(phi), cos(theta));

   // tangent space to world
   vec3 sampleVec = tangenSample.x * right + tangenSample.y * up + tangentSample.z * N;

   irradiance += texture(environmentMap, sampleVec).rgb * cos(theta) * sin(theta);
   nrSamples++;
  }
 }
 irradiance = PI * irradiance * (1.0 / float(nrSamples));

 FragColor = vec4(irradiance, 1.0);
}

우리는 반구를 가로지르기 위해 고정된 sampleDelte detalvalue를 명시한다; sample delta를 감소하거나 증가시키는 것은 그 정확도를 각각 증가시키거나 감소시킨다.

두 개의 루프 내에서, 우리는 3D 카르테시안 샘플 벡터로 바꾸기위해 구면좌표를 취하고, 그 샘플을 tangent에서 world space로 변환하고, HDR 환경맵에 직접 샘플링하기 위해 이 샘플 벡터를 사용한다. 우리는 각 샘플 결과를 irradiance에 더하고, 마지막에 그것을 총 취해진 샘플들의 개수로 나눈다. 이것은 우리에게 평균적으로 샘플링된 irradiance를 준다. 우리는 샘플링된 컬러값을 더욱 큰 각에 대해 약해지는 빛 때문에 cos(theta)로 스케일링하고, 더 높은 위치의 반구 지역에서 더 작은 샘플 면적을 고려하기 위해 sin(theta)로 스케일링한다는 것에 유의해라.

이제 해야할 남은 것은 우리가 이전에 캡쳐된 envCubemap을 convolute할 수 있도록 OpenGL 렌더링 코드를 설정하는 것이다. 처음에 우리는 irradiance cubemap을 만든다 (또 다시, 우리는 이것을 렌더링 루프전에 한 번 해야한다.):

irradiance map은 모든 주변 radiance를 균일하게 평균화하기 때문에, 그것은 많은 높은 frequency details를 갖지 않는다. 그래서 우리는 그 맵을 낮은 해상도 (32x32)에서 저장하고, OpenGL의 linear filtering이 대부분의 일을 하게 한다. 다음으로, 우리는 그 캡쳐 프레임버퍼를 새로운 해상도로 다시 스케일링한다:

convolution shader를 사용하여, 우리는 우리가 environment cubemap을 캡쳐했던 비슷한 방식으로 environment map을 convolute한다:

이제 이 루틴 후에, 우리는 우리가 직접적으로 diffuse IBL를 위해 사용할 수 있는 미리 계산된 irradiance map을 가질 것이다. 만약 우리가 성공적으로 환경 맵을 convoluted했다면, 그 환경 맵을 irradiance map으로 스카이박스의 environment sampler로 바꿔보자:

만약 그것이 environment map의 많이 블러처리된 버정니라면, 너는 성공적으로 그 환경맵을 convolute했다.

PBR and indirect irradiance lighting
그 irradiance map은 reflectance 적분의 모든 주변 indirect light로부터 축적된 diffuse part이다. 그 light가 어떤 직접적인 광원으로부터 오지 않고 주변환경으로부터 온다는 것을 고려하면, 우리는 그 diffuse and specular indirect lighting을 ambient lighting으로 다루어야 한다. 그리고 우리의 이전에 설정한 constant 항을 바꿔야 한다.

처음에 우리는 미리 계산된 irradiance map을 cube sampler로서 추가하도록 하자:

  uniform samplerCube irradianceMap;

scene의 indirect diffuse light의 모든 것을 가진 irradiance map이 주어지면, fragment에 영향을 미치는 irradiance를 얻는 것은 surface의 normal이 주어진 단일의 텍스쳐 샘플만큼 쉽다:

  vec3 ambient = texture(irradianceMap, N).rgb;

그러나, 우리가 reflectance equation의 분리된 버전으로부터 봤듯이 indirect lighting은 diffuse와 specular 둘 다를 포함하기 때문에, 우리는 diffuse part를 그에 따라 가중치를 줄 필요가 있다. 우리가 이전 튜토리얼에서 했던 것과 유사하게, 우리는 그 표면의 indirect reflectance ratio를 결정하기 위해 Fresnel equation을 사용한다. 그리고 우리는 굴절 또는 diffuse ratio를 얻게된다:


vec3 kS = fresnelSchlick(max(dot(N,V), 0.0), F0);
vec3 kD = vec3(1.0) - kS;
vec3 irradiance = texture(irradianceMap, N).rgb;
vec3 diffuse = irradiance * albedo;
vec3 ambient = (kD * diffuse) * ao;

ambient light는 normal N을 향하는 반구내에서 모든 방향으로부터 오기 때문에, Fresnel respone를 결정한 단일의 halfway vector는 없다. 여전히 Fresnel을 시뮬레이션하기 위해, 우리는 normal and view vector사이의 각으로부터 Fresnel을 계산한다. 그러나 일찍이 우리는 Fresnel equation에 입력인 surface의 roughness에 영향을 받는 micro-surface halfway vector를 사용했었다. 우리는 현재 어떠한 roughness를 고려하지 않기 때문에, 그 표면의 반사 비율은 항상 상대적으로 높게 될 것이다. 간접 light는 direct light의 같은 특징을 따르게 된다. 그래서 우리는 더 거친 표면이 표면의 edges에서 덜 강하게 반사하는 것을 기대한다. 우리는 surface의 roughness를 고려하지 않기 때문에, 간접 Fresnel reflection strength는 거친 non-metal surfaces에서 조금 이상해 보인다 (다소 시연 목적으로 과장된):

우리는 그 문제를 Sebastien Lgarde에 의해 설명된 roughness 항을 Fresnel-Schlick 방정식에 넣어서 완화 시킬 수 있다.


vec3 fresnelSchlickRoughness(float cosTheta, vec3 F0, float roughness)
{
 return F0 + (max(vec3(1.0 - roughness), F0) - F0) * pow(1.0 - cosTheta, 5.0);
}

Fresnel respone를 계산할 때 그 표면의 roughness를 고려하여, ambient code는 이렇게 된다:


// ambient lighting (we now use IBL as the ambient term)
vec3 kS = fresnelSchlickRoughness(max(dot(N, V), 0.0), F0, roughness);
vec3 kD = vec3(1.0) - kS;
vec3 irradiance = texture(irradianceMap, N).rgb;
vec3 diffuse = irradiance * albedo;
vec3 ambient = (kD * diffuse) * ao;

너도 볼 수 있듯이, 실제 IBL 연산은 꽤 간단하고, 오직 단일의 cubemap texture lookup만을 요구한다; 대부분의 작업은 미리 연산한 또는 convoluting한 environment map을 irradiance map로 바꾸는데 있다.

만약 우리가 각 구가 수직으로 metallic이 증가하고, 수평으로 roughness value가 증가하는 lighting tutorial의 초기 scene을 가져오고, diffuse IBL을 추가한다면, 그것은 이것처럼 보일 것이다:



그것은 조금 이상해 보인다, 더 metallic한 구가 몇몇의 반사형태가 적절히 metallic surfaces처럼 보이기 시작하라고 요구하기 때문이다 (metallic surfaces는 빛을 diffuse light를 반사하지 않기 때문이다) 그리고 그 순간에 그것은 오직 point light source로부터 오고있는 것이다. 그럼에도 불구하고, 너느 벌써 구들이 환경내에서 좀 더 제자리에 있는 것처럼 느껴지는 것을 구분할 수 있다 (특히 만약 너가 environment maps를 바꾼다면). 그 surface response는 환경의 ambient lighting에 따라서 작동하기 때문이다.

너는 여기에서 이야기된 주제의 완전한 소스코드를 찾을 수 있다. 다음 튜토리얼에서, 우리는 reflectance 적분의 indirect  specular part를 더할 것이다. 거기에서 우리는 PBR의 힘을 보게 될 것이다.

Further reading








댓글 없음:

댓글 쓰기