[42cursurs]Ray Tracing in One Weekend 9-Metal

이상헌·2020년 10월 19일
0

Metal이라는 재질의 사물을 표현해본다.

An Abstract Class for Materials

Material 추상class를 작성한다. 다양한 재질을 구현할 때 쓰기 위함이다.
예제에서 쓰는 Material에는 다음 두 가지가 필요하다.

  1. 산란하는(scattered) 광선을 생성하는 기능
  2. 산란 되는 광선의 감쇠 정도

material.h

#ifndef MATERIAL_H
#define MATERIAL_H

#include "rtweekend.h"

struct hit_record;

class material {
    public:
        virtual bool scatter(// 광선이 산란되는 지 판단한다.
            const ray& r_in, const hit_record& rec, color& attenuation, ray& scattered
        ) const = 0;
};

#endif

A Data Structure to Describe Ray-Object Intersections

원문은 c++에 대한 내용이다. 새로 작성한 Material class가 있으니 사물들에 Material 속성을 추가 해주는 작업이다.

hittable.h

#include "rtweekend.h"
#include "ray.h"

class material;

struct hit_record {
    point3 p;
    vec3 normal;
    shared_ptr<material> mat_ptr;// material class 추가
    double t;
    bool front_face;

    inline void set_face_normal(const ray& r, const vec3& outward_normal) {
        front_face = dot(r.direction(), outward_normal) < 0;
        normal = front_face ? outward_normal :-outward_normal;
    }
};

sphere.h

class sphere : public hittable {
    public:
        sphere() {}
        sphere(point3 cen, double r, shared_ptr<material> m)
            : center(cen), radius(r), mat_ptr(m) {};

        virtual bool hit(
            const ray& r, double tmin, double tmax, hit_record& rec) const override;

    public:
        point3 center;
        double radius;
        shared_ptr<material> mat_ptr;// material class 추가
};

bool sphere::hit(const ray& r, double t_min, double t_max, hit_record& rec) const {
    vec3 oc = r.origin() - center;
    auto a = r.direction().length_squared();
    auto half_b = dot(oc, r.direction());
    auto c = oc.length_squared() - radius*radius;
    auto discriminant = half_b*half_b - a*c;

    if (discriminant > 0) {
        auto root = sqrt(discriminant);
        auto temp = (-half_b - root) / a;
        if (temp < t_max && temp > t_min) {
            rec.t = temp;
            rec.p = r.at(rec.t);
            vec3 outward_normal = (rec.p - center) / radius;
            rec.set_face_normal(r, outward_normal);
            rec.mat_ptr = mat_ptr;
            return true;
        }
        temp = (-half_b + root) / a;
        if (temp < t_max && temp > t_min) {
            rec.t = temp;
            rec.p = r.at(rec.t);
            vec3 outward_normal = (rec.p - center) / radius;
            rec.set_face_normal(r, outward_normal);
            rec.mat_ptr = mat_ptr;
            return true;
        }
    }
    return false;
}

Modeling Light Scatter and Reflectance

원문... 어렵다. 다행히 코드를 살펴보면 대략적으로 감이 온다. 지난 챕터에 구현한 lamberitian을 구현한다. 그럼 lambertian이라는 Material 속성을 사물에 부여할 수 있게 된다. albedo는 반사율을 뜻하며 Material를 추상화할 때 나온 감쇠율이다. 이 값은 ray_color에서 광선이 산란되서 광선의 색상을 수정하는 데 쓰인다.

material.h

class lambertian : public material {
    public:
        lambertian(const color& a) : albedo(a) {}

        virtual bool scatter(
            const ray& r_in, const hit_record& rec, color& attenuation, ray& scattered
        ) const override {
            vec3 scatter_direction = rec.normal + random_unit_vector();
            scattered = ray(rec.p, scatter_direction);
            attenuation = albedo;
            return true;
        }

    public:
        color albedo;
};

Mirrored Light Reflection

Mirrored Light Reflection 뜻은 잘 모르겠지만 전반사를 의미하는 게 아닐까한다. lambertian은 거친 표면의 재질을 표현했다. 이번엔 매끈한 표면의 재질을 표현하는 법을 살펴보자.

Figure 11: Ray reflection

위의 그림에서 붉은색 벡터(이하 red)가 우리에게 필요한 반사된 벡터다. 그림에 따라

red = v + 2b

라는 것을 알 수 있다. 여기서 b의 크기가 vn의 내적의 값이라는 것을 알면,

red = v + 2(v⋅n)n

으로 나타낼 수 있다. 내적이 어렵다면 그냥 그러려니하고 넘어가도 좋다.
위의 공식을 사용해 reflect함수를 구현한다.

vec3.h

vec3 reflect(const vec3& v, const vec3& n) {
    return v - 2*dot(v,n)*n;
}

아래는 metal속성의 Material을 구현한 코드다.

material.h

class metal : public material {
    public:
        metal(const color& a) : albedo(a) {}

        virtual bool scatter(
            const ray& r_in, const hit_record& rec, color& attenuation, ray& scattered
        ) const override {
            vec3 reflected = reflect(unit_vector(r_in.direction()), rec.normal);
            scattered = ray(rec.p, reflected);
            attenuation = albedo;
            return (dot(scattered.direction(), rec.normal) > 0);
        }

    public:
        color albedo;
};

위의 내용을 적용해 ray_color를 수정한다.

main.cc

color ray_color(const ray& r, const hittable& world, int depth) {
    hit_record rec;

    // If we've exceeded the ray bounce 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;// 사물의 albedo 값으로 반사된 광선의 색상에 영향을 준다.
        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);
}

A Scene with Metal Spheres

metal 재질의 구를 시험해보자.

main.cc

...

#include "material.h"

...

int main() {

    // 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.7, 0.3, 0.3));
    auto material_left   = make_shared<metal>(color(0.8, 0.8, 0.8));
    auto material_right  = make_shared<metal>(color(0.8, 0.6, 0.2));

    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.5, material_right));

    // Camera

    camera cam;

    // 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";
}

아래와 같이 출력된다. 양쪽의 구가 완전한 구가 아니라 가로가 넓적한 계란처럼 보인다. 이건 카메라 시점(광원)과 뷰 포트가 가까워서 일어나는 현상이다. 카메라를 좀 멀리 두면 나아진다.

Image 11: Shiny metal

Fuzzy Reflection

반사되는 광선 생성에 아래와 같은 변화를 주면 흐린(Fuzzy) 효과를 줄 수 있다. 반사되는 광선의 끝의 가상의 작은 구 범위에 무작위 점을 향하도록 광선을 생성하는 것이다.

Figure 12: Generating fuzzed reflection rays 이때 가상의 구의 크기가 클수록 더 흐린 효과를 준다.

material.h

class metal : public material {
    public:
        metal(const color& a, double f) : albedo(a), fuzz(f < 1 ? f : 1) {}

        virtual bool scatter(
            const ray& r_in, const hit_record& rec, color& attenuation, ray& scattered
        ) const override {
            vec3 reflected = reflect(unit_vector(r_in.direction()), rec.normal);
            scattered = ray(rec.p, reflected + fuzz*random_in_unit_sphere());
            attenuation = albedo;
            return (dot(scattered.direction(), rec.normal) > 0);
        }

    public:
        color albedo;
        double fuzz;// 흐림 정도를 결정하는 변수를 추가한다.
};

main.cc

int main() {
    ...
    // World

    auto material_ground = make_shared<lambertian>(color(0.8, 0.8, 0.0));
    auto material_center = make_shared<lambertian>(color(0.7, 0.3, 0.3));
    auto material_left   = make_shared<metal>(color(0.8, 0.8, 0.8), 0.3);
    auto material_right  = make_shared<metal>(color(0.8, 0.6, 0.2), 1.0);
    ...
}
Image 12: Fuzzed metal
profile
배고픈 개발자 sayi입니다!

0개의 댓글