diff --git a/CMakeLists.txt b/CMakeLists.txt index 756b34b..8e348c5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,6 +13,7 @@ add_library(batomorph src/ff.c src/material.c src/obj.c + src/render.c src/rng.c src/vec3.c ) diff --git a/demo.c b/demo.c index e82da25..2ef973e 100644 --- a/demo.c +++ b/demo.c @@ -1,7 +1,5 @@ -#include "camera.h" #include "ff.h" -#include "obj.h" -#include "rng.h" +#include "render.h" #include @@ -64,15 +62,14 @@ int main() rand_obj(objs + i, &rng); img_t img = { .pix = pixbuf }; - camera_t camera - = camera_init(camera_pos, target, FOV, W, H, SAMPLES_PER_PIXEL); + camera_t camera = camera_init(camera_pos, target, FOV, W, H); const scene_t scene = { .sky_colour = sky, .objs = objs, .obj_count = OBJ_COUNT, }; - camera_render(&camera, &scene, &img); + render(&camera, &scene, &img, SAMPLES_PER_PIXEL); ff_write(STDOUT_FILENO, img); diff --git a/include/camera.h b/include/camera.h index e577577..6c282b7 100644 --- a/include/camera.h +++ b/include/camera.h @@ -1,8 +1,7 @@ #ifndef CAMERA_H #define CAMERA_H -#include "img.h" -#include "scene.h" +#include "vec3.h" #include @@ -10,14 +9,10 @@ typedef struct { vec3_t pos; vec3_t pix_origin, x_step, y_step; uint32_t img_width, img_height; - unsigned samples_per_pixel; } camera_t; camera_t camera_init( vec3_t pos, vec3_t target, double fov, uint32_t img_width, - uint32_t img_height, unsigned samples_per_pixel); - -void camera_render( - const camera_t *camera, const scene_t *scene, img_t *img_out); + uint32_t img_height); #endif diff --git a/include/render.h b/include/render.h new file mode 100644 index 0000000..28c8d9b --- /dev/null +++ b/include/render.h @@ -0,0 +1,12 @@ +#ifndef RENDER_H +#define RENDER_H + +#include "camera.h" +#include "img.h" +#include "scene.h" + +void render( + const camera_t *camera, const scene_t *scene, img_t *img_out, + unsigned samples_per_pixel); + +#endif diff --git a/src/camera.c b/src/camera.c index 06c77c7..dfc50a0 100644 --- a/src/camera.c +++ b/src/camera.c @@ -1,125 +1,16 @@ #include "camera.h" -#include "ray.h" -#include "rng.h" - -#include #include -#include -#include -#include #ifndef M_PI #define M_PI 3.14159265258979323846264 #endif -#define MAX_ITER 200 -#define MIN_T 1e-6 -#define SAMPLE_STDDEV 0.333 - -#define GAMMA 2.2 - -#define NTHREADS 20 - -typedef struct { - const camera_t *camera; - const scene_t *scene; - pix_t *pixels; - rng_t rng; - unsigned start_y, row_count; - atomic_uint *progress; -} work_slice_t; - static const vec3_t up = { 0.0, 1.0, 0.0 }; -static const vec3_t white = { 1.0, 1.0, 1.0 }; -static const vec3_t black = { 0.0, 0.0, 0.0 }; - -static vec3_t trace(ray_t ray, const scene_t *scene, rng_t *rng) -{ - vec3_t colour = white; - for (unsigned i = 0; i < MAX_ITER; ++i) { - hit_t hit = { .t = DBL_MAX }; - material_t material = {}; - for (unsigned j = 0; j < scene->obj_count; ++j) { - const obj_t *obj = scene->objs + j; - if (obj->intersect(obj->params, ray, &hit, MIN_T, hit.t)) - material = obj->material; - } - - if (hit.t == DBL_MAX) { - const double a = (ray.dir.y + 1.0) / 2.0; - const vec3_t bg = vec3_add( - vec3_scale(scene->sky_colour, a), vec3_scale(white, 1 - a)); - return vec3_hadamard(colour, bg); - } - - vec3_t atten; - if (!material.scatter(material.params, hit, rng, &ray, &atten)) - return black; - colour = vec3_hadamard(colour, atten); - } - - return black; -} - -static double linear_to_gamma(double channel) -{ - return pow(fmin(channel, 1.0), 1.0 / GAMMA); -} - -static void setpix(vec3_t col, pix_t *out) -{ - out->r = UINT16_MAX * linear_to_gamma(col.x); - out->g = UINT16_MAX * linear_to_gamma(col.y); - out->b = UINT16_MAX * linear_to_gamma(col.z); - out->a = UINT16_MAX; -} - -static int render_thread(void *arg) -{ - work_slice_t *slice = (work_slice_t *)arg; - const camera_t *camera = slice->camera; - const uint32_t w = camera->img_width; - const double sample_weight = 1.0 / (double)camera->samples_per_pixel; - - const unsigned stop_y = slice->start_y + slice->row_count; - for (unsigned y = slice->start_y; y < stop_y; ++y) { - const vec3_t row - = vec3_add(camera->pix_origin, vec3_scale(camera->y_step, y)); - for (unsigned x = 0; x < w; ++x) { - const vec3_t pix = vec3_add(row, vec3_scale(camera->x_step, x)); - - vec3_t colour = black; - for (unsigned i = 0; i < camera->samples_per_pixel; ++i) { - const vec3_t jitter - = rng_gaussian_xy(&slice->rng, SAMPLE_STDDEV); - const vec3_t offset = vec3_add( - vec3_scale(camera->x_step, jitter.x), - vec3_scale(camera->y_step, jitter.y)); - const vec3_t jittered_pix = vec3_add(pix, offset); - - const ray_t ray = { - .orig = camera->pos, - .dir = vec3_unit(vec3_sub(jittered_pix, camera->pos)), - }; - const vec3_t sample = trace(ray, slice->scene, &slice->rng); - - colour = vec3_add(colour, vec3_scale(sample, sample_weight)); - } - - setpix(colour, slice->pixels + (w * y + x)); - } - - atomic_fetch_add(slice->progress, 1); - } - - return 0; -} - camera_t camera_init( vec3_t pos, vec3_t target, double fov, uint32_t img_width, - uint32_t img_height, unsigned samples_per_pixel) + uint32_t img_height) { const double fov_rad = M_PI * fov / 180.0; const double aspect = (double)img_width / (double)img_height; @@ -148,49 +39,5 @@ camera_t camera_init( .y_step = y_step, .img_width = img_width, .img_height = img_height, - .samples_per_pixel = samples_per_pixel, }; } - -void camera_render( - const camera_t *camera, const scene_t *scene, img_t *img_out) -{ - img_out->w = camera->img_width; - img_out->h = camera->img_height; - - const unsigned rows = camera->img_height; - const unsigned rows_per_thread = rows / NTHREADS; - const unsigned rem_rows = rows % NTHREADS; - - atomic_uint progress = 0; - - thrd_t threads[NTHREADS]; - work_slice_t slices[NTHREADS]; - for (unsigned i = 0; i < NTHREADS; ++i) { - slices[i].camera = camera; - slices[i].scene = scene; - slices[i].pixels = img_out->pix; - slices[i].rng = rng_init(i); - - slices[i].start_y = i * rows_per_thread; - slices[i].row_count = rows_per_thread; - if (rem_rows != 0 && i == NTHREADS - 1) - slices[i].row_count += rem_rows; - - slices[i].progress = &progress; - - thrd_create(threads + i, render_thread, slices + i); - } - - const unsigned digits = (unsigned)floor(log(rows) / log(10)) + 1; - unsigned done; - do { - thrd_sleep(&(struct timespec) { .tv_nsec = 50'000'000 }, nullptr); - done = atomic_load(&progress); - fprintf(stderr, "\r[%*d/%d]", digits, done, rows); - fflush(stderr); - } while (done < rows); - - for (unsigned i = 0; i < NTHREADS; ++i) - thrd_join(threads[i], 0); -} diff --git a/src/render.c b/src/render.c new file mode 100644 index 0000000..c48db95 --- /dev/null +++ b/src/render.c @@ -0,0 +1,158 @@ +#include "render.h" + +#include "ray.h" +#include "rng.h" + +#include +#include +#include +#include +#include + +#define MAX_ITER 200 +#define MIN_T 1e-6 +#define SAMPLE_STDDEV 0.333 + +#define GAMMA 2.2 + +#define NTHREADS 20 + +typedef struct { + const camera_t *camera; + const scene_t *scene; + pix_t *pixels; + rng_t rng; + unsigned samples_per_pixel; + unsigned start_y, row_count; + atomic_uint *progress; +} work_slice_t; + +static const vec3_t white = { 1.0, 1.0, 1.0 }; +static const vec3_t black = { 0.0, 0.0, 0.0 }; + +static vec3_t trace(ray_t ray, const scene_t *scene, rng_t *rng) +{ + vec3_t colour = white; + for (unsigned i = 0; i < MAX_ITER; ++i) { + hit_t hit = { .t = DBL_MAX }; + material_t material = {}; + for (unsigned j = 0; j < scene->obj_count; ++j) { + const obj_t *obj = scene->objs + j; + if (obj->intersect(obj->params, ray, &hit, MIN_T, hit.t)) + material = obj->material; + } + + if (hit.t == DBL_MAX) { + const double a = (ray.dir.y + 1.0) / 2.0; + const vec3_t bg = vec3_add( + vec3_scale(scene->sky_colour, a), vec3_scale(white, 1 - a)); + return vec3_hadamard(colour, bg); + } + + vec3_t atten; + if (!material.scatter(material.params, hit, rng, &ray, &atten)) + return black; + colour = vec3_hadamard(colour, atten); + } + + return black; +} + +static double linear_to_gamma(double channel) +{ + return pow(fmin(channel, 1.0), 1.0 / GAMMA); +} + +static void setpix(vec3_t col, pix_t *out) +{ + out->r = UINT16_MAX * linear_to_gamma(col.x); + out->g = UINT16_MAX * linear_to_gamma(col.y); + out->b = UINT16_MAX * linear_to_gamma(col.z); + out->a = UINT16_MAX; +} + +static int render_thread(void *arg) +{ + work_slice_t *slice = (work_slice_t *)arg; + const camera_t *camera = slice->camera; + const uint32_t w = camera->img_width; + const double sample_weight = 1.0 / (double)slice->samples_per_pixel; + + const unsigned stop_y = slice->start_y + slice->row_count; + for (unsigned y = slice->start_y; y < stop_y; ++y) { + const vec3_t row + = vec3_add(camera->pix_origin, vec3_scale(camera->y_step, y)); + for (unsigned x = 0; x < w; ++x) { + const vec3_t pix = vec3_add(row, vec3_scale(camera->x_step, x)); + + vec3_t colour = black; + for (unsigned i = 0; i < slice->samples_per_pixel; ++i) { + const vec3_t jitter + = rng_gaussian_xy(&slice->rng, SAMPLE_STDDEV); + const vec3_t offset = vec3_add( + vec3_scale(camera->x_step, jitter.x), + vec3_scale(camera->y_step, jitter.y)); + const vec3_t jittered_pix = vec3_add(pix, offset); + + const ray_t ray = { + .orig = camera->pos, + .dir = vec3_unit(vec3_sub(jittered_pix, camera->pos)), + }; + const vec3_t sample = trace(ray, slice->scene, &slice->rng); + + colour = vec3_add(colour, vec3_scale(sample, sample_weight)); + } + + setpix(colour, slice->pixels + (w * y + x)); + } + + atomic_fetch_add(slice->progress, 1); + } + + return 0; +} + +void render( + const camera_t *camera, const scene_t *scene, img_t *img_out, + unsigned samples_per_pixel) +{ + img_out->w = camera->img_width; + img_out->h = camera->img_height; + + const unsigned rows = camera->img_height; + const unsigned rows_per_thread = rows / NTHREADS; + const unsigned rem_rows = rows % NTHREADS; + + atomic_uint progress = 0; + + thrd_t threads[NTHREADS]; + work_slice_t slices[NTHREADS]; + for (unsigned i = 0; i < NTHREADS; ++i) { + slices[i].camera = camera; + slices[i].scene = scene; + slices[i].pixels = img_out->pix; + slices[i].rng = rng_init(i); + slices[i].samples_per_pixel = samples_per_pixel; + + slices[i].start_y = i * rows_per_thread; + slices[i].row_count = rows_per_thread; + if (rem_rows != 0 && i == NTHREADS - 1) + slices[i].row_count += rem_rows; + + slices[i].progress = &progress; + + thrd_create(threads + i, render_thread, slices + i); + } + + const unsigned digits = (unsigned)floor(log(rows) / log(10)) + 1; + unsigned done; + do { + thrd_sleep(&(struct timespec) { .tv_nsec = 50'000'000 }, nullptr); + done = atomic_load(&progress); + fprintf(stderr, "\r[%*d/%d]", digits, done, rows); + fflush(stderr); + } while (done < rows); + + for (unsigned i = 0; i < NTHREADS; ++i) + thrd_join(threads[i], 0); +}