pathtracer.cpp 5.06 KB
Newer Older
TheNumbat's avatar
TheNumbat committed
1
2
3
4

#include "../rays/pathtracer.h"
#include "../rays/samplers.h"
#include "../util/rand.h"
TheNumbat's avatar
TheNumbat committed
5
#include "debug.h"
TheNumbat's avatar
TheNumbat committed
6
7
8
9
10
11
12
13
14

namespace PT {

Spectrum Pathtracer::trace_pixel(size_t x, size_t y) {

    Vec2 xy((float)x, (float)y);
    Vec2 wh((float)out_w, (float)out_h);

    // TODO (PathTracer): Task 1
TheNumbat's avatar
TheNumbat committed
15

TheNumbat's avatar
TheNumbat committed
16
17
18
19
20
    // Generate a sample within the pixel with coordinates xy and return the
    // incoming light using trace_ray.

    // Tip: Samplers::Rect::Uniform
    // Tip: you may want to use log_ray for debugging
TheNumbat's avatar
TheNumbat committed
21

TheNumbat's avatar
TheNumbat committed
22
23
24
25
26
27
28
    // This currently generates a ray at the bottom left of the pixel every time.

    Ray out = camera.generate_ray(xy / wh);
    out.depth = max_depth;
    return trace_ray(out);
}

TheNumbat's avatar
TheNumbat committed
29
30
Spectrum Pathtracer::trace_ray(const Ray &ray) {

TheNumbat's avatar
TheNumbat committed
31
32
    // Trace ray into scene. If nothing is hit, sample the environment
    Trace hit = scene.hit(ray);
TheNumbat's avatar
TheNumbat committed
33
34
    if (!hit.hit) {
        if (env_light.has_value()) {
TheNumbat's avatar
TheNumbat committed
35
36
37
38
39
40
41
42
43
44
45
            return env_light.value().sample_direction(ray.dir);
        }
        return {};
    }

    // Set up a coordinate frame at the hit point, where the surface normal becomes {0, 1, 0}
    // This gives us out_dir and later in_dir in object space, where computations involving the
    // normal become much easier. For example, cos(theta) = dot(N,dir) = dir.y!
    Mat4 object_to_world = Mat4::rotate_to(hit.normal);
    Mat4 world_to_object = object_to_world.T();
    Vec3 out_dir = world_to_object.rotate(ray.point - hit.position).unit();
TheNumbat's avatar
TheNumbat committed
46
    const BSDF &bsdf = materials[hit.material];
TheNumbat's avatar
TheNumbat committed
47
48
49
50
51
52
53
54
55
56
57

    // Now we can compute the rendering equation at this point.
    // We split it into two stages: sampling lighting (i.e. directly connecting
    // the current path to each light in the scene), then sampling the BSDF
    // to create a new path segment.

    // TODO (PathTracer): Task 5
    // Instead of initializing this value to a constant color, use the direct,
    // indirect lighting components calculated in the code below. The starter
    // code sets radiance_out to (0.5,0.5,0.5) so that you can test your geometry
    // queries before you implement path tracing.
TheNumbat's avatar
TheNumbat committed
58
59
    Spectrum radiance_out =
        debug_data.normal_colors ? Spectrum(0.5f) : Spectrum::direction(hit.normal);
TheNumbat's avatar
TheNumbat committed
60
    {
TheNumbat's avatar
TheNumbat committed
61
        auto sample_light = [&](const auto &light) {
TheNumbat's avatar
TheNumbat committed
62
63
64
            // If the light is discrete (e.g. a point light), then we only need
            // one sample, as all samples will be equivalent
            int samples = light.is_discrete() ? 1 : (int)n_area_samples;
TheNumbat's avatar
TheNumbat committed
65
            for (int i = 0; i < samples; i++) {
TheNumbat's avatar
TheNumbat committed
66
67
68
69
70
71

                Light_Sample sample = light.sample(hit.position);
                Vec3 in_dir = world_to_object.rotate(sample.direction);

                // If the light is below the horizon, ignore it
                float cos_theta = in_dir.y;
TheNumbat's avatar
TheNumbat committed
72
73
                if (cos_theta <= 0.0f)
                    continue;
TheNumbat's avatar
TheNumbat committed
74
75
76
77
78

                // If the BSDF has 0 throughput in this direction, ignore it
                // This is another oppritunity to do Russian roulette on low-throughput rays,
                // which would allow us to skip the shadow ray cast, increasing efficiency.
                Spectrum absorbsion = bsdf.evaluate(out_dir, in_dir);
TheNumbat's avatar
TheNumbat committed
79
80
81
                if (absorbsion.luma() == 0.0f)
                    continue;

TheNumbat's avatar
TheNumbat committed
82
83
84
85
                // TODO (PathTracer): Task 4
                // Construct a shadow ray and compute whether the intersected surface is
                // in shadow. Only accumulate light if not in shadow.

TheNumbat's avatar
TheNumbat committed
86
87
                // Tip: when making your ray, you will want to slightly offset it from the
                // surface it starts on, lest it intersect at time=0. Similarly, you may want
TheNumbat's avatar
TheNumbat committed
88
89
                // to limit the ray slightly before it would hit the light itself.

TheNumbat's avatar
TheNumbat committed
90
91
92
                // Note: that along with the typical cos_theta, pdf factors, we divide by samples.
                // This is because we're  doing another monte-carlo estimate of the lighting from
                // area lights.
TheNumbat's avatar
TheNumbat committed
93
94
95
96
97
98
                radiance_out += (cos_theta / (samples * sample.pdf)) * sample.radiance * absorbsion;
            }
        };

        // If the BSDF is discrete (i.e. uses dirac deltas/if statements), then we are never
        // going to hit the exact right direction by sampling lights, so ignore them.
TheNumbat's avatar
TheNumbat committed
99
100
        if (!bsdf.is_discrete()) {
            for (const auto &light : lights)
TheNumbat's avatar
TheNumbat committed
101
                sample_light(light);
TheNumbat's avatar
TheNumbat committed
102
            if (env_light.has_value())
TheNumbat's avatar
TheNumbat committed
103
104
105
106
107
108
                sample_light(env_light.value());
        }
    }

    // TODO (PathTracer): Task 5
    // Compute an indirect lighting estimate using pathtracing with Monte Carlo.
TheNumbat's avatar
TheNumbat committed
109

TheNumbat's avatar
TheNumbat committed
110
111
112
    // (1) Ray objects have a depth field; you should use this to avoid
    // traveling down one path forever.

TheNumbat's avatar
TheNumbat committed
113
    // (2) randomly select a new ray direction (it may be reflection or transmittence
TheNumbat's avatar
TheNumbat committed
114
115
    // ray depending on surface type) using bsdf.sample()

TheNumbat's avatar
TheNumbat committed
116
    // (3) potentially terminate path (using Russian roulette). You can make this
TheNumbat's avatar
TheNumbat committed
117
118
119
120
121
    // a function of the bsdf attenuation or track overall ray throughput

    // (4) create new scene-space ray and cast it to get incoming light

    // (5) add contribution due to incoming light with proper weighting
TheNumbat's avatar
TheNumbat committed
122
    return radiance_out;
TheNumbat's avatar
TheNumbat committed
123
124
}

TheNumbat's avatar
TheNumbat committed
125
} // namespace PT