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.
This commit is contained in:
dvdrw 2023-12-03 21:20:49 +01:00
parent e7bc9882e6
commit 3827be61ac
Signed by: dvdrw
GPG Key ID: 4756FA53D8797D7F
3 changed files with 88 additions and 23 deletions

View File

@ -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

1
README
View File

@ -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.

View File

@ -1,7 +1,36 @@
#include "../include/raytrace.hpp"
#include "../include/objects.hpp"
#include <math.h>
#ifdef RANDOM_SAMPLING
#include <random>
#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<Renderable*>& scene,
const std::vector<Renderable*>& 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;