Search

Defocus Blur

Created
2021/03/30
Tags
Chapter 12
이 강의의 마지막 특징은 Defocus Blur (흐린 초점)이다. 사진사들이 Depth of Field라고 부르는 것과 동일한 용어이니 Defocus Blur라는 용어에만 얽매여선 안 된다. 실제 카메라를 이용할 때 빛을 모아야 상이 맺히는데, 이 때 조그마한 핀홀보다는 큰 홀이 빛을 잘 모으게 된다. 그런데 빛을 모으는 과정 중에서 Defocus Blur가 발생하게 된다. 빛을 모으는 과정 중에 발생한 Defocus Blur는 보이는 모든 것들을 흐릿하게 만든다. 또한 빛을 많이 모으면 모을수록 그 과정에서 Defocus Blur가 더 발생하게 된다. 이렇게 모인 빛은 상을 맺는데 이용되는데, 모은 빛의 양에 따라서 초점을 잡는 거리가 모두 달라진다.
실제로 렌즈를 살펴보았을 때, 모든 빛은 특정 지점에서 시작되어 일정 거리를 갖고 렌즈에 닿아 Image Sensor에서 처리할 수 있도록 한 점으로 모인다. 우리는 초점이 잡혀 상이 맺힐 수 있도록 하는 거리를 Focus Distance (초점 거리)라고 한다. 여기서 사용한 Focus Distance는 앞 서 주의하라고 말한 Focal Length와는 전혀 다른 것이니 주의해야 한다. (Focal LengthImage를 그릴 때 카메라에서 해당 Screen까지의 거리이다.)
카메라를 사용할 때 어디에 초점을 두는지에 바꿀 때마다 렌즈의 움직임을 볼 수 있었던 이유는 실제 카메라의 Focus Distance가 렌즈와 센서 사이의 거리를 통해서 조절 되기 때문이다.
Aperture (개구부)는 렌즈의 크기를 효율적으로 조절할 수 있는 카메라의 부품이다. 실제 카메라에서는 빛이 더 필요하다면 개구부를 더 크게 만들게 되는데, 이럴 수록 흐린 초점이 생길 수 있는 확률이 높아진다. 우리가 코드로 만든 가상의 카메라에서는 정확한 양의 빛이 들어오고 빛이 모자랄 일이 없는 카메라이다. 따라서 Defocus Blur를 원하는 경우에만 Aperture를 이용하면 된다.

1. A Thin Lens Approximation

실제 카메라는 복잡한 구조의 혼합 렌즈를 이용한다. 이를 코드로 구현하려면, 센서, 렌즈, 개구부 순서로 작성하면 된다. 따라서 ray를 어디로 보낼지 정하여 Image를 처리한 후, 만들어진 Image를 다시 뒤집으면 된다. 이와 같이 구현된 경우에는 실제 카메라처럼 Image가 필름에 거꾸로 Projection 되기 때문이다. 하지만 이처럼 구현하는 것이 꽤나 복잡한 과정들을 거치기 때문에 Graphics에 종사하는 사람들은 주로 얇은 렌즈를 사용하여 근사하는 방식을 이용한다.
얇은 렌즈를 사용하여 근사하면, 위 그림과 같은 실제 카메라 내부를 모두 구현할 필요는 없다. 카메라에 보이는 Screen을 그려 내려는 목적과 그 과정을 생각했을 때, 연산이 충분히 무겁기 때문에 복잡한 연산은 최대한 피해야하기 때문이다. 복잡한 연산 대신 렌즈로부터 ray를 만들고, 이를 하나도 놓치지 않고 완벽하게 상을 맺도록 하는 평면으로 바로 보낼 것이다. (이 때 렌즈로부터 상이 맺히는 평면까지의 거리를 focus_dist라고 하겠다.)

2. Generating Sample Rays

이전 Chapter에서 다뤘던 것처럼 raylookfrom 지점으로부터 생성된다. Defocus Blur을 구현하기 위해선 lookfrom을 중심점으로 하는 렌즈의 무작위 내부 지점을 잡아 생성된 ray를 이용하면 된다. 이 때 lookfrom을 중심점으로 하는 렌즈의 반지름이 크다면 더 흐릿한 Image를 만들 것이고, 렌즈의 반지름이 작다면 조금은 덜 흐릿한 Image를 만들어 낼 것이다.
즉, 흐릿하지 않은 일반적인 카메라는 lookfrom을 중심점으로 하는 반지름 0짜리 렌즈를 이용하는 것이라 볼 수 있다.
따라서 모든 ray는 렌즈의 중심점인 lookfrom으로부터 생성되는 것으로 이해하면 된다. (렌즈 의 무작위 내부 지점도 lookfrom으로부터 생성되므로) 렌즈의 무작위 점을 생성하는 코드는 vec3.h에 정의하면 된다.
#ifndef VEC3_H # define VEC3_H # include "rtweekend.h" # include <cmath> # include <iostream> using std::sqrt; class vec3 { public: vec3() : e{0,0,0} {} vec3(double e0, double e1, double e2) : e{e0, e1, e2} {} double x() const { return e[0]; } double y() const { return e[1]; } double z() const { return e[2]; } vec3 operator-() const { return vec3(-e[0], -e[1], -e[2]); } double operator[](int i) const { return e[i]; } double& operator[](int i) { return e[i]; } vec3& operator+=(const vec3 &v) { e[0] += v.e[0]; e[1] += v.e[1]; e[2] += v.e[2]; return *this; } vec3& operator*=(const double t) { e[0] *= t; e[1] *= t; e[2] *= t; return *this; } vec3& operator/=(const double t) { return *this *= 1/t; } double length() const { return sqrt(length_squared()); } double length_squared() const { return e[0]*e[0] + e[1]*e[1] + e[2]*e[2]; } bool near_zero() const { // Return true if the vector is close to zero in all dimensions. const auto s = 1e-8; return ((fabs(e[0]) < s) && (fabs(e[1]) < s) && (fabs(e[2]) < s)); } inline static vec3 random() { return (vec3(random_double(), random_double(), random_double())); } inline static vec3 random(double min, double max) { return (vec3(random_double(min, max), random_double(min, max), random_double(min, max))); } public: double e[3]; }; // Type aliases for vec3 using point3 = vec3; // 3D point using color = vec3; // RGB color // vec3 Utility Functions inline std::ostream& operator<<(std::ostream &out, const vec3 &v) { return out << v.e[0] << ' ' << v.e[1] << ' ' << v.e[2]; } inline vec3 operator+(const vec3 &u, const vec3 &v) { return vec3(u.e[0] + v.e[0], u.e[1] + v.e[1], u.e[2] + v.e[2]); } inline vec3 operator-(const vec3 &u, const vec3 &v) { return vec3(u.e[0] - v.e[0], u.e[1] - v.e[1], u.e[2] - v.e[2]); } inline vec3 operator*(const vec3 &u, const vec3 &v) { return vec3(u.e[0] * v.e[0], u.e[1] * v.e[1], u.e[2] * v.e[2]); } inline vec3 operator*(double t, const vec3 &v) { return vec3(t*v.e[0], t*v.e[1], t*v.e[2]); } inline vec3 operator*(const vec3 &v, double t) { return t * v; } inline vec3 operator/(vec3 v, double t) { return (1/t) * v; } inline double dot(const vec3 &u, const vec3 &v) { return u.e[0] * v.e[0] + u.e[1] * v.e[1] + u.e[2] * v.e[2]; } inline vec3 cross(const vec3 &u, const vec3 &v) { return vec3(u.e[1] * v.e[2] - u.e[2] * v.e[1], u.e[2] * v.e[0] - u.e[0] * v.e[2], u.e[0] * v.e[1] - u.e[1] * v.e[0]); } inline vec3 unit_vector(vec3 v) { return v / v.length(); } inline vec3 random_in_unit_sphere() { while (true) { auto p = vec3::random(-1, 1); if (p.length_squared() >= 1) continue ; return (p); } } vec3 random_unit_vector() { return (unit_vector(random_in_unit_sphere())); } vec3 random_in_hemisphere(const vec3& normal) { vec3 in_unit_sphere = random_in_unit_sphere(); if (dot(in_unit_sphere, normal) > 0.0) // In the same hemisphere as the normal return (in_unit_sphere); return (-in_unit_sphere); } vec3 reflect(const vec3& v, const vec3& n) { return (v - 2 * dot(v, n) * n); } vec3 refract(const vec3& uv, const vec3& n, double etai_over_etat) { auto cos_theta = fmin(dot(-uv,n), 1.0); vec3 r_out_perp = etai_over_etat * (uv + cos_theta * n); vec3 r_out_parallel = -sqrt(fabs(1.0 - r_out_perp.length_squared())) * n; return (r_out_perp + r_out_parallel); } vec3 random_in_unit_disk() { while (true) { auto p = vec3(random_double(-1, 1), random_double(-1, 1), 0); if (p.length_squared() >= 1) continue ; return (p); } } #endif
C++
정의한 random_in_unit_diskcamera 객체 내부에서 이용하므로 이를 camera.h에 작성한다.
#ifndef CAMERA_H # define CAMERA_H # include "rtweekend.h" class camera { public: camera(point3 lookfrom, point3 lookat, vec3 vup, double vfov, double aspect_ratio, double aperture, double focus_dist) { auto theta = degrees_to_radians(vfov); auto h = tan(theta / 2); auto viewport_height = 2.0 * h; auto viewport_width = aspect_ratio * viewport_height; w = unit_vector(lookfrom - lookat); u = unit_vector(cross(vup, w)); v = cross(w, u); origin = lookfrom; horizontal = focus_dist * viewport_width * u; vertical = focus_dist * viewport_height * v; lower_left_corner = origin - horizontal / 2 - vertical / 2 - focus_dist * w; lens_radius = aperture / 2; } ray get_ray(double s, double t) const { vec3 rd = lens_radius * random_in_unit_disk(); vec3 offset = u * rd.x() + v * rd.y(); return ray(origin + offset, lower_left_corner + s * horizontal + t * vertical - origin - offset); } private: point3 origin; point3 lower_left_corner; vec3 horizontal; vec3 vertical; vec3 u, v, w; double lens_radius; }; #endif
C++
수정된 camera 객체를 이용할 수 있도록 main.cpp에서 코드를 수정한다.
#include "rtweekend.h" #include "color.h" #include "hittable_list.h" #include "sphere.h" #include "camera.h" #include "vec3.h" #include "material.h" #include <iostream> color ray_color(const ray& r, const hittable& world, int depth) { hit_record rec; // If we've exceeded the ray bound limit, no more light is gathered. if (depth <= 0) return (color(0, 0, 0)); if (world.hit(r, 0.001, infinity, rec)) { ray scattered; color attenuation; if (rec.mat_ptr->scatter(r, rec, attenuation, scattered)) return (attenuation * ray_color(scattered, world, depth - 1)); return (color(0, 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); const int samples_per_pixel = 100; const int max_depth = 50; // World hittable_list world; auto material_ground = make_shared<lambertian>(color(0.8, 0.8, 0.0)); auto material_center = make_shared<lambertian>(color(0.1, 0.2, 0.5)); auto material_left = make_shared<dielectric>(1.5); auto material_right = make_shared<metal>(color(0.8, 0.6, 0.2), 0.0); world.add(make_shared<sphere>(point3(0.0, -100.5, -1.0), 100.0, material_ground)); world.add(make_shared<sphere>(point3(0.0, 0.0, -1.0), 0.5, material_center)); world.add(make_shared<sphere>(point3(-1.0, 0.0, -1.0), 0.5, material_left)); world.add(make_shared<sphere>(point3(-1.0, 0.0, -1.0), -0.45, material_left)); world.add(make_shared<sphere>(point3(1.0, 0.0, -1.0), 0.5, material_right)); // Camera point3 lookfrom(3, 3, 2); point3 lookat(0, 0, -1); vec3 vup(0, 1, 0); auto dist_to_focus = (lookfrom - lookat).length(); auto aperture = 2.0; camera cam(lookfrom, lookat, vup, 20.0, aspect_ratio, aperture, dist_to_focus); // 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) { color pixel_color(0, 0, 0); for (int s = 0; s < samples_per_pixel; ++s) { auto u = (i + random_double()) / (image_width - 1); auto v = (j + random_double()) / (image_height - 1); ray r = cam.get_ray(u, v); pixel_color += ray_color(r, world, max_depth); } write_color(std::cout, pixel_color, samples_per_pixel); } } std::cerr << "\nDone.\n"; return (0); }
C++
코드를 실행해보면 성공적으로 Defocus Blur가 적용된 것을 볼 수 있다.