From 3827be61ac441d9a24a34245fe5ea14bd1be0333 Mon Sep 17 00:00:00 2001 From: dvdrw Date: Sun, 3 Dec 2023 21:20:49 +0100 Subject: [PATCH] feat: impl random sampling instead of point light algorithm Random sampling is available when building with `-DRANDOM_SAMPLING', or with `make randsampl'. The sample counts are rather low, so the output is both slow and noisy, which is why it's disabled by default. --- Makefile | 8 +++- README | 1 + src/raytrace.cpp | 102 +++++++++++++++++++++++++++++++++++++---------- 3 files changed, 88 insertions(+), 23 deletions(-) diff --git a/Makefile b/Makefile index e02f44a..a0f6094 100644 --- a/Makefile +++ b/Makefile @@ -19,11 +19,15 @@ bin/raytracer: $(OBJ_FILES) @mkdir -p $(OBJ_DIR) $(CXX) $(CXXFLAGS) -o $@ $^ +.PHONY: randsampl +randsampl: CXXFLAGS += -DRANDOM_SAMPLING +randsampl: bin/raytracer + .PHONY: debug debug: CXXFLAGS += -O0 -g debug: clean bin/raytracer .PHONY: clean clean: - $(RM) -r obj/*.o - $(RM) bin/* + $(RM) obj/*.o + $(RM) bin/raytracer diff --git a/README b/README index b1962c3..3b69204 100644 --- a/README +++ b/README @@ -9,6 +9,7 @@ The raytracer implements the following: - IOR-based refraction - Cook-Torrance BRDF (a few other models are left in the code) - Primitive object materials + - Random sampling (build with `make randsampl' to add fireflies :) You can move around with WASD and SPC/Z; zoom with I/O, change the exposure with +/-, and quit with Q. diff --git a/src/raytrace.cpp b/src/raytrace.cpp index 74ecaeb..2aabebf 100644 --- a/src/raytrace.cpp +++ b/src/raytrace.cpp @@ -1,7 +1,36 @@ #include "../include/raytrace.hpp" #include "../include/objects.hpp" #include + +#ifdef RANDOM_SAMPLING #include +#endif + +#ifdef RANDOM_SAMPLING +#define MAX(l, r) ((l) < (r)) ? (r) : (l) +#define MIN(l, r) ((r) < (l)) ? (r) : (l) + +static inline v3 generate_normal(const v3 &v) +{ + struct h { + choice_t x; + unsigned n; + bool operator<(const h& r) { return x < r.x; } + }; + + h one{v.x, 1}, two{v.y, 2}, three{v.z, 3}; + auto max = MAX(one, MAX(two, three)); + auto min = MIN(one, MAX(two, three)); + unsigned middle = 6 - max.n - min.n; + + v3 normal; + normal.c[max.n] = normal.c[middle]; + normal.c[middle] = -max.x; + normal.c[min.n] = 0; + + return normal; +} +#endif Renderable::Renderable(const Material& specs) : specs(specs) {} @@ -33,6 +62,12 @@ Renderable* shoot_ray_into_objects(const v3& eye, const v3& dir, const std::vect return hit_object; } +#ifdef RANDOM_SAMPLING +std::random_device rd; +std::mt19937 gen(rd()); +std::uniform_real_distribution<> dis(0.0, 1.0); +#endif + v3 ray_trace(const v3& eye, const v3& dir, const std::vector& scene, const std::vector& objects, @@ -56,7 +91,6 @@ v3 ray_trace(const v3& eye, const v3& dir, colour += ((Light*)hit_obj)->specs.colour * 8 * kDistance; if(depth > 0) colour += ray_trace(eye, dir, scene, objects, lights, depth - 1, l) * (1 - kDistance); } else if (hit_type > 0) { - // Figure out which lights are visible from the hit location choice_t dist; v3 loc, normal; v3 light_dir; @@ -64,22 +98,47 @@ v3 ray_trace(const v3& eye, const v3& dir, Light *l = nullptr; + // Figure out which lights are visible from the hit location for (auto* _l : lights) { l = (Light *)_l; const v3 to_light = l->o - hit_loc; light_dir = to_light.norm(); dist = to_light.len(); - shoot_ray_into_objects(hit_loc, light_dir, objects, - dist, loc, normal, type, hit_obj); - // We hit the light directly: shade the pixel. - if (type == -1) { - const choice_t kDistance = to_light.lensquared() / 1000; - colour += cook_torrance(dir, hit_normal, light_dir, hit_obj->specs.specular, - hit_obj->specs.roughness, hit_obj->specs.ior, - hit_obj->specs.colour, l->specs.colour) - * (1 - hit_obj->specs.transparency) / kDistance; + v3 sample_dir = light_dir; +#ifndef RANDOM_SAMPLING + constexpr const int SAMPLE_COUNT = 1; +#endif + +#ifdef RANDOM_SAMPLING + v3 disc_n1 = generate_normal(light_dir).norm(); + v3 disc_n2 = disc_n1.cross(light_dir); + choice_t cone_angle = asin(l->r / sqrt(l->r*l->r + dist*dist)); + + constexpr const int SAMPLE_COUNT = 5; + + for(int i = 0; i < SAMPLE_COUNT; ++i) { + choice_t phi = dis(gen) * 2 * M_PI; + choice_t r = sqrt(dis(gen)) * l->r; + + sample_dir = (((disc_n1 * cos(phi) + disc_n2 * sin(phi)) * r) + to_light).norm(); +#endif + + shoot_ray_into_objects(hit_loc, sample_dir, objects, + dist, loc, normal, type, hit_obj); + + // We hit the light: shade the pixel. + if (type == -1) { + const choice_t kDistance = to_light.lensquared() / 1000; + colour += cook_torrance(dir, hit_normal, light_dir, hit_obj->specs.specular, + hit_obj->specs.roughness, hit_obj->specs.ior, + hit_obj->specs.colour, l->specs.colour) + * (1 - hit_obj->specs.transparency) / (kDistance * SAMPLE_COUNT); + } + +#ifdef RANDOM_SAMPLING } +#endif } // Add the colour from the reflection, if we have enough depth @@ -99,17 +158,18 @@ v3 ray_trace(const v3& eye, const v3& dir, } } - // // Random sample - // std::random_device rd; - // std::mt19937 gen(rd()); - // std::uniform_real_distribution<> dis(0.0, 1.0); - // const v3 y = hit_normal.cross(dir); - // for(int i = 0; i < 2; ++i) { - // const v3 sample_dir = (hit_normal + dir * dis(gen) + y * dis(gen)).norm(); - // choice_t dist = 1; - // const v3 hit_colour = ray_trace(hit_loc, sample_dir, scene, objects, lights, 0, hit_obj, &dist); - // colour += hit_colour / (dist * dist); - // } + +#ifdef RANDOM_SAMPLING + if(kReflectionAtt > 1e-4 && depth > 0) { + const v3 y = hit_normal.cross(dir); + for(int i = 0; i < 3; ++i) { + const v3 sample_dir = (hit_normal + dir * dis(gen) + y * dis(gen)).norm(); + const v3 hit_colour = ray_trace(hit_loc, sample_dir, scene, objects, lights, 0, hit_obj, &dist); + colour += hit_colour * kReflectionAtt / 3; + } + } +#endif + } return colour;