0%

【RayTracer】(二十一)混合概率密度

现在我们有了光线在平面随机散射的 pdf 和直接对光源采样的 pdf,接下来我们可以混合这两种 pdf 得到混合概率密度,使用概率密度的好处之一正是 pdf 支持线性组合。

因为我们要混和多种 pdf,最好的方法就是新建一个类去管理他们:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*
* 概率密度函数
*/

#pragma once
#ifndef PDF_H
#define PDF_H

#include "utilities.h"

class pdf {
public:
virtual ~pdf() {}

virtual double value(const vec3& direction) const = 0;
virtual vec3 generate() const = 0;
};

#endif

然后我们将之前的随机散射的 pdf 作为派生类实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 法线周围随机散射的pdf
class cosine_pdf : public pdf {
public:
cosine_pdf(const vec3& w) { uvw.build_from_w(w); }

virtual double value(const vec3& direction) const override {
auto cosine = dot(normalize(direction), uvw.w());
return (cosine <= 0) ? 0 : cosine / pi;
}

virtual vec3 generate() const override {
return uvw.local(random_cosine_direction());
}

public:
onb uvw;
};

然后实现一个向场景中某个物体的方向采样光线的 pdf 类,这样我们可以不只向光源方向采样,还可以支持场景中的其他物体:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 向场景中某个物体方向采样的pdf
class hittable_pdf : public pdf {
public:
hittable_pdf(shared_ptr<hittable> p, const point3& origin) : ptr(p), o(origin) {}

virtual double value(const vec3& direction) const override {
return ptr->pdf_value(o, direction);
}

virtual vec3 generate() const override {
return ptr->random(o);
}

public:
point3 o;
shared_ptr<hittable> ptr;
};

这里我们新调用了 hittable 类中的两个方法 valuerandom,因此要在抽象类中声明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class hittable {
public:
// 纯虚函数,在派生类中实现
// 计算光线与物体的交点
virtual bool hit(ray& r, double t_min, double t_max, hit_record& rec) const = 0;
// 计算物体的包围盒
virtual bool bounding_box(double time0, double time1, aabb& output_box) const = 0;
// 虚函数,不要求所有派生类都实现
// 计算对该物体方向采样的pdf
virtual double pdf_value(const point3& o, const vec3& v) const {
return 0.0;
}
// 生成对该物体方向采样的随机光线
virtual vec3 random(const vec3& o) const {
return vec3(1, 0, 0);
}
};

然后在 xz 平面物体类中实现这两个函数:

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
// xz平面矩形
class xz_rect : public hittable {
public:
...

/**********作为光源平面用到的方法*********/
// 计算随机采样的pdf
virtual double pdf_value(const point3& origin, const vec3& v) const override {
hit_record rec;
if (!this->hit(ray(origin, v), 0.001, infinity, rec))
return 0;
// 光源平面面积
auto area = (x1 - x0) * (z1 - z0);
// 光源采样点到着色点的距离平方
auto distance_squared = rec.t * rec.t * v.length_squared();
// 光线和光源平面法线cos
auto cosine = fabs(dot(v, rec.normal) / v.length());
// 概率密度
return distance_squared / (cosine * area);
}

// 随机采样一点,作为随机采样的方向
virtual vec3 random(const point3& origin) const override {
auto random_point = point3(random_double(x0, x1), k, random_double(z0, z1));
return random_point - origin;
}

...
};

然后开始实现混合 pdf 类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 混合pdf
class mixture_pdf : public pdf {
public:
mixture_pdf(shared_ptr<pdf> p0, shared_ptr<pdf> p1) {
p[0] = p0;
p[1] = p1;
}

virtual double value(const vec3& direction) const override {
return 0.5 * p[0]->value(direction) + 0.5 * p[1]->value(direction);
}

virtual vec3 generate() const override {
if (random_double() < 0.5)
return p[0]->generate();
else
return p[1]->generate();
}

public:
shared_ptr<pdf> p[2];
};

我们这里只是简单的把两个 pdf 平均起来。

然后修改 ray_color 函数:

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
// 得到光线颜色
color ray_color(
ray& r, const color& background, const hittable& world,
shared_ptr<hittable>& lights, int depth, double RR) {
hit_record rec;

// 光线弹射指定次数后开始用RR算法终止递归
if (depth < 0 && random_double() >= RR) return color(0, 0, 0);

// 如果光线没有打到任何物体,返回背景颜色
// 这里的t的下界设为0.001是为了防止一些光线弹射到物体上得到的t非常接近0,比如可能出现0.000001这样的值
if (!world.hit(r, 0.001, infinity, rec))
return background;

// 根据物体材质得到光线传播方向、反射率及自发光颜色
ray scattered;
color albedo;
color emitted = rec.mat_ptr->emitted(r, rec, rec.u, rec.v, rec.p);
// 采样光线的概率密度
double pdf_val;

// 对于光源,不会发生散射,返回光源颜色
if (!rec.mat_ptr->scatter(r, rec, albedo, scattered, pdf_val))
return emitted;

// 混合 pdf
auto p0 = make_shared<hittable_pdf>(lights, rec.p);
auto p1 = make_shared<cosine_pdf>(rec.normal);
mixture_pdf mixed_pdf(p0, p1);
// 使用混合pdf采样光线
scattered = ray(rec.p, mixed_pdf.generate(), r.time());
pdf_val = mixed_pdf.value(scattered.direction());

// 渲染方程
return emitted
+ albedo * rec.mat_ptr->scattering_pdf(r, rec, scattered)
* ray_color(scattered, background, world, lights, depth - 1, RR) / pdf_val / RR;
}

然后修改主函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/*******创建场景*******/
hittable_list world;
shared_ptr<hittable> lights;

...

int sence = 6;
switch (sence) {
...
case 6: // Cornell Box 场景
world = cornell_box();
lights = make_shared<xz_rect>(213, 343, 227, 332, 554, shared_ptr<material>());
aspect_ratio = 1.0;
image_width = 600;
samples_per_pixel = 10000;
min_bounce = 95;
background = color(0, 0, 0);
lookfrom = point3(278, 278, -800);
lookat = point3(278, 278, 0);
vfov = 40.0;
break;
...
}

得到的效果:

CornellBoxFinal

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

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