[42cursurs]Ray Tracing in One Weekend 6-Surface Normals and Multiple Objects(2)

이상헌·2020년 10월 15일
0

Front Faces Versus Back Faces

광선이 쏘아져 부딪친 물체의 표면에 대해 외면내면 중 어느 측면에서 광선이 쏘아졌는지는 중요한 요소다. 광선이 쏘아진 측면에 따라 물체를 다르게 rendering해야하기 때문이다.(ex: 문자가 쓰여진 종이의 양면, 내면과 외면을 가진 유리구슬 등) 이번에할 작업은 광선이 물체표면에 대해 어느 측면에서 쏘아졌는지를 판단하는 것이다. 이를 위해 먼저 교차점 P에 대한 normal의 방향을 결정해야한다.

교차점 P에 대해 normal은 외면과 내면 방향을 향하는 두 가지 normal이 있을 수 있다. 원문에서는 normal을 결정하는 두 가지 방법을 제시하고 선호하는 방법을 쓰라고한다.

첫째로 normal이 항상 외면을 가리키도록할 수 있다. 현재 쓰고 있는 방법이다. 저번에 작성한 sphere.h에서 normal에 대해 아래와 같이 코드를 작성했다.

rec.normal = (rec.p - center) / radius;

이 경우 normal의 방향을 결정하는 rec.p - center 부분을 보면 생성되는 normal은 항상 구의 중심에서 교차점 P를 가리키도록 결정된다. 잘 모르겠다면 벡터의 뺄셈을 공부하자. 이 방법을 사용하면 생성된 normal과 광선의 내적으로 광원의 위치를 특정할 수 있다.

if (dot(ray_direction, outward_normal) > 0.0) {
    // ray is inside the sphere
    ...
} else {
    // ray is outside the sphere
    ...
}

둘째로 normal이 항상 광선과 표면에 대해 반대방향으로 생성할 수 있다. 이 경우 내적만으로 광원의 위치를 특정할 수 없으므로 위치 정보를 front_face 에 저장해 둔다. 여기서는 첫 번째 방법으로 광원의 위치를 판별한다. 이후 판별된 위치를 저장하고 normal을 적절히 결정하도록 했다.

bool front_face;
if (dot(ray_direction, outward_normal) > 0.0) {
    // ray is inside the sphere
    normal = -outward_normal;
    front_face = false;
} else {
    // ray is outside the sphere
    normal = outward_normal;
    front_face = true;
}

아래는 두 번째 방법을 적용한 코드다.

hittable.h

struct hit_record {
    point3 p;
    vec3 normal;
    double t;
    bool front_face; // 광원의 표면에 대한 위치(내면 or 외면)

    inline void set_face_normal(const ray& r, const vec3& outward_normal) {// normal을 결정한다.
    	// 광선과 구의 중심에서 교차점을 향하는 normal의 내적으로 
        // 광원의 위치를 특정한다.
        front_face = dot(r.direction(), outward_normal) < 0;
        // 광원의 위치에 따라 normal을 결정한다.
        normal = front_face ? outward_normal :-outward_normal;
    }
};

sphere.h

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);
            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);
            return true;
        }
    }
    return false;
}

A List of Hittable Objects

hittable 객체가 여럿일 경우 관리할 수 있는 list 객체를 작성한다.

hittable_list.h

#ifndef HITTABLE_LIST_H
#define HITTABLE_LIST_H

#include "hittable.h"

#include <memory>
#include <vector>

using std::shared_ptr;
using std::make_shared;

class hittable_list : public hittable {
    public:
        hittable_list() {}
        hittable_list(shared_ptr<hittable> object) { add(object); }

        void clear() { objects.clear(); }
        void add(shared_ptr<hittable> object) { objects.push_back(object); }

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

    public:
        std::vector<shared_ptr<hittable>> objects;
};

bool hittable_list::hit(const ray& r, double t_min, double t_max, hit_record& rec) const {
// 광선 r과 list에 있는 hittable 객체들의 교차여부를 모두 확인한다.
    hit_record temp_rec;
    // 확인한 객체 중 교차하는 개체가 있는 지 저장하는 변수
    bool hit_anything = false;
    // 광선 r과 교차하는 객체 중 가장 가까운 객체의 거리
    auto closest_so_far = t_max;

    for (const auto& object : objects) {
        if (object->hit(r, t_min, closest_so_far, temp_rec)) {
            hit_anything = true;
            closest_so_far = temp_rec.t;
            rec = temp_rec;
        }
    }

    return hit_anything;
}

#endif

Some New C++ Features

c++의 shared ptr, vector에 관한 이야기인 듯하다. c++로 코딩할 거 아니니까 그냥 넘어가자.

Common Constants and Utility Functions

편의성을 위해 상수와 함수를 정의한 rtweekend.h헤더파일을 작성한다.

rtweekend.h

#ifndef RTWEEKEND_H
#define RTWEEKEND_H

#include <cmath>
#include <limits>
#include <memory>


// Usings

using std::shared_ptr;
using std::make_shared;
using std::sqrt;

// Constants

const double infinity = std::numeric_limits<double>::infinity();
const double pi = 3.1415926535897932385;

// Utility Functions

inline double degrees_to_radians(double degrees) {
    return degrees * pi / 180.0;
}

// Common Headers

#include "ray.h"
#include "vec3.h"

#endif

main.cc

#include "rtweekend.h"

#include "color.h"
#include "hittable_list.h"
#include "sphere.h"

#include <iostream>
color ray_color(const ray& r, const hittable& world) {
    hit_record rec;
    if (world.hit(r, 0, infinity, rec)) {
        return 0.5 * (rec.normal + color(1,1,1));
    }
    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);
}
아래는 위의 내용들을 적용한 새 `main.cc`다.
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);

	// 객체들의 list
    // World
    hittable_list world;
    // hittable_list 에 큰 구와 작은 구를 추가한다.
    world.add(make_shared<sphere>(point3(0,0,-1), 0.5));
    world.add(make_shared<sphere>(point3(0,-100.5,-1), 100));

    // 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);
            color pixel_color = ray_color(r, world);
            write_color(std::cout, pixel_color);
        }
    }

    std::cerr << "\nDone.\n";
}	

아래와 같은 결과를 출력한다.

profile
배고픈 개발자 sayi입니다!

0개의 댓글