diff --git a/examples/raytracer/scenes/rings.scene b/examples/raytracer/scenes/rings.scene new file mode 100644 index 0000000..cfcefb5 --- /dev/null +++ b/examples/raytracer/scenes/rings.scene @@ -0,0 +1,17 @@ +# A ring of alternating metal and diffuse spheres around a central emitter. + +camera 0 3.2 8.0 0 0.6 0 30 +background 0.010 0.012 0.020 + +sphere 0.0 -500.0 0.0 500.0 lambert 0.55 0.55 0.58 + +sphere 2.60 0.55 0.00 0.55 metal 0.95 0.80 0.45 0.02 +sphere 1.84 0.55 1.84 0.55 lambert 0.85 0.30 0.30 +sphere 0.00 0.55 2.60 0.55 metal 0.85 0.85 0.90 0.10 +sphere -1.84 0.55 1.84 0.55 lambert 0.30 0.75 0.45 +sphere -2.60 0.55 0.00 0.55 metal 0.60 0.70 0.95 0.02 +sphere -1.84 0.55 -1.84 0.55 lambert 0.80 0.55 0.20 +sphere 0.00 0.55 -2.60 0.55 metal 0.90 0.90 0.90 0.00 +sphere 1.84 0.55 -1.84 0.55 lambert 0.45 0.35 0.80 + +sphere 0.00 1.10 0.00 0.70 light 4.20 3.60 2.60 diff --git a/examples/raytracer/scenes/studio.scene b/examples/raytracer/scenes/studio.scene new file mode 100644 index 0000000..4966ed8 --- /dev/null +++ b/examples/raytracer/scenes/studio.scene @@ -0,0 +1,13 @@ +# Three spheres on a ground plane, lit by one emissive sphere overhead. + +camera 0 1.4 7.5 0 0.7 0 34 +background 0.020 0.028 0.045 + +# centre radius material albedo fuzz +sphere 0.0 -500.0 0.0 500.0 lambert 0.62 0.62 0.60 +sphere -2.1 0.75 0.1 0.75 lambert 0.86 0.24 0.22 +sphere 0.0 0.85 -0.3 0.85 metal 0.92 0.88 0.78 0.03 +sphere 2.1 0.70 0.2 0.70 lambert 0.22 0.42 0.86 +sphere 0.6 0.32 1.5 0.32 metal 0.75 0.75 0.80 0.28 +sphere -0.9 0.28 1.7 0.28 lambert 0.90 0.72 0.20 +sphere 0.0 6.20 1.0 3.20 light 3.60 3.40 3.00 diff --git a/examples/raytracer/src/scene/render.c b/examples/raytracer/src/scene/render.c new file mode 100644 index 0000000..9fe2341 --- /dev/null +++ b/examples/raytracer/src/scene/render.c @@ -0,0 +1,76 @@ +#include "rt_config.h" +#include "rt_scene.h" +#include +#include + +#if defined(HAVE_PTHREAD_CREATE) && defined(HAVE_PTHREAD_H) +#include +#define RT_THREADED 1 +#endif + +static unsigned char to_srgb(double linear) { + double c = linear <= 0.0031308 ? linear * 12.92 : 1.055 * pow(linear, 1.0 / 2.4) - 0.055; + if (c < 0.0) c = 0.0; + if (c > 1.0) c = 1.0; + return (unsigned char)(c * 255.0 + 0.5); +} + +static void render_row(render_job *job, int y) { + rng rs; + rng_seed(&rs, (unsigned long long)y * 0x9E3779B97F4A7C15ULL + 1u); + + for (int x = 0; x < job->width; x++) { + vec3 sum = v3(0.0, 0.0, 0.0); + for (int s = 0; s < job->samples; s++) { + double u = ((double)x + rng_unit(&rs)) / (double)job->width; + double v = 1.0 - ((double)y + rng_unit(&rs)) / (double)job->height; + ray r = camera_ray(job->cam, u, v); + sum = v3add(sum, ray_colour(job->sc, r, job->max_depth, &rs)); + } + vec3 c = v3scale(sum, 1.0 / (double)job->samples); + unsigned char *px = job->rgb + ((size_t)y * (size_t)job->width + (size_t)x) * 3u; + px[0] = to_srgb(c.x); + px[1] = to_srgb(c.y); + px[2] = to_srgb(c.z); + } +} + +#ifdef RT_THREADED +static pthread_mutex_t row_lock = PTHREAD_MUTEX_INITIALIZER; + +static void *worker(void *arg) { + render_job *job = (render_job *)arg; + for (;;) { + pthread_mutex_lock(&row_lock); + int y = job->row_next++; + pthread_mutex_unlock(&row_lock); + if (y >= job->height) break; + render_row(job, y); + } + return NULL; +} +#endif + +int render_run(render_job *job) { + job->row_next = 0; + +#ifdef RT_THREADED + int n = job->threads; + if (n < 1) n = 1; + if (n > 64) n = 64; + if (n > 1) { + pthread_t *ts = (pthread_t *)calloc((size_t)n, sizeof *ts); + if (ts) { + int started = 0; + for (int i = 0; i < n; i++) + if (pthread_create(&ts[i], NULL, worker, job) == 0) started++; + for (int i = 0; i < started; i++) pthread_join(ts[i], NULL); + free(ts); + if (started > 0) return started; + } + } +#endif + + for (int y = 0; y < job->height; y++) render_row(job, y); + return 1; +} diff --git a/examples/raytracer/src/scene/shade.c b/examples/raytracer/src/scene/shade.c new file mode 100644 index 0000000..6454593 --- /dev/null +++ b/examples/raytracer/src/scene/shade.c @@ -0,0 +1,61 @@ +#include "rt_scene.h" +#include + +static int scatter(const hit *h, ray in, rng *rs, ray *out, vec3 *attenuation) { + switch (h->mat.kind) { + case MAT_LAMBERT: { + vec3 dir = v3add(h->normal, rng_unit_vector(rs)); + if (v3len2(dir) < 1e-12) dir = h->normal; + out->origin = h->point; + out->dir = v3norm(dir); + *attenuation = h->mat.albedo; + return 1; + } + case MAT_METAL: { + vec3 r = v3reflect(v3norm(in.dir), h->normal); + vec3 dir = v3add(r, v3scale(rng_in_sphere(rs), h->mat.fuzz)); + out->origin = h->point; + out->dir = v3norm(dir); + *attenuation = h->mat.albedo; + return v3dot(out->dir, h->normal) > 0.0; + } + case MAT_LIGHT: + default: + return 0; + } +} + +vec3 ray_colour(const scene *s, ray r, int depth, rng *rs) { + vec3 accumulated = v3(0.0, 0.0, 0.0); + vec3 throughput = v3(1.0, 1.0, 1.0); + + for (int bounce = 0; bounce < depth; bounce++) { + hit h; + if (!scene_hit(s, r, 1e-4, 1e30, &h)) { + accumulated = v3add(accumulated, v3mul(throughput, s->background)); + break; + } + + if (h.mat.kind == MAT_LIGHT) { + accumulated = v3add(accumulated, v3mul(throughput, h.mat.albedo)); + break; + } + + ray next; + vec3 attenuation; + if (!scatter(&h, r, rs, &next, &attenuation)) break; + + throughput = v3mul(throughput, attenuation); + r = next; + + /* Russian roulette once the path stops carrying much energy. */ + if (bounce > 3) { + double p = fmax(throughput.x, fmax(throughput.y, throughput.z)); + if (p < 1.0) { + if (rng_unit(rs) > p) break; + throughput = v3scale(throughput, 1.0 / p); + } + } + } + return accumulated; +}