0%

【RayTracer】(十六)参与介质

之前在图形学中我们学过参与介质(participating media)的实现原理,比如烟雾,这一节我们来实现一个恒定密度的参与介质。

1 恒定密度介质

因为我们之前所有的实现都是基于“表面”的,而参与介质是基于“体积”的,这二者之间还是有很大的不同的,但是一个简单的办法是可以把整个参与介质看作是由表面构成的,但这个表面可以在物体内部,只要在一定范围内的点都算作该物体的表面,所以都可以和光线发生作用。

我们在图形学中学过,光线穿过烟雾会在其内部发生各种散射,我们可以用一个概率模型来描述这种过程,如果一个烟雾的密度越大,那么光线在其中发生散射的几率也就越大,如果光线越稀薄,光线就越有可能直接穿过介质而不发生散射,如下图:

fig-2.08-ray-vol

我们认为光线在烟雾中走过 $\Delta L$ 距离发生散射的几率是:
$$
probability = C·\Delta L
$$
其中 $C$ 与介质的密度成正比,于是对于一个随机数就可以用上面的式子计算得到概率,并把这个概率认为是散射发生的距离。如果散射发生的距离大于光线在介质中传播的距离,说明光线没有击中介质,而是直接穿过。

因此一个恒定密度的介质只需要一个密度和边界就可以描述,边界使用另一个物体来确定,相当于该物体形状的烟雾,一个恒定密度的介质类的实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
/*
* 恒定密度的参与介质类
*/
#pragma once
#ifndef CONSTANT_MEDIUM_H
#define CONSTANT_MEDIUM_H

#include "utilities.h"

#include "hittable.h"
#include "material.h"
#include "texture.h"

class constant_medium : public hittable {
public:
constant_medium(shared_ptr<hittable> b, double d, shared_ptr<texture> a)
: boundary(b),
neg_inv_density(-1 / d),
phase_function(make_shared<isotropic>(a))
{}

constant_medium(shared_ptr<hittable> b, double d, color c)
: boundary(b),
neg_inv_density(-1 / d),
phase_function(make_shared<isotropic>(c))
{}

virtual bool hit(
ray& r, double t_min, double t_max, hit_record& rec) const override;

virtual bool bounding_box(double time0, double time1, aabb& output_box) const override {
return boundary->bounding_box(time0, time1, output_box);
}

public:
shared_ptr<hittable> boundary; // 边界
shared_ptr<material> phase_function; // 各向同性材质,保证光线向各个方向等概率均匀散射
double neg_inv_density; // 密度的负倒数
};

bool constant_medium::hit(ray& r, double t_min, double t_max, hit_record& rec) const {
// 用于debug
const bool enableDebug = false;
const bool debugging = enableDebug && random_double() < 0.00001;

// 求光线和边界的两个交点
hit_record rec1, rec2;

if (!boundary->hit(r, -infinity, infinity, rec1))
return false;

if (!boundary->hit(r, rec1.t + 0.0001, infinity, rec2))
return false;

if (debugging) std::cerr << "\nt_min=" << rec1.t << ", t_max=" << rec2.t << '\n';

if (rec1.t < t_min) rec1.t = t_min;
if (rec2.t > t_max) rec2.t = t_max;

if (rec1.t >= rec2.t)
return false;

if (rec1.t < 0)
rec1.t = 0;

// 光线在介质中的距离
const auto ray_length = r.direction().length();
const auto distance_inside_boundary = (rec2.t - rec1.t) * ray_length;
// 光线发生散射的距离,两个相乘的数都是小于1的负数,所以密度越大值越小
const auto hit_distance = neg_inv_density * log(random_double());

// 发生散射的距离大于光线在介质中的距离则没有发生散射,直接穿过介质
if (hit_distance > distance_inside_boundary)
return false;

// 散射发生的位置
rec.t = rec1.t + hit_distance / ray_length;
rec.p = r.at(rec.t);

if (debugging) {
std::cerr << "hit_distance = " << hit_distance << '\n'
<< "rec.t = " << rec.t << '\n'
<< "rec.p = " << rec.p << '\n';
}

// 法线方向这些属性可以随便设置
rec.normal = vec3(1, 0, 0);
rec.front_face = true;
rec.mat_ptr = phase_function;

return true;
}

#endif

上面的实现中我们默认光线一旦出了介质就不会再在介质中弹射了,因此只适用于凸多边形物体,不适用于凹多边形物体。其中控制光线向各个方向等概率散射的材质在 material.h 中定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 各向同性材质
class isotropic : public material {
public:
isotropic(color c) : albedo(make_shared<solid_color>(c)) {}
isotropic(shared_ptr<texture> a) : albedo(a) {}

virtual bool scatter(
ray& r_in, const hit_record& rec, color& attenuation, ray& scattered
) const override {
// 光线向各个方向等概率均匀散射
scattered = ray(rec.p, random_in_unit_sphere(), r_in.time());
attenuation = albedo->value(rec.u, rec.v, rec.p);
return true;
}

public:
shared_ptr<texture> albedo;
};

2 用烟雾渲染 Cornell Box

我们使用上面实现的介质新建一个 Cornell Box 场景:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 烟雾Cornell Box场景
hittable_list cornell_smoke() {
hittable_list objects;

auto red = make_shared<lambertian>(color(.65, .05, .05));
auto white = make_shared<lambertian>(color(.73, .73, .73));
auto green = make_shared<lambertian>(color(.12, .45, .15));
auto light = make_shared<diffuse_light>(color(7, 7, 7));

objects.add(make_shared<yz_rect>(0, 555, 0, 555, 555, green));
objects.add(make_shared<yz_rect>(0, 555, 0, 555, 0, red));
objects.add(make_shared<xz_rect>(113, 443, 127, 432, 554, light));
objects.add(make_shared<xz_rect>(0, 555, 0, 555, 555, white));
objects.add(make_shared<xz_rect>(0, 555, 0, 555, 0, white));
objects.add(make_shared<xy_rect>(0, 555, 0, 555, 555, white));

shared_ptr<hittable> box1 = make_shared<box>(point3(0, 0, 0), point3(165, 330, 165), white);
box1 = make_shared<rotate_y>(box1, 15);
box1 = make_shared<translate>(box1, vec3(265, 0, 295));

shared_ptr<hittable> box2 = make_shared<box>(point3(0, 0, 0), point3(165, 165, 165), white);
box2 = make_shared<rotate_y>(box2, -18);
box2 = make_shared<translate>(box2, vec3(130, 0, 65));

objects.add(make_shared<constant_medium>(box1, 0.01, color(0, 0, 0)));
objects.add(make_shared<constant_medium>(box2, 0.01, color(1, 1, 1)));

return objects;
}

主函数修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int main() {
...
switch (sence) {
...
default:
case 7:
world = cornell_smoke();
aspect_ratio = 1.0;
image_width = 600;
samples_per_pixel = 200;
lookfrom = point3(278, 278, -800);
lookat = point3(278, 278, 0);
vfov = 40.0;
break;
...
}

得到的效果:

CornellBoxSmoke

---- 本文结束 知识又增加了亿点点!----

文章版权声明 1、博客名称:LycTechStack
2、博客网址:https://lz328.github.io/LycTechStack.github.io/
3、本博客的文章部分内容可能来源于网络,仅供大家学习与参考,如有侵权,请联系博主进行删除处理。
4、本博客所有文章版权归博主所有,如需转载请标明出处。