《Ray Tracing in One Weekend》阅读笔记 - 8、金属

8.1 材料的抽象类

    如果我们想要不同的物体有不同的材料,那么有两种做法,一种是硬编码,在通用材质函数中设置很多个参数,要呈现一种材质只需要将其他一些参数置零,或者将材料抽象成一个类。将材料抽象成一个material类会更加优雅。在这个项目中,材质需要做以下两件事:

  1. 生成散射光线(或者说吸收入射光线)
  2. 如果散射,说明射线应该衰减多少

我们的抽象类如下:

// 网页代码  material.h
#ifndef MATERIAL_H
#define MATERIAL_H
class material {
    public:
        virtual bool scatter(
            const ray& r_in, const hit_record& rec, color& attenuation, ray& scattered
        ) const = 0;
};
#endif

 

 

8.2 一种用于描述射线对象相交的数据结构

    上面的scatter函数中的hit_record参数的使用是为了避免在函数参数列表中使用太多参数,将他们放到一个数据结构中。(当然也可以使用很多参数,只是个人喜好)Hittalbematerials类需要互相知道对方,这将会导致循环引用。在C++中,你只需要提醒编译器指针指向一个类,下面的hittable类中的“calss material”就是这样做的:

// 网页上的代码 hittable.h
#ifndef HITTABLE_H
#define HITTABLE_H
#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;
    }
};
class hittable {
    public:
        virtual bool hit(const ray& r, double t_min, double t_max, hit_record& rec) const = 0;
};
#endif

    我们将在这里设置的是,这种材料将告诉我们光线如何与表面相互作用。当一条光线击中一个物体表面(假设是一个球体),当我们开始在main()中设置球体时,在hit_record中的材质的指针将被设置为指向材质的指针。(When a ray hits a surface (a particular sphere for example), the material pointer in the hit_record will be set to point at the material pointer the sphere was given when it was set up in main() when we start.) 当ray_color()例程获得hit_record时,它可以调用material pointer的成员函数找出散射的光线(如果存在的话)。

    为了实现这个,我们需要在hit_record中返sphere calss的一个对material的引用。See the highlighted:

// 网页上的代码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;
         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;
}

 

 

8.3光散射和反射率建模

    对于Lambertian(diffuse)我们可以通过反射率R,或者(1 - R)的吸收率,或者同时使用这两个特征进行建模。通过对material类的p(ublic继承,我们可以写出一个简单的Lambertian类:

  1. 包含的成员变量:颜色color
  2. 拥有的方法:
    • 带参初始化
    • 判断是否散射(如果是,返回散射的光线和颜色)
      • 计算散射方向:(最初的生成随机漫反射的方法,或自选)= 击中一侧的点上的表面法向量 + 随机生成单位球内的点
      • 生成散射的光线并记录到引用参数中
      • 记录颜色到引用参数中

代码如下:

// 网站上的代码  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 {
            vec3 scatter_direction = rec.normal + random_unit_vector();
            scattered = ray(rec.p, scatter_direction);
            attenuation = albedo;
            return true;
        }
     public:
        color albedo;
};

    注意,我们也可以只使用概率p进行散射,而衰减为albedo/p。你的选择

 

 

8.4镜面光反射

    光滑金属的材料不会随机反射,可以运用向量计算获得从金属镜中反射的光线:

    在这里,我们的n是一个单位向量,而v不一定,由上面的图可知:反射光线:R = V + 2 * B,而B = -(v n),我们将这些获得R的计算步骤写入函数reflect中,有:

// 网站上的代码  vec3.h
vec3 reflect(const vec3& v, const vec3& n) {
    return v - 2*dot(v,n)*n;
}

    这里我们使用上面的函数写一个metal类,还是和上面的Lambertian类的思路一样:

  1. 包含的成员变量:颜色color
  2. 拥有的方法:
    1. 带参初始化
    2. 判断是否散射(如果是,返回散射的光线和颜色)
      1. 计算散射方向:调用reflect函数
      2. 生成散射的光线并记录到引用参数中
      3. 记录颜色到引用参数中
      4. 判断散射的光线是否在反射面一侧,返回true or false

    代码如下:

// 网页上的代码 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 {
            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()函数:

    在判断射线是否击中物体并求交部分:

  • 调用物体的material指针,调用求散射射线函数scatter, 如果散射的射线可见,则递归调用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;
        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);
}

 

 

8.5一个金属球的场景

    现在在场景中添加一些金属球:

// 网页上的代码  main.cc
int main() {
    const auto aspect_ratio = 16.0 / 9.0;
    const int image_width = 384;
    const int image_height = static_cast<int>(image_width / aspect_ratio);
    const int samples_per_pixel = 100;
    const int max_depth = 50;
        std::cout << "P3\n" << image_width << " " << image_height << "\n255\n";

        hittable_list world;
        world.add(make_shared<sphere>(
        point3(0,0,-1), 0.5, make_shared<lambertian>(color(0.7, 0.3, 0.3))));
        world.add(make_shared<sphere>(
        point3(0,-100.5,-1), 100, make_shared<lambertian>(color(0.8, 0.8, 0.0))));
        world.add(make_shared<sphere>(point3(1,0,-1), 0.5, make_shared<metal>(color(.8,.6,.2))));
    world.add(make_shared<sphere>(point3(-1,0,-1), 0.5, make_shared<metal>(color(.8,.8,.8))));

        camera cam;

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

效果如下:

 

 

8.6模糊反射

    我们可以随机化光线反射的方向:在反射光线(下图红色的线,这里为单位长度)上的点做一个圆(下图黑色虚线的圆),在圆内随机选择一个点,让射线与反射面的交点到该店的方向作为反射光的方向(下图蓝色的线)。模糊程度随圆的增大而增大。

    我们可以在metal类中添加一个fuzz变量作为圆的半径,表示模糊程度。问题是,对于大的球体或掠射光线,我们可能会分散到地表以下。我们可以让表面吸收它们。

    更新后的代码如下:

// 网页上的代码 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 {
            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中的)金属中加入0.3和1.0的模糊度来尝试一下:

使用第一种漫反射:

使用第二种漫反射:

使用第三种漫反射:

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值