0%

十一:金属

镜面反射

一个顺滑的金属的表面并不会随机反射射来的光线,而是会镜面反射光线。在现实中很难看到完美的金属,但金属的物质有一个基本的视觉直觉,那就是镜面反射可以制造物体的金属感。

要如何生成镜面反射的光线呢,在已知入射光线方向和法线方向之后,一个简单的向量运算即可得到反射光线方向。

image-20220311082447549如图是一个平面,光线从上方射到平面上,法线方向朝上,红色的向量即是我们要求的向量。我们可以把入射向量的起点放到碰撞点,这样不难看出向量之间的关系:V+ 2B 即是反射光线方向。B可以看作是V在N上的投影的逆向量,又N在我们的设计中是单位向量,我们可以在vec3.h中加入如下类外函数:

1
2
3
4
vec3 reflect(const vec3& v, const vec3& n) {
// 即 V - 2 *|B|* N
return v - 2*dot(v,n)*n;
}

金属类

在material.h中添加金属类,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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;
};

在世界中,我们就可以放一个金属球了

1
2
3
4
5
6
7
8
9
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));

image-20220311082857687

亚光金属

在现实世界中很多情况下无法从金属平面上看到自己清晰的脸的原因是金属会哑光,现代社会中为了防止光污染,很多金属表面都会做哑光处理。

哑光实际上就是把金属表面做成凹凸不平,一定程度上削弱镜面反射把所有光线都反射到同一个方向的讨厌特性,有效避免光污染。

在技术实现的角度上,我们可以通过对镜面反射的最终结果加上一个随机的向量来使光线变得更加混乱,从而模拟哑光的效果。

image-20220311082950838

这里使用之前的球面随机向量生成函数,来对最后的结果进行扰动。反映到最终的代码里,有:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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;
};

同样在世界中加入它,看看效果吧:

1
2
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);

image-20220311083217092

拓展

  1. 某些金属艺术品的表面是凹凸不平的鳞片状,这种形状的金属表面光线弹射的法则同样遵循镜面反射原则,但表面的法线会依据某种周期性进行变换,这样的金属材质,构造的时候除了颜色,哑光程度之外,还能改变鳞片的大小。image-20220311125157869

    代码为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Tmetal : public material {
public:
Tmetal(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(r_in.direction(), rec.normal);
attenuation = albedo;
//取法线的第一位小数后的部分,通过控制乘10还是乘更大的数可以控制鳞片大小
vec3 x = rec.normal * 10;
int a = x.x();
int b = x.y();
int c = x.z();
vec3 re = vec3(x.x() - a, x.y() - b, 0);
//将反射光线加上使用偏移量乘以上面得到的向量,周期为0.1(取决于前面法向量乘以的数)。
scattered = ray(rec.p, reflected + fuzz * vec3(re.x(), re.y(), re.z()));
return (dot(scattered.direction(), rec.normal) > 0);
}
public:
color albedo;
//光线偏向法线的程度,越大越偏离镜面反射。
double fuzz;
};
  1. 光栅化中有金属材质吗?它能够映射出其他物体吗?如果不能,游戏中的是如何实现的呢?

参考文献

https://raytracing.github.io/books/RayTracingInOneWeekend.html

参考自《RayTracingInOneWeekend》第9.4节到9.6节。