文章

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 进行授权