레이 트레이싱 구현기 2-2. Pinhole Camera의 구조, 원리 그리고 구현

Plato·2023년 5월 29일
0

저번 글을 통해, 핀홀 카메라의 구조와 원리 그리고 구현해야 할 내용에 대해 살펴봤다. 이제 삼차원 실공간에 존재하는 대상을 이차원 평면에 대응시켜 줄 가상 카메라에 대해 더 살펴본 뒤 레이 트레이싱의 종류를 알아보자.

가상 카메라의 구현

가상 카메라의 구조와 원리

가상 카메라의 구조와 원리는 아래와 같다.

출처: scratchapixel

위의 이미지에서, 한 꼭짓점과 eye를 지나는 광선이 canvas와 만나는 점에 해당 꼭짓점을 대응시킴으로써, 삼차원 실공간에 존재하는 꼭짓점을 이차원 평면인 canvas에 그려낸다.

가상 카메라를 실제 카메라와 비교하면, pinhole의 역할을 eye가 그리고 카메라의 필름/센서의 역할을 canvas가 수행한다. 여기에서 eye가 필요한 이유는, canvas의 한 점을 지나는 광선을 고유하게 만들고 싶기 때문이다. eye와 canvas의 점을 지나는 광선을, 해당 point에 도달하는 광선이라 보면, 하나의 광선만 한 점을 지날 수 있게 된다.

여기에서 주목해야 할 부분은, 실제 카메라의 경우 핀홀(eye)이 센서(canvas)의 앞에 위치하고 우리가 구현할 가상 카메라에서는 eye가 canvas의 뒤에 위치한다는 것이다. 왜 이렇게 구현해도 괜찮을까? 저번 글에서 살펴봤듯이, canvas가 어디에 위치하든지 실제 카메라와 동일한 화각(fov)을 갖기만 하면 무방하기 때문이다.

레이 트레이싱 종류

레이트레이싱의 종류에 따라서 가상 카메라를 다르게 구현하기에, 레이트레이싱의 종류를 먼저 알아보자.
레이 트레이싱은 크게 forward ray tracing과 backward ray tracing으로 나뉜다.

Forward ray tracing은 광원에서 무수히 많은 광선을 쏜 뒤, 광선을 추적하여 canvas의 어느 점을 지나는지 확인한다. 이를 기반으로, 해당 점의 색을 결정한 뒤, 삼차원 실공간에 존재하는 해당 점을 이산적인 픽셀로 바꾼다.
Backward ray tracing은 역방향으로 광선을 쏜다. 렌더링하고 싶은 픽셀을 선택한 뒤, 해당 픽셀을 canvas 위의 점으로 변환한 뒤 카메라에서 해당 점으로 광선을 쏘고 이를 추적한다.

필자는 backward ray tracing을 선택했다. Backward ray tracing은 렌더링하고 싶은 픽셀이 있으면, 확실하게 해당 픽셀을 '지나는' 광선을 생성할 방법은 잠깐만 고민해봐도 쉽게 떠올릴 수 있는 반면에, forward ray tracing의 경우 특정 픽셀을 '지나는' 광선을 만드는 방법을 떠올리기 어렵기 때문이다.

카메라의 방향

동일한 스펙의 카메라가 동일한 위치에 놓여져있더라도, 바라보는 방향에 따라서 생성되는 이미지는 달라질 것이다. 그렇기에, 카메라의 방향을 나타낼 방법이 필요하다. 카메라 좌표계의 z축이 카메라가 바라보는 방향이 되도록 설정한다.

이 글의 나머지 부분과 다음 글을 통해 아래의 내용을 배울 것이다.

  1. 렌더링하고 싶은 픽셀을 canvas 위의 점으로 변환
  2. canvas 위의 점을 글로벌 좌표계의 점으로 변환
  3. 광선 생성

이산적인 공간의 픽셀 -> 연속적인 공간의 점

우선, 우리가 생성할 이미지는 이산적이다. 픽셀이 연속적으로 존재하지 않고 이산적으로 존재한다. 하지만, 캔버스의 경우 삼차원 실공간의 연속적인 부분 공간이다. 그렇기에, 이산적인 픽셀을 연속적인 공간에 맵핑시켜줄 필요가 있다. 픽셀을 제대로 캔버스에 맵핑시킨다고 한다면, 픽셀은 캔버스 위에서 면적을 갖는 대상으로 맵핑될 것이다. 그리고 이 면적을 지나는 무수히 많은 광선을 쏴서 추적해야 한다. 하지만 이는 너무 비효율적이기 때문에, 픽셀을 캔버스 위의 어떤 한 점에 맵핑시키고 싶다. 픽셀의 x, y 좌표를 그대로 사용해도 큰 문제는 없겠으나, 일반적으로는 pixel_x와 pixel_y에 0.5를 더함으로써 구현한다.

x = pixel_x + 0.5
y = pixel_y + 0.5

축소 공간 생성

계산을 단순하게 하기 위해서 x와 y의 최솟값과 최댓값이 각각 0 그리고 1이 되도록 만든다.

x' = x / (image_resolution_x)
y' = y / (image_resolution_y)

원점 조정

지금까지의 계산을 통해 생긴 캔버스의 경우 왼쪽 위 모서리의 좌표가 (0, 0)이 되고, 오른쪽 아래 모서리의 좌표가 (1, 1)이 된다.
원점을 캔버스의 중앙에 위치하도록 만들면 화각을 설정할 때 편해지기 때문에 중앙에 위치시킬 것이다.

x'' = x' 2 - 1
y'' = (y'
2 - 1) * -1

이때, 픽셀 공간에서는 y축이 아래를 바라보지만 우리는 y축이 위를 바라보는 오른 좌표계를 사용할 것이기에 y에는 -1을 추가로 곱해준다.

화각 및 비율 설정

원하는 화각을 갖는 이미지를 생성하기 위해서는, 캔버스의 크기를 바꾸거나 캔버스와 카메라 사이의 거리를 바꿔줘야 한다.
계산을 단순하게 하기 위해서 카메라가 바라보는 방향으로 1만큼 떨어지도록 캔버스를 위치시키자. 그렇다면 캔버스의 크기를 바꾸는 방식으로만 화각을 조정할 수 있다. 수평 화각이 주어졌다고 하자. 캔버스는 카메라로부터 z축으로 1만큼 떨어져 있고 (0, 0)이 캔버스의 중앙을 나타내기 때문에 캔버스의 최대 x값은 tan(수평_화각)이 된다.

x''' = x'' tan(수평_화각)
y''' = y''
tan(수직_화각)

마무리

위의 과정을 거치고 나면, 픽셀을 캔버스 위의 점에 대응시킬 수 있다. 다음 글에서는 로컬 좌표계에서 광선을 생성하는 방법과 로컬 좌표계의 광선을 글로벌 좌표계의 광선으로 바꾸는 방법을 다뤄보자.

0개의 댓글