pathtracer.cpp 5.3 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
            return env_light.value().sample_direction(ray.dir);
        }
        return {};
    }

TheNumbat's avatar
TheNumbat committed
40
41
42
43
44
45
    // If we're using a two-sided material, treat back-faces the same as front-faces
    const BSDF &bsdf = materials[hit.material];
    if(!bsdf.is_sided() && dot(hit.normal, ray.dir) > 0.0f) {
        hit.normal = -hit.normal;
    }

TheNumbat's avatar
TheNumbat committed
46
47
48
49
50
51
52
    // 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();

53
54
55
56
    // Debugging: if the normal colors flag is set, return the normal color
    if(debug_data.normal_colors)
        return Spectrum::direction(hit.normal);

TheNumbat's avatar
TheNumbat committed
57
58
59
60
61
62
    // 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
63
64
65
66
    // 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. You should change this to (0,0,0) and accumulate
    // the direct and indirect lighting computed below.
    Spectrum radiance_out = Spectrum(0.5f);
TheNumbat's avatar
TheNumbat committed
67
    {
TheNumbat's avatar
TheNumbat committed
68
        auto sample_light = [&](const auto &light) {
TheNumbat's avatar
TheNumbat committed
69
70
71
            // 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
72
            for (int i = 0; i < samples; i++) {
TheNumbat's avatar
TheNumbat committed
73
74
75
76
77
78

                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
79
80
                if (cos_theta <= 0.0f)
                    continue;
TheNumbat's avatar
TheNumbat committed
81
82
83
84
85

                // 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
86
87
88
                if (absorbsion.luma() == 0.0f)
                    continue;

TheNumbat's avatar
TheNumbat committed
89
90
91
92
                // 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
93
94
                // 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
95
96
                // to limit the ray slightly before it would hit the light itself.

TheNumbat's avatar
TheNumbat committed
97
98
99
                // 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
100
101
102
103
104
105
                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
106
107
        if (!bsdf.is_discrete()) {
            for (const auto &light : lights)
TheNumbat's avatar
TheNumbat committed
108
                sample_light(light);
TheNumbat's avatar
TheNumbat committed
109
            if (env_light.has_value())
TheNumbat's avatar
TheNumbat committed
110
111
112
113
114
115
                sample_light(env_light.value());
        }
    }

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

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

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

TheNumbat's avatar
TheNumbat committed
123
    // (3) potentially terminate path (using Russian roulette). You can make this
TheNumbat's avatar
TheNumbat committed
124
125
126
127
128
    // 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
129
    return radiance_out;
TheNumbat's avatar
TheNumbat committed
130
131
}

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