본문 바로가기
Dev/Computer Graphics

[Nori] Assignment 4: Distribution과 Whitted-style Ray Tracing #2

by Jino Park 2019. 4. 1.
반응형

--- 2021/6/25 수정 : 코드 관련 부분 삭제

이번 게시글에서는 지난번 게시글에 이어 네 번째 Assignment를 마저 진행해본다.

3. Whitted-style ray tracing

(20-01-07 추가 : 틀렸던 부분이 많아서 수정한 부분이 많음.. 레포지토리 참고하기)

Nori를 같이 진행하고 있는 분이라면 순서가 4번이랑 바뀐 것을 알 수 있다. 나는 실제로 진행 순서를 바꿔서 구현했는데, 왜냐하면 dielectric을 테스트하기 위해서 지금 진행하는 Whitted-style ray tracing이 필요하기 때문이다. 내가 구현한 dielectric을 확인하지도 않고 다음 단계로 넘어가기는 싫어서 이렇게 진행했다..

Whitted-style ray tracing은 Tunner Whitted라는 분이 고안한 ray tracing 방법이다. 요약해서 간단히 설명하면 재귀적으로 일어나는 ray tracing이라고 할 수 있는데, 어떤 경우에 재귀적으로 ray tracing을 해야 할까? 가장 쉬운 예시는 거울을 들 수 있다. 

1
2
3
4
5
6
<mesh type="obj">
    <string name="filename" value="meshes/title.obj"/>
    <bsdf type="diffuse">
        <color name="albedo" value="0.5, 0.5, 0.5"/>
    </bsdf>
</mesh>
cs

 

우리가 이때까지 했던 방식으로 거울을 표현할 수 있을까? 답은 '아니오'이다. 우리가 이때까지 Nori에서 물체의 색을 표현할 때는, 위의 XML와 같이 상수 값을 갖는 반사율을 이용했다. 거울을 생각해보면 저런 표현이 적절하지 않음을 직관적으로 이해할 수 있다. 거울 위의 한 점의 색을 표현하기 위해서, 우리는 실제로 빛을 반사시키고, 그 빛을 추적해 색을 알아내야 한다. 이를 식으로 나타내면 다음과 같다.

 

카메라(c)에서 특정 방향(wc)으로 쏜 ray가 scene의 거울 위의 한 점 x에 부딪힌 상황을 생각해보자. Li(c, wc)는 wc 방향에서 카메라로 들어오는 Radiance를 나타낸다. wr은 카메라에서 나온 ray가 거울에 부딪힌 후에 진행하는 반사각이다. 즉 새로운 ray를 다시 추적하는 것을 위 식으로 알 수 있다. 0.95는 프로그램이 멈추지 않도록 하기 위함이다. 함수의 기댓값을 맞추기 위해 0.95로 나누었다.

 

이제 위의 식을 구현해보자. 일단 먼저 생각해야 하는 것은, 언제 재귀적으로 추적할지를 정하는 것이다. Nori는 ray가 부딪히는 물체의 재질이 diffuse인지, specular인지에 따라 구분했다. 저 둘을 쉽게 설명하자면, diffuse는 목재처럼 어디서나 같은 색으로 보이는, 즉 빛이 난반사하는 재질이고, specular는 금속이나 (위에서 예시로 든) 거울처럼 보는 위치나 광원의 위치에 따라 다르게 보이는 재질이다. Nori에서는 bsdf::isDiffuse()를 통해 그 둘을 구분할 수 있다. 

저 조건문의 몸체 안에서 재귀적으로 Li() 함수를 호출하면 된다. 조건에 걸리지 않는다면 재질이 diffuse인 경우이고, 바로 전에 구현한 부분으로 넘어가면 된다.

 

이제 재귀적으로 만들어진 ray의 방향과 bsdf 값이 필요하다. Bsdf값은 반사나 투과가 되는 빛의 비율이라고 받아들일 수 있다. 이는 Mesh::sample()을 이용해 알 수 있는데, 이 함수에는 특별한 파라미터 BSDFQueryRecord가 필요하다. 

wi만 채워서 Mesh::sample()BSDFQueryRecord를 넘겨주면 함수 안에서 wo, eta, measure를 채워 줄 것이고, 채워진 정보들(특히 wo)를 이용해 재귀적으로 ray를 생성해서 추적할 수 있다. Mesh::sample()은 bsdf 값을 반환해준다. 정리하면 Mesh::sample()을 이용해서 입사광이 어떤 방향으로 얼마나 반사(굴절)되는지와 그 밖의 정보들을 알 수 있다.

bRec을 생성할 때 wi를 local coordinate로 변환하고, ray를 생성할 때는 world coordinate로 변환해서 사용했음을 주의하자.

Mesh::sample()이 채워준 bRec을 이용해 새로운 ref_ray를 생성한 후 Li()를 다시 호출했다. 05263f는 1/0.95를 미리 계산한 값이다. 저번에 했던 구현까지 포함해서, 전체 코드는 아래와 같다. 변경된 점은 39번째 줄부터 50번째 줄이다.

미리 구현되어있는 `mirror.cpp`를 이용해 구현을 잘했는지 확인해볼 수 있었다.

두 Mirror 재질의 공이 cornell box 안에 있다.

4. Dielectrics

 

이제 mirror 말고 새로운 specular 재질인 dielectric을 구현해본다. Dielectric은 내가 이해한 바로는 투명한 물질들을 일컫는데, 블로그에 이에 대해 간략히 설명한 포스팅이 있다. 

Dielectric은 Nori 페이지에 설명이 나와있지 않다. 나는 PBRT를 많이 참고했다.

Dielectric을 구현하기 위해 필요한 몇 가지 함수를 미리 정의하자. 스넬의 법칙을 이용해 빛이 투과하는 각도의 sin값을 구하는 snell_get_sin_t(), 빛이 반사될지 투과될지를 결정하는데 쓰일 프레넬 계수를 구하는 FrDielectric()이다. 함수에 대한 설명은 앞에서 링크된 포스팅으로 충분하다고 본다. 변수 이름의 i와 t는 각각 incident(입사), transmitted(투과)를 의미한다.

FrDielectric()이 반환하는 값이 전체 빛에서 반사되는 빛의 비율이 된다. 스넬의 법칙에서 투과하는 빛의 각도가 90도가 나왔다는 것은 빛이 전반사를 하는 것을 의미한다. 이 경우에는 프레넬 계수를 계산할 필요가 없다.  FrDielectric()의 9번째 줄이 이를 나타낸다.

 

(2018-04-06 수정) fresnel() 함수가 common.cpp에 이미 정의되어 있었다. 구현은 살짝 다르지만 두 함수 다 잘 작동한다.

 

이제 sample() 함수를 구현해보자.

 

우선 입사각의 cos값을 구하고, 입사 매질과 투과 매질, 그리고 법선 벡터를 설정한다. 입사각의 cos은 sample()의 파라미터를 통해 쉽게 구할 수 있다. 나는 빛이 항상 물체의 밖에서 안으로 들어온다고 생각하고 구현해서 eta_im_extIOR로, eta_tm_intIOR로, 그리고 법선 벡터를 (0,0,1)로 설정했다.

 

하지만 입사광이 항상 물체의 밖에서 온다는 보장은 없다. 투명한 물질이라면, 안에서 밖으로 빛이 나갈 수도 있지 않은가? 사실 물체의 안, 밖을 따지는 것도 말이 이상하다. 결국 한 매질에서 다른 매질로 가는 것뿐이다. 엄밀하게 따지면, 입사광이 우리가 ni로 설정한 매질에서 오지 않고 nt로 설정한 매질에서 오는 경우를 보는 것이다. 

 

이런 경우를 어떻게 다룰 것인가?

 

뒤에 나올 프레넬 계수를 계산하기 위해선 cos항이 모두 0 이상이어야 한다. 하지만 위의 그림과 같이 빛이 물질 안에서(일단 이렇게 받아들이자) 밖으로 가는 경우엔 cos값이 음수가 된다. 

 

이런 경우를 다루기 위해서, 본격적인 계산을 시작하기 전 입사각의 cos이 0 이상인지, 혹은 미만인지 확인해야 한다. 0 이상이면 우리가 흔히 생각하는 보통의 경우이고, 0 미만이라면 특별한 조치가 필요하다. 생각을 뒤집어서, 물체의 안이 사실은 밖이고, 물체 밖이 사실은 물체 안이라고 생각하는 것이다. 

 

생각을 뒤집기 위해 수정해야 하는 값은 4개다. 법선 벡터를 뒤집고, 입사각의 cos값을 새로 구하고, ni와 nt의 값을 서로 바꾸는 것이다. 

이제 앞에서 구현한 FrDielectric()을 이용해 빛이 반사될 확률을 구한다. sampler를 이용해 uniform하게 수를 뽑아서 빛을 반사시킬지, 아니면 투과시킬지를 결정한다. 만약 빛을 반사시킬 경우라면 bRec.wo를 적절히 채우면 된다. 함수의 나머지 부분은 mirror.cpp를 참고했다. 반사광의 벡터를 계산하는 부분은 역시 PBRT를 참고했다.

빛을 투과시키는 경우에는 스넬의 법칙을 이용한다. 이번에도 역시 PBRT를 참고해 bRec.wo를 계산하고, radiance를 계산해 반환한다. bRec.eta는 잘 모르겠다..

전체 sample() 코드는 아래와 같다.

왼쪽 공은 dielectric, 오른쪽 공은 mirror

 

링크

나중에 수정이 될 수도 있으니 최신 코드도 참고하기

(20-01-07 추가 : 틀렸던 부분이 많아서 수정한 부분이 많음)

커밋3

커밋4

Assignment 4

반응형