材质类完成之后,我们制作各种材质的难度大大降低了。我们接下来基于材料类的框架,制作第二种材质——金属。
镜面反射的向量计算
一个顺滑的金属的表面并不会随机反射射来的光线,而是会镜面反射光线。我们在现实中很难看到完美的金属,但你应该对金属的物质有一个基本的视觉直觉,那就是镜面反射可以制造物体的金属感。
要如何生成镜面反射的光线呢,在已知入射光线方向和法线方向之后,一个简单的向量运算即可得到反射光线方向。
如图是一个平面,光线从上方射到平面上,法线方向朝上,红色的向量即是我们要求的向量。我们可以把入射向量的起点放到碰撞点,这样不难看出向量之间的关系:V+ 2B 即是反射光线方向。B可以看作是V在N上的投影的逆向量,又N在我们的设计中是单位向量,我们可以在vec3.h中加入如下类外函数:
vec3 reflect(const vec3& v, const vec3& n) {
// 即 V - 2 *|B|* N
return v - 2*dot(v,n)*n;
}
金属类
在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 {
//通过reflect函数生成反射方向,对v的单位化保证了最终生成的方向向量也是单位向量。
vec3 reflected = reflect(unit_vector(r_in.direction()), rec.normal);
//从碰撞点射出反射光线。
scattered = ray(rec.p, reflected);
attenuation = albedo;
//或许会有用处的保险。但是对于我们的球来说,反射光线和N的夹角不会大于90°。
return (dot(scattered.direction(), rec.normal) > 0);
}
public:
color albedo;
};
更改世界中的物体,我们放入一个金属球:
hittable_list world;
auto material_ground = make_shared<lambertian>(color(0.8, 0.8, 0.0));
auto material_left = make_shared<lambertian>(color(0.7, 0.3, 0.3));
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.5, 0.0, -1.0), 0.5, material_left));
world.add(make_shared<sphere>(point3(0.5, 0.0, -1.0), 0.5, material_right));
你会得到这个:<br />
哑光金属
我们在现实世界中很多情况下无法从金属平面上看到自己清晰的脸的原因是金属会哑光,现代社会中为了防止光污染,很多金属表面都会做哑光处理。
哑光实际上就是把金属表面做成凹凸不平,一定程度上削弱镜面反射把所有光线都反射到同一个方向的讨厌特性,有效避免光污染。
在技术实现的角度上,我们可以通过对镜面反射的最终结果加上一个随机的向量来使光线变得更加混乱,从而模拟哑光的效果。
如图所示,我们使用我们的球面随机向量生成函数,来对最后的结果进行扰动。反映到最终的代码里,有:
class metal : public material {
public:
// 增加一个变量f表示扰动的幅度,对成员变量fuzz进行参数列表赋值的时候,限制扰动程度的上限。
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);
// 对最终的反射光线加上一个球面随机向量,至于这个球的大小,由fuzz决定,它最大可以是1。
scattered = ray(rec.p, reflected + fuzz*random_in_unit_sphere());
attenuation = albedo;
return (dot(scattered.direction(), rec.normal) > 0);
}
public:
color albedo;
// 扰动幅度
double fuzz;
};
来看一下最终效果,修改材质为哑光材质:
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.4, 0.2), 0.4);
课后实践
某些金属艺术品的表面是凹凸不平的鳞片状,这种形状的金属表面光线弹射的法则同样遵循镜面反射原则,但表面的法线会依据某种周期性进行变换,请添加一种这样的金属材质,要求材质构造的时候除了颜色,哑光程度之外,还能改变鳞片的大小。
某种方形鳞片表面金属如下图所示(const int image_width = 1600;
)。无需和图片完全一样,做出鳞片感即可。
参考文献
https://raytracing.github.io/books/RayTracingInOneWeekend.html
参考自《RayTracingInOneWeekend》第9.4节到9.6节。