Search

Rays, a Simple Camera, and Background

Created
2021/03/24
Tags
Chapter 4

1. The ray Class

RT를 구현하는 모든 사람들이 가지고 있는 공통적으로 갖고 있는 것은 ray Classray를 통해서 어떤 색상이 보일지에 대한 계산이다. 다음과 같은 함수가 있다고 생각해보자.
P(t)=A+tBP(t) = A + tB
PP3D 상에 존재하는 직선의 좌표이다. AAray의 기준점이며, BBray의 방향이다. ray Parametertt는 코드 상에서 double로 작성된 실수 (Real Number)이다.
tt에 매 회마다 다른 값을 집어 넣어보면, P(t)P(t)는 ray에 따라서 좌표가 바뀌는 것을 볼 수 있다. 만일 주어진 Parameter tt가 음수로 들어온다면, 3D 상의 직선에서 P(t)P(t)는 어떤 값이든 될 수 있다.
tt값으로 양수만 받을 수 있다면 P(t)P(t)의 좌표는 앞으로만 전진할 수 있으며, 이를 Half-Line 혹은 Half-Ray라고 부른다.
위와 같이 동작하는 rayray.h 라는 Header에 생성해보자. 코드는 아래와 같다.
#ifndef RAY_H # define RAY_H # include "vec3.h" class ray { public: ray() {} ray(const point3& origin, const vec3& direction) : orig(origin), dir(direction) {} point3 origin() const { return orig; } vec3 direction() const { return dir; } point3 at(double t) const { return orig + t*dir; } public: point3 orig; vec3 dir; }; #endif
C++

2. Sending Rays into the Scene

위와 같이 ray Class에 대해 정의가 끝났다면, Ray Tracer를 만들 준비가 되었다. 핵심은 Ray TracerPixel을 통해 ray를 보내고, ray에 방향에 해당하는 Pixel에 대해서 보여지는 색상을 계산하는 것이다. 단계는 아래와 같다.
1.
Eye에서 Pixel까지의 ray를 계산한다.
2.
ray가 교차하는 물체에 대해 인식한다.
3.
물체의 교차 지점에 대해서 색상을 계산한다.
Ray Tracer를 처음 만들 때는 항상 간단한 카메라로 코드를 조작하게 된다. 또한 ray를 인자로 받아 배경색 (단순한 Gradient로 된)을 반환하는 ray_color라는 함수를 함께 만들면 된다.
정사각형 Image를 이용하면 xxyy에 대해서 잦은 Transpose 연산을 취하게 되면서 종종 문제를 경험하게 되므로 정사각형이 아닌 Image를 이용하겠다. 16:9 종횡비가 일반적이므로 이를 이용하겠다.
RenderingImagePixel 크기를 정하는 것 외에도, rayScene을 통과할 수 있도록 가상 Viewport를 설정해야 한다. 이 때 표준 정사각형 Pixel의 간격을 이용하는 경우 Viewport의 종횡비는 RenderingImage의 종횡비와 일치해야 한다.
현재 사용하는 Viewport에 대해서는 두 개의 높이로 고르게 된다.
또한 Projection 되는 면 (Projection Plane)Projection을 하려는 점 (Projection Point)간의 거리도 구해야 한다. 이를 Focal Length라 한다. (Focus Distance와는 명확하게 다르다!)
Eye (카메라의 중심부 혹은 렌즈)의 좌표를 (0,0,0)(0, 0, 0)라고 하자. 이 때의 yy축은 윗 방향, xx측은 우측 방향으로 증가하는 일반적인 Right-Handed Coordinate이라고 해보자. 이 때의 zz축은 화면 안쪽으로 갈수록 음의 방향, 화면 밖으로 갈수록 양의 방향이라고 보면 된다.
이전에 작성했던 코드에서 (0,0)(0, 0)의 좌표가 어디에 해당하는 것일지 잘 생각해보라고 언급한적이 있었다. 위 그림에서 보는 것과 같이 원점 (0,0,0)(0, 0, 0)Screen에 투영될 때 정중앙에 위치하는 것을 볼 수 있다. 이전에 작성했던 코드와 같이 좌측 상단부터 방문해나갈 것이며, 이 때 Screen을 가로지르는 ray의 움직임을 나타내기 위해서 uu, vv라고하는 Offset Vector를 이용하게 된다.
일반적으로는 ray의 방향을 나타내기 위해서 Unit Vector를 이용하지만 여기서는 조금 더 빠른 코딩을 위해서 Unit Vector를 이용하지 않고 그대로 사용한다.
아래 코드에서 ray의 객체 rPixel의 정중앙을 비추는 것으로 어림잡는다. 차후에 Anti-Aliasing을 추가할 것이기 때문에 이 부분에 대해서는 나중에 생각한다. (이전 main.cpp를 조금 수정한 것이다.)
#include "vec3.h" #include "color.h" #include "ray.h" color ray_color(const ray& r) { vec3 unit_direction = unit_vector(r.direction()); auto t = 0.5 * (unit_direction.y() + 1.0); return (1.0 - t) * color(1.0, 1.0, 1.0) + t * color(0.5, 0.7, 1.0); } int main(void) { // Image const auto aspect_ratio = 16.0 / 9.0; const int image_width = 400; const int image_height = static_cast<int>(image_width / aspect_ratio); // Camera auto viewport_height = 2.0; auto viewport_width = aspect_ratio * viewport_height; auto focal_length = 1.0; auto origin = point3(0, 0, 0); auto horizontal = vec3(viewport_width, 0, 0); auto vertical = vec3(0, viewport_height, 0); auto lower_left_corner = origin - horizontal / 2 - vertical / 2 - vec3(0, 0, focal_length); // Render std::cout << "P3\n" << image_width << ' ' << image_height << "\n255\n"; for (int j = image_height - 1; j >= 0; --j) { std::cerr << "\rScanlines Remaining: " << j << ' ' << std::flush; for (int i = 0; i < image_width; ++i) { auto u = double(i) / (image_width - 1); auto v = double(j) / (image_height - 1); ray r(origin, lower_left_corner + u * horizontal + v * vertical - origin); color pixel_color = ray_color(r); write_color(std::cout, pixel_color); } } std::cerr << "\nDone.\n"; return (0); }
C++
이전 작성 내용에서 Image의 크기 및 종횡비, Projection 공간에서 나타나는 Vector와 이에 따른 색상을 결정하는 ray_color라고 하는 함수가 추가 되었다.
실행해보면 아래와 같이 파란색 계열로 Gradient 색상이 잘 나타나는 것을 확인할 수 있다.
여기서 잠깐 ray_color라고 하는 함수를 살펴보면, Linear Blend (선형 혼합, Linear Interpolation, lerp와 동의어)을 사용하는 것을 확인할 수 있다. 이 때 Linear Blend의 처리를 보면 yy축 값에 따라서 색상을 결정하는 것을 볼 수 있는데, 이미 NormalizeVectoryy축 값을 보기 때문에 Vertical Gradient를 볼 수 있다. 따라서 yy축 높이가 높을 수록 파란색 계열을 띄게된다.
온전한 파란색으로만 나타난 것이 아니라 흰색과 함께 Interpolation을 한 것이다. (1y1-1 ≤ y ≤ 1 로 나타나는 값을 Normalize하여 0y10 ≤ y ≤ 1의 값으로 만들어 이에 대해서 파란색을 곱하고, 11에서 Normalize0y10 ≤ y ≤ 1의 값을 뺀 것을 흰색에 곱하여 서로 더한 것을 의미) 즉, Normalize된 값이 11에 가까울수록 파란색을, 00에 가까울수록 흰색을 띈다.
여기서 나타난 Linear Blend의 공식은 일반적으로 다음과 같이 나타난다.
BlendedValue=(1t)×StartValue+t×EndValueBlendedValue = (1- t) \times StartValue + t \times EndValue
만일 Linear Blend를 하지 않은 파란색 계열만 이용하게 된다면 다음과 같은 결과를 볼 수 있다.