Search

Adding a Sphere

Created
2021/03/25
Tags
Chapter 5
이전 Chapter에서는 ray 객체를 정의하고 Project Space에 대한 Vector들을 통해 ray_color라는 함수를 이용하여 Pixel의 색상을 결정해보았다.
이번 Chapter에서는 하나의 Object를 추가해보자. 일반적으로는 Ray TracerSphere (구체)를 많이 이용하는데, 이는 ray가 구체에 닿았을때에 대한 계산이 상당히 직관적이어서 그렇다.

1. Ray-Sphere Intersection

우리는 학교에서 기하학을 배웠다면 구의 방정식을 알고 있을 것이다. 구의 중심점으로부터 반지름을 RR이라고 했을 때, x2+y2+z2=r2x^2 + y^2 + z^2 = r^2가 구의 방정식이다.
구의 방정식을 통해서 특정 점이 구의 안에 있는지, 밖에 있는지, 걸쳐 있는지 알 수 있다. 만일 (x,y,z)(x, y, z) 좌표가 구 위에 걸쳐 있다면, x2+y2+z2=r2x^2 + y^2 + z^2 = r^2가 된다. 또한 주어진 좌표 (x,y,z)(x, y ,z) 좌표가 구 안에 존재한다면, x2+y2+z2<r2x^2 + y^2 + z^2 < r^2가 됨을 알 수 있고, 구의 밖에 존재한다면 x2+y2+z2>r2x^2 + y^2 + z^2 > r^2임을 알 수 있다.
일반적으로 주어지는 x2+y2+z2=r2x^2 + y^2 + z^2 = r^2는 구의 중심점이 (0,0,0)(0, 0, 0)에 해당할 때의 방정식이다. 따라서 구의 중심점이 (Cx,Cy,Cz)(C_x, C_y, C_z)라고 한다면, 구의 방정식은 다음과 같이 나타난다.
(xCx)2+(yCy)2+(zCz)2=r2(x - C_x)^2 + (y - C_y)^2 + (z - C_z)^2 = r^2
Graphics에서는 이러한 공식들을 대체적으로 Vector의 형태로 나타나기 때문에 공식에서 주어진 xx, yy, zz에 대한 식들은 vec3 Class로 나타낼 수 있다. 예를들어 중심점 (Cx,Cy,Cz)(C_x, C_y, C_z)CC로, 점 (x,y,z)(x, y, z)PP로 표현할 때 주어진 두 점에 대한 VectorPCP-C가 된다. 주어진 Vector를 구의 방정식 형태로 나타내기 위해서 PCP-CPCP-CDot Product하면 다음과 같이 나타난다.
(PC)(PC)=(xCx)2+(yCy)2+(zCz)2(P-C) \cdot (P-C) = (x - C_x)^2 + (y - C_y)^2 + (z - C_z)^2
(xCx)2+(yCy)2+(zCz)2(x - C_x)^2 + (y - C_y)^2 + (z - C_z)^2R2R^2였으므로 (PC)(PC)(P - C) \cdot (P - C) 역시 R2R^2이 된다.
구의 방정식을 Vector로 표현 했기 때문에 우리는 구체 위의 임의의 점을 PP라고 했을 때 ray가 구체에 닿는지 확인을 하는 것이 가능하다. 이 때 ray는 저번 Chapter에서 다뤘던 식처럼 시작점 AA에 대해 방향 bbParameter tt를 이용하여 A+tbA+tbP(t)P(t)로 표현할 수 있었다. 만일 ray가 구체에 닿는다면, P(t)P(t)는 구의 방정식을 만족할 것이다. (즉, 구의 방정식을 만족한다는 말은 P(t)P(t)가 구체 위의 임의의 점인 PP를 만족하는 것이다.) 따라서 다음과 같은 공식이 성립한다.
(P(t)C)(P(t)C)=r2(P(t) - C) \cdot (P(t) - C) = r^2
위 식을 ray를 구하는 형태로 바꿔보면 (A+tBC)(A+tBC)=r2(A + tB - C) \cdot (A + tB - C) = r^2이 되는데 이를 Vector Algebra의 규칙을 만족시키기 위해 펼쳐놓은 방정식을 좌항으로 모두 옮겨서 표현하면 다음과 같이 나온다.
A,C,BA, C, BVector이고, ttParameter이다.
t2BB+2tB(AC)+(AC)(AC)r2=0t^2B \cdot B + 2tB \cdot (A - C) + (A - C) \cdot (A - C) - r^2 = 0
주어진 식에서 tt를 제외한 모든 값들은 상수로 정해져 있는 2차 방정식의 형태이므로 tt에 대해서 풀어낼 수 있다. 근의 공식 중에 판별식 DD를 통해서 근의 개수를 알아낼 수 있는데, 판별식 DD가 양수라면 근이 2개, 음수라면 근이 0개, 00이라면 근이 1개이다. 구해낸 근의 개수는 Graphics에서 기하학적으로 접근할 수 있다. 아래 그림과 같은 해석이 가능하다.

2. Creating Our First Raytraced Image

이전에 Vector Algebra에 따라서 풀어낸 식을 이용하여 코드를 작성하면 된다. 아래의 코드를 main.cpp에 추가하고, ray_color 함수를 수정한다.
#include "vec3.h" #include "color.h" #include "ray.h" bool hit_sphere(const point3& center, double radius, const ray& r) { vec3 oc = r.origin() - center; auto a = dot(r.direction(), r.direction()); auto b = 2.0 * dot(oc, r.direction()); auto c = dot(oc, oc) - radius * radius; auto discriminant = b * b - 4 * a * c; return (discriminant > 0); } color ray_color(const ray& r) { if (hit_sphere(point3(0, 0, -1), 0.5, r)) return (color(1, 0, 0)); 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++
작성한 코드를 실행하면 자그마한 구체에 빛이 맞는 부분은 빨간색으로 나타나게 된다. (구체는 zz축의 Focal Length에 따라 1-1에 위치해 있다고 가정한다.) 따라서 아래 그림과 같은 형태를 얻을 수 있다.
비록 정말 많이 부족한 형태의 구체이지만 (쉐이딩, 빛의 반사, 여러 Object에 대한 적용 등...) 굉장히 성공적으로 물체를 그래픽으로 담아냈다. ray가 구체에 닿는지 판별하는데 있어서 ray를 쏘는 방향의 Parametertt 값이 양수 (즉, 화면 안에 존재한다)라는 가정하에 진행한 것인데, 구체에 닿는 판별식 자체가 2차 방정식으로 풀어내기 때문에 tt 값이 음수 (즉, 화면 밖에 존재한다)라고 해도 동일한 판별식으로 처리되는 문제가 있다. 물론 tt 값이 음수라는 말은 ray를 뒷 방향으로 쏘았기 때문에 구체가 기존처럼 zz축의 1-1이 아닌 11에 위치한 것으로 정정하여 정상적인 작동을 확인할 수 있다. 여튼 카메라 뒤에 있는 물체에 대해서 카메라 앞에 있는 것과 동일한 상이 잡히는 문제에 대해서는 차후에 해결하니 걱정하지 않아도 된다.