Metal이라는 재질의 사물을 표현해본다.
Material 추상class를 작성한다. 다양한 재질을 구현할 때 쓰기 위함이다.
예제에서 쓰는 Material에는 다음 두 가지가 필요하다.
#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
원문은 c++에 대한 내용이다. 새로 작성한 Material class가 있으니 사물들에 Material 속성을 추가 해주는 작업이다.
#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;
}
};
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;
}
원문... 어렵다. 다행히 코드를 살펴보면 대략적으로 감이 온다. 지난 챕터에 구현한 lamberitian을 구현한다. 그럼 lambertian이라는 Material 속성을 사물에 부여할 수 있게 된다. albedo
는 반사율을 뜻하며 Material를 추상화할 때 나온 감쇠율이다. 이 값은 ray_color
에서 광선이 산란되서 광선의 색상을 수정하는 데 쓰인다.
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 뜻은 잘 모르겠지만 전반사를 의미하는 게 아닐까한다. lambertian은 거친 표면의 재질을 표현했다. 이번엔 매끈한 표면의 재질을 표현하는 법을 살펴보자.
Figure 11: Ray reflection위의 그림에서 붉은색 벡터(이하 red)가 우리에게 필요한 반사된 벡터다. 그림에 따라
red = v + 2b
라는 것을 알 수 있다. 여기서 b의 크기가 v와 n의 내적의 값이라는 것을 알면,
red = v + 2(v⋅n)n
으로 나타낼 수 있다. 내적이 어렵다면 그냥 그러려니하고 넘어가도 좋다.
위의 공식을 사용해 reflect
함수를 구현한다.
vec3 reflect(const vec3& v, const vec3& n) {
return v - 2*dot(v,n)*n;
}
아래는 metal속성의 Material을 구현한 코드다.
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
를 수정한다.
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);
}
metal 재질의 구를 시험해보자.
...
#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) 효과를 줄 수 있다. 반사되는 광선의 끝의 가상의 작은 구 범위에 무작위 점을 향하도록 광선을 생성하는 것이다.
Figure 12: Generating fuzzed reflection rays 이때 가상의 구의 크기가 클수록 더 흐린 효과를 준다.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;// 흐림 정도를 결정하는 변수를 추가한다.
};
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