Volumes
9.1 Constant Density Mediums
首先,我们来实现一个密度恒定的volume。穿过volume的光线既有概率在volume内部散射,也有可能直接穿过,如下图所示。当volume的密度更低或volume更薄时,穿过volume的概率更大。此外,光线在volume中行进的距离也影响了光线穿过volume的概率
当光线穿过volume时,可能会在任意位置上发生散射。光线在任意很小的距离$\Delta L$上散射的概率为:
\[probability = C \cdot \Delta L\]其中,$C$与volume的密度成正比。通过微分方程和随机数(为了模拟散射的随机性),可以计算出光线在体积中发生散射的具体距离。如果计算出的距离超出了体积的边界,则认为光线没有散射,即没有“命中”。这样,我们就可以构建出一个派生自hittable
类的constantVolume
类了:
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
#ifndef CONSTANT_VOLUME_H
#define CONSTANT_VOLUME_H
#include "rayTracing.h"
#include "hittable.h"
#include "material.h"
#include "texture.h"
class constantVolume final : public hittable
{
public:
constantVolume(shared_ptr<hittable> boundary, double density, shared_ptr<texture> texture)
: boundary(boundary), negInvDensity(-1 / density), phaseFunction(make_shared<isotropic>(texture))
{
}
constantVolume(shared_ptr<hittable> boundary, double density, const color& albedo)
: boundary(boundary), negInvDensity(-1 / density), phaseFunction(make_shared<isotropic>(albedo))
{
}
bool hit(const ray& r, interval tInterval, hitInfo& info) const override
{
// print occasional samples when debugging. To enable, set enableDebug true
const bool enableDebug = false;
const bool debugging = enableDebug && randomZeroToOne() < 0.00001;
hitInfo info1, info2;
if (!boundary->hit(r, interval::universe, info1))
return false;
if (!boundary->hit(r, interval(info1.t + 0.0001, infinity), info2))
return false;
if (debugging)
std::clog << "\ntMin = " << info1.t << ", tMax = " << info2.t << "\n";
if (info1.t < tInterval.min) info1.t = tInterval.min;
if (info2.t > tInterval.max) info2.t = tInterval.max;
if (info1.t >= info2.t)
return false;
if (info1.t < 0)
info1.t = 0;
double rayLength = r.direction().length();
double distanceInsideBoundary = (info2.t - info1.t) * rayLength;
double hitDistance = negInvDensity * log(randomZeroToOne());
if (hitDistance > distanceInsideBoundary)
return false;
info.t = info1.t + hitDistance / rayLength;
info.position = r.at(info.t);
if (debugging)
{
std::clog << "hitDistance = " << hitDistance << '\n'
<< "info.t = " << info.t << '\n'
<< "info.position" << info.position << '\n';
}
info.normal = vec3(1, 0, 0); // arbitrary
info.frontFace = true; // also arbitrary
info.material = phaseFunction;
return true;
}
aabb boundingBox() const override {return boundary->boundingBox(); }
private:
shared_ptr<hittable> boundary;
double negInvDensity;
shared_ptr<material> phaseFunction;
};
#endif
同时,我们也需要实现一个新的材质类,用于计算各向同性的散射函数,并选择一个均匀的随机方向:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class diffuseLight final : public material {...}
class isotropic final : public material
{
public:
explicit isotropic(const color& albedo) : texture(make_shared<solidColor>(albedo)) {}
explicit isotropic(const shared_ptr<texture>& texture) : texture(texture) {}
bool scatter(const ray& rayIncoming, const hitInfo& info, color& attenuation, ray& rayScattered)
const override
{
rayScattered = ray(info.position, randomVectorOnUnitSphere(), rayIncoming.time());
attenuation = texture->value(info.u, info.v, info.position);
return true;
}
private:
shared_ptr<texture> texture;
};
9.2 Rendering a Cornell Box with Smoke and Fog Boxes
我们将康奈尔盒中的两个方块替换为烟雾,其中一个颜色较深,另一个颜色较浅。
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
#include "rayTracing.h"
#include "bvh.h"
#include "camera.h"
#include "constantVolume.h"
#include "hittable.h"
#include "hittableList.h"
#include "material.h"
#include "quad.h"
#include "sphere.h"
#include "texture.h"
...
void cornellBoxWithSmokes()
{
hittableList world;
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<diffuseLight>(color(7, 7, 7));
world.add(make_shared<quad>(point3(555,0,0), vec3(0,555,0), vec3(0,0,555), green));
world.add(make_shared<quad>(point3(0,0,0), vec3(0,555,0), vec3(0,0,555), red));
world.add(make_shared<quad>(point3(113,554,127), vec3(330,0,0), vec3(0,0,305), light));
world.add(make_shared<quad>(point3(0,555,0), vec3(555,0,0), vec3(0,0,555), white));
world.add(make_shared<quad>(point3(0,0,0), vec3(555,0,0), vec3(0,0,555), white));
world.add(make_shared<quad>(point3(0,0,555), vec3(555,0,0), vec3(0,555,0), white));
shared_ptr<hittable> box1 = box(point3(0,0,0), point3(165,330,165), white);
box1 = make_shared<rotateY>(box1, 15);
box1 = make_shared<translate>(box1, vec3(265,0,295));
shared_ptr<hittable> box2 = box(point3(0,0,0), point3(165,165,165), white);
box2 = make_shared<rotateY>(box2, -18);
box2 = make_shared<translate>(box2, vec3(130,0,65));
world.add(make_shared<constantVolume>(box1, 0.01, color(0,0,0)));
world.add(make_shared<constantVolume>(box2, 0.01, color(1,1,1)));
camera cam;
cam.aspectRatio = 1.0;
cam.imageWidth = 600;
cam.samplesPerPixel = 200;
cam.maxDepth = 50;
cam.background = color(0, 0, 0);
cam.verticalFOV = 40;
cam.lookFrom = point3(278, 278, -800);
cam.lookAt = point3(278, 278, 0);
cam.viewUp = vec3(0,1,0);
cam.defocusAngle = 0;
cam.render(world);
}
int main()
{
switch (8)
{
case 1: bouncingSphere(); break;
case 2: checkeredSphere(); break;
case 3: earth(); break;
case 4: perlinSpheres(); break;
case 5: quads(); break;
case 6: simpleLight(); break;
case 7: cornellBox(); break;
case 8: cornellBoxWithSmokes(); break;
default: ;
}
}
渲染中。。。
本文由作者按照 CC BY 4.0 进行授权