想看原书可以看本系列的第一篇《Ray Tracing in a Weekend》学习笔记01
9. Metal
9.1 An Abstract Class for Materials
对于本项目,材料需要做两件事情一是产生散射射线(或者说它吸收了入射射线)二是如果是散射,说出应将射线衰减多少。
//[material.h] The material class
#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
9.2 A Data Structure to Describe Ray-Object Intersections
hit_record是为了避免使用一堆参数,因此我们可以在其中填充所需的任何信息。我只需告诉编译器,这个指针是一个类。像下面代码这样:
//[hittable.h] Hit record with added material pointer
#include "rtweekend.h"
#include "ray.h"
class material;
struct hit_record {
point3 p;
vec3 normal;
shared_ptr<material> mat_ptr;//
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;
}
};
我们在这里设置的是材料将告诉我们射线如何与表面相互作用。hit_record只是将一堆参数填充到结构中的一种方法。因此我们要将它们作为一组数据发送。当光线撞击表面(例如特定的球体)时,hit_record中的材质指针将被设置为指向在我们开始时在main()中设置该球体时所给定的材质指针。 当ray_color()例程获取hit_record时,它可以调用材质指针的成员函数来找出散射的光线(如果有)。
为此我们必须对在hit_record中返回的球形类的材料进行引用。
//[sphere.h] Ray-sphere intersection with added material information
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;
};
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;
}
9.3 Modeling Light Scatter and Reflectance
前面已经有了Lambertian反射,它既可以始终散射并通过其反射率R进行衰减,也可以不进行散射但吸收射线的1-R参数进行散射或者可以混合这些策略。针对Lambertian材料,可以得到下面这个类:
//[material.h] The lambertian material class
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;
};
ps:这里我们也可以仅以某个概率p进行散射并衰减为albedo/p。
9.4 Mirrored Light Reflection
对于光滑的金属,射线不会被随机散射。这里涉及到向量的问题。
如图所示,反射的方向是v+2b。n是单位向量,为该点的法向量,但v不是单位向量。b的长度应该是v·n。相当于v在n上的投影,但方向是与b方向相反,所以需要添加一个负号。
//[vec3.h] vec3 reflection function
vec3 reflect(const vec3& v, const vec3& n) {
return v - 2*dot(v,n)*n;
}
金属材料就只是使用该点的反射光线:
//[material.h] Metal material with reflectance function
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.c] Ray color with scattered reflectance
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;
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);
}
9.5 A Scene with Metal Spheres
//[main.cc] Scene with metal spheres
...
#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";
}
9.6 Fuzzy Reflection
还可以通过使用小球体,并为射线选择新的端点,以此来随机化反射方向:
球体越大,反射将变得越模糊。 这建议添加一个模糊度参数,该参数仅是球体的半径(因此零是没有扰动)。 要注意的是,对于大球体或掠食性射线,我们可能会散射到表面以下。 我们可以让表面吸收那些。
//[material.h] Metal material fuzziness
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;
};
我们可以通过在金属上添加模糊度0.3和1.0来进行尝试:
//[main.cc] Metal spheres with fuzziness
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);
...
}