From 8db2d5aa2ca8dc3dc007798957ae7e97103bc03c Mon Sep 17 00:00:00 2001 From: Jean-Michel Gorius Date: Sun, 13 Nov 2022 16:42:05 +0100 Subject: [PATCH] Reduce the public API of Hittable and Material --- build.sh | 2 +- hittable.c | 306 +++++++++++++++++++++++++++-------------------------- hittable.h | 20 ---- main.c | 4 +- material.c | 123 ++++++++++----------- material.h | 10 -- 6 files changed, 219 insertions(+), 246 deletions(-) diff --git a/build.sh b/build.sh index 4770e8a..e04c886 100755 --- a/build.sh +++ b/build.sh @@ -1,7 +1,7 @@ #!/bin/bash CC=${CC:-"gcc"} -CFLAGS=${CFLAGS:-"-Wall -Wextra -std=gnu18 -fno-strict-aliasing -O3"} +CFLAGS=${CFLAGS:-"-Wall -Wextra -Wno-unused-function -std=gnu18 -fno-strict-aliasing -O3"} LDFLAGS=${LDFLAGS:-"-lm"} MAIN_FILE=${MAIN_FILE:-"main.c"} diff --git a/hittable.c b/hittable.c index b4ca9a9..ceb0d90 100644 --- a/hittable.c +++ b/hittable.c @@ -101,6 +101,99 @@ Hittable *hittable_create_bvh_node(const Hittable **objects, size_t start, return result; } +static bool hittable_list_hit(const HittableList *list, Ray r, double t_min, + double t_max, HitRecord *record) { + bool hit_anything = false; + double closest_so_far = t_max; + + for (size_t i = 0; i < list->size; ++i) { + if (hittable_hit(list->objects[i], r, t_min, closest_so_far, record)) { + hit_anything = true; + closest_so_far = record->t; + } + } + + return hit_anything; +} + +static void get_sphere_uv(Point3 p, double *u, double *v) { + double theta = acos(-p.y); + double phi = atan2(-p.z, p.x) + M_PI; + *u = phi / (2 * M_PI); + *v = theta / M_PI; +} + +static bool sphere_hit(const Sphere *sphere, Ray r, double t_min, double t_max, + HitRecord *record) { + Vec3 oc = point3_sub(r.origin, sphere->center); + double a = vec3_length2(r.direction); + double half_b = vec3_dot(oc, r.direction); + double c = vec3_length2(oc) - sphere->radius * sphere->radius; + double discriminant = half_b * half_b - a * c; + if (discriminant < 0) + return false; + + double square_root = sqrt(discriminant); + double root = (-half_b - square_root) / a; + if (root < t_min || t_max < root) { + root = (-half_b + square_root) / a; + if (root < t_min || t_max < root) + return false; + } + + record->t = root; + record->p = ray_at(r, root); + Vec3 outward_normal = + vec3_div(point3_sub(record->p, sphere->center), sphere->radius); + hit_record_set_face_normal(record, r, outward_normal); + get_sphere_uv((Point3){outward_normal.x, outward_normal.y, outward_normal.z}, + &record->u, &record->v); + record->material = sphere->material; + return true; +} + +static bool moving_sphere_hit(const MovingSphere *sphere, Ray r, double t_min, + double t_max, HitRecord *record) { + Vec3 oc = point3_sub(r.origin, moving_sphere_center(sphere, r.time)); + double a = vec3_length2(r.direction); + double half_b = vec3_dot(oc, r.direction); + double c = vec3_length2(oc) - sphere->radius * sphere->radius; + double discriminant = half_b * half_b - a * c; + if (discriminant < 0) + return false; + + double square_root = sqrt(discriminant); + double root = (-half_b - square_root) / a; + if (root < t_min || t_max < root) { + root = (-half_b + square_root) / a; + if (root < t_min || t_max < root) + return false; + } + + record->t = root; + record->p = ray_at(r, root); + Vec3 outward_normal = + vec3_div(point3_sub(record->p, moving_sphere_center(sphere, r.time)), + sphere->radius); + hit_record_set_face_normal(record, r, outward_normal); + get_sphere_uv((Point3){outward_normal.x, outward_normal.y, outward_normal.z}, + &record->u, &record->v); + record->material = sphere->material; + return true; +} + +static bool bvh_node_hit(const BVHNode *node, Ray r, double t_min, double t_max, + HitRecord *record) { + if (!aabb_hit(&node->box, r, t_min, t_max)) + return false; + + bool hit_left = hittable_hit(node->left, r, t_min, t_max, record); + bool hit_right = + hittable_hit(node->right, r, t_min, hit_left ? record->t : t_max, record); + + return hit_left || hit_right; +} + bool hittable_hit(const Hittable *hittable, Ray r, double t_min, double t_max, HitRecord *record) { switch (hittable->type) { @@ -116,6 +209,67 @@ bool hittable_hit(const Hittable *hittable, Ray r, double t_min, double t_max, return false; } +static bool hittable_list_bounding_box(const HittableList *list, + double time_start, double time_end, + AABB *bounding_box) { + if (list->size == 0) + return false; + + AABB temp_box; + bool first_box = true; + + for (size_t i = 0; i < list->size; ++i) { + if (!hittable_bounding_box(list->objects[i], time_start, time_end, + &temp_box)) + return false; + *bounding_box = + first_box ? temp_box : aabb_surrounding_box(bounding_box, &temp_box); + first_box = false; + } + + return true; +} + +static bool sphere_bounding_box(const Sphere *sphere, double time_start, + double time_end, AABB *bounding_box) { + (void)time_start, (void)time_end; + *bounding_box = (AABB){ + .min = point3_add(sphere->center, (Vec3){-sphere->radius, -sphere->radius, + -sphere->radius}), + .max = point3_add(sphere->center, + (Vec3){sphere->radius, sphere->radius, sphere->radius}), + }; + return true; +} + +static bool moving_sphere_bounding_box(const MovingSphere *sphere, + double time_start, double time_end, + AABB *bounding_box) { + AABB box_start = { + .min = + point3_add(moving_sphere_center(sphere, time_start), + (Vec3){-sphere->radius, -sphere->radius, -sphere->radius}), + .max = point3_add(moving_sphere_center(sphere, time_start), + (Vec3){sphere->radius, sphere->radius, sphere->radius}), + }; + AABB box_end = { + .min = + point3_add(moving_sphere_center(sphere, time_end), + (Vec3){-sphere->radius, -sphere->radius, -sphere->radius}), + .max = point3_add(moving_sphere_center(sphere, time_end), + (Vec3){sphere->radius, sphere->radius, sphere->radius}), + }; + *bounding_box = aabb_surrounding_box(&box_start, &box_end); + return true; +} + +static bool bvh_node_bounding_box(const BVHNode *node, double time_start, + double time_end, AABB *bounding_box) { + (void)time_start, (void)time_end; + *bounding_box = node->box; + return true; +} + bool hittable_bounding_box(const Hittable *hittable, double time_start, double time_end, AABB *bounding_box) { switch (hittable->type) { @@ -156,89 +310,6 @@ void hittable_list_add(HittableList *list, const Hittable *hittable, list->objects[list->size++] = hittable; } -bool hittable_list_hit(const HittableList *list, Ray r, double t_min, - double t_max, HitRecord *record) { - bool hit_anything = false; - double closest_so_far = t_max; - - for (size_t i = 0; i < list->size; ++i) { - if (hittable_hit(list->objects[i], r, t_min, closest_so_far, record)) { - hit_anything = true; - closest_so_far = record->t; - } - } - - return hit_anything; -} - -bool hittable_list_bounding_box(const HittableList *list, double time_start, - double time_end, AABB *bounding_box) { - if (list->size == 0) - return false; - - AABB temp_box; - bool first_box = true; - - for (size_t i = 0; i < list->size; ++i) { - if (!hittable_bounding_box(list->objects[i], time_start, time_end, - &temp_box)) - return false; - *bounding_box = - first_box ? temp_box : aabb_surrounding_box(bounding_box, &temp_box); - first_box = false; - } - - return true; -} - -static void get_sphere_uv(Point3 p, double *u, double *v) { - double theta = acos(-p.y); - double phi = atan2(-p.z, p.x) + M_PI; - *u = phi / (2 * M_PI); - *v = theta / M_PI; -} - -bool sphere_hit(const Sphere *sphere, Ray r, double t_min, double t_max, - HitRecord *record) { - Vec3 oc = point3_sub(r.origin, sphere->center); - double a = vec3_length2(r.direction); - double half_b = vec3_dot(oc, r.direction); - double c = vec3_length2(oc) - sphere->radius * sphere->radius; - double discriminant = half_b * half_b - a * c; - if (discriminant < 0) - return false; - - double square_root = sqrt(discriminant); - double root = (-half_b - square_root) / a; - if (root < t_min || t_max < root) { - root = (-half_b + square_root) / a; - if (root < t_min || t_max < root) - return false; - } - - record->t = root; - record->p = ray_at(r, root); - Vec3 outward_normal = - vec3_div(point3_sub(record->p, sphere->center), sphere->radius); - hit_record_set_face_normal(record, r, outward_normal); - get_sphere_uv((Point3){outward_normal.x, outward_normal.y, outward_normal.z}, - &record->u, &record->v); - record->material = sphere->material; - return true; -} - -bool sphere_bounding_box(const Sphere *sphere, double time_start, - double time_end, AABB *bounding_box) { - (void)time_start, (void)time_end; - *bounding_box = (AABB){ - .min = point3_add(sphere->center, (Vec3){-sphere->radius, -sphere->radius, - -sphere->radius}), - .max = point3_add(sphere->center, - (Vec3){sphere->radius, sphere->radius, sphere->radius}), - }; - return true; -} - Hittable *hittable_create_moving_sphere(Point3 center_start, Point3 center_end, double start, double end, double radius, const Material *material, @@ -254,72 +325,3 @@ Hittable *hittable_create_moving_sphere(Point3 center_start, Point3 center_end, result->moving_sphere.material = material; return result; } - -bool moving_sphere_hit(const MovingSphere *sphere, Ray r, double t_min, - double t_max, HitRecord *record) { - Vec3 oc = point3_sub(r.origin, moving_sphere_center(sphere, r.time)); - double a = vec3_length2(r.direction); - double half_b = vec3_dot(oc, r.direction); - double c = vec3_length2(oc) - sphere->radius * sphere->radius; - double discriminant = half_b * half_b - a * c; - if (discriminant < 0) - return false; - - double square_root = sqrt(discriminant); - double root = (-half_b - square_root) / a; - if (root < t_min || t_max < root) { - root = (-half_b + square_root) / a; - if (root < t_min || t_max < root) - return false; - } - - record->t = root; - record->p = ray_at(r, root); - Vec3 outward_normal = - vec3_div(point3_sub(record->p, moving_sphere_center(sphere, r.time)), - sphere->radius); - hit_record_set_face_normal(record, r, outward_normal); - get_sphere_uv((Point3){outward_normal.x, outward_normal.y, outward_normal.z}, - &record->u, &record->v); - record->material = sphere->material; - return true; -} - -bool moving_sphere_bounding_box(const MovingSphere *sphere, double time_start, - double time_end, AABB *bounding_box) { - AABB box_start = { - .min = - point3_add(moving_sphere_center(sphere, time_start), - (Vec3){-sphere->radius, -sphere->radius, -sphere->radius}), - .max = point3_add(moving_sphere_center(sphere, time_start), - (Vec3){sphere->radius, sphere->radius, sphere->radius}), - }; - AABB box_end = { - .min = - point3_add(moving_sphere_center(sphere, time_end), - (Vec3){-sphere->radius, -sphere->radius, -sphere->radius}), - .max = point3_add(moving_sphere_center(sphere, time_end), - (Vec3){sphere->radius, sphere->radius, sphere->radius}), - }; - *bounding_box = aabb_surrounding_box(&box_start, &box_end); - return true; -} - -bool bvh_node_hit(const BVHNode *node, Ray r, double t_min, double t_max, - HitRecord *record) { - if (!aabb_hit(&node->box, r, t_min, t_max)) - return false; - - bool hit_left = hittable_hit(node->left, r, t_min, t_max, record); - bool hit_right = - hittable_hit(node->right, r, t_min, hit_left ? record->t : t_max, record); - - return hit_left || hit_right; -} - -bool bvh_node_bounding_box(const BVHNode *node, double time_start, - double time_end, AABB *bounding_box) { - (void)time_start, (void)time_end; - *bounding_box = node->box; - return true; -} diff --git a/hittable.h b/hittable.h index 0eac955..c26c0b8 100644 --- a/hittable.h +++ b/hittable.h @@ -83,25 +83,5 @@ bool hittable_bounding_box(const Hittable *hittable, double time_start, void hittable_list_add(HittableList *list, const Hittable *hittable, Arena *arena); -bool hittable_list_hit(const HittableList *list, Ray r, double t_min, - double t_max, HitRecord *record); -bool hittable_list_bounding_box(const HittableList *list, double time_start, - double time_end, AABB *bounding_box); - -bool sphere_hit(const Sphere *sphere, Ray r, double t_min, double t_max, - HitRecord *record); -bool sphere_bounding_box(const Sphere *sphere, double time_start, - double time_end, AABB *bounding_box); - -Point3 moving_sphere_center(const MovingSphere *sphere, double t); -bool moving_sphere_hit(const MovingSphere *sphere, Ray r, double t_min, - double t_max, HitRecord *record); -bool moving_sphere_bounding_box(const MovingSphere *sphere, double time_start, - double time_end, AABB *bounding_box); - -bool bvh_node_hit(const BVHNode *node, Ray r, double t_min, double t_max, - HitRecord *record); -bool bvh_node_bounding_box(const BVHNode *node, double time_start, - double time_end, AABB *bounding_box); #endif /* INCLUDED_HITTABLE_H */ diff --git a/main.c b/main.c index 3951426..5a502a8 100644 --- a/main.c +++ b/main.c @@ -167,9 +167,9 @@ int main(void) { /* Image parameters */ const double aspect_ratio = 16.0 / 9.0; - const int image_width = 1200; + const int image_width = 400; const int image_height = (int)(image_width / aspect_ratio); - const int samples_per_pixel = 500; + const int samples_per_pixel = 100; const int max_depth = 50; /* World */ diff --git a/material.c b/material.c index da26171..92c0ac8 100644 --- a/material.c +++ b/material.c @@ -7,6 +7,68 @@ #include #include +static bool lambertian_scatter(const Lambertian *lambertian, Ray r, + const HitRecord *record, Color *attenuation, + Ray *scattered) { + (void)r; + Vec3 scatter_direction = vec3_add(record->normal, vec3_random_unit_vector()); + + /* Catch degenerate scatter direction */ + if (vec3_is_near_zero(scatter_direction)) + scatter_direction = record->normal; + + *scattered = (Ray){record->p, scatter_direction, r.time}; + *attenuation = + texture_value(lambertian->albedo, record->u, record->v, record->p); + return true; +} + +static bool metal_scatter(const Metal *metal, Ray r, + const struct HitRecord *record, Color *attenuation, + Ray *scattered) { + Vec3 reflected = vec3_reflect(vec3_normalize(r.direction), record->normal); + assert(metal->fuzziness <= 1); + *scattered = (Ray){ + record->p, + vec3_add(reflected, + vec3_mul(metal->fuzziness, vec3_random_in_unit_sphere())), + r.time, + }; + *attenuation = texture_value(metal->albedo, record->u, record->v, record->p); + return vec3_dot(scattered->direction, record->normal) > 0; +} + +static double schlick_reflectance(double cosine, double eta) { + double r0 = (1 - eta) / (1 + eta); + r0 *= r0; + return r0 + (1 - r0) * pow(1 - cosine, 5); +} + +static bool dielectric_scatter(const Dielectric *dielectric, Ray r, + const struct HitRecord *record, + Color *attenuation, Ray *scattered) { + *attenuation = (Color){1.0, 1.0, 1.0}; + double refraction_ratio = + record->front_face ? (1.0 / dielectric->eta) : dielectric->eta; + + Vec3 unit_direction = vec3_normalize(r.direction); + double cos_theta = + fmin(vec3_dot(vec3_neg(unit_direction), record->normal), 1.0); + double sin_theta = sqrt(1.0 - cos_theta * cos_theta); + + bool cannot_refract = refraction_ratio * sin_theta > 1.0; + + Vec3 direction; + if (cannot_refract || + schlick_reflectance(cos_theta, refraction_ratio) > random_double()) + direction = vec3_reflect(unit_direction, record->normal); + else + direction = vec3_refract(unit_direction, record->normal, refraction_ratio); + + *scattered = (Ray){record->p, direction, r.time}; + return true; +} + bool material_scatter(const Material *material, Ray r, const struct HitRecord *record, Color *attenuation, Ray *scattered) { @@ -35,22 +97,6 @@ Material *material_create_lambertian_color(Color albedo, Arena *arena) { arena); } -bool lambertian_scatter(const Lambertian *lambertian, Ray r, - const HitRecord *record, Color *attenuation, - Ray *scattered) { - (void)r; - Vec3 scatter_direction = vec3_add(record->normal, vec3_random_unit_vector()); - - /* Catch degenerate scatter direction */ - if (vec3_is_near_zero(scatter_direction)) - scatter_direction = record->normal; - - *scattered = (Ray){record->p, scatter_direction, r.time}; - *attenuation = - texture_value(lambertian->albedo, record->u, record->v, record->p); - return true; -} - Material *material_create_metal(const Texture *albedo, double fuzziness, Arena *arena) { Material *result = arena_alloc(arena, sizeof(Material)); @@ -66,54 +112,9 @@ Material *material_create_metal_color(Color albedo, double fuzziness, fuzziness, arena); } -bool metal_scatter(const Metal *metal, Ray r, const struct HitRecord *record, - Color *attenuation, Ray *scattered) { - Vec3 reflected = vec3_reflect(vec3_normalize(r.direction), record->normal); - assert(metal->fuzziness <= 1); - *scattered = (Ray){ - record->p, - vec3_add(reflected, - vec3_mul(metal->fuzziness, vec3_random_in_unit_sphere())), - r.time, - }; - *attenuation = texture_value(metal->albedo, record->u, record->v, record->p); - return vec3_dot(scattered->direction, record->normal) > 0; -} - Material *material_create_dielectric(double eta, Arena *arena) { Material *result = arena_alloc(arena, sizeof(Material)); result->type = MATERIAL_DIELECTRIC; result->dielectric.eta = eta; return result; } - -static double schlick_reflectance(double cosine, double eta) { - double r0 = (1 - eta) / (1 + eta); - r0 *= r0; - return r0 + (1 - r0) * pow(1 - cosine, 5); -} - -bool dielectric_scatter(const Dielectric *dielectric, Ray r, - const struct HitRecord *record, Color *attenuation, - Ray *scattered) { - *attenuation = (Color){1.0, 1.0, 1.0}; - double refraction_ratio = - record->front_face ? (1.0 / dielectric->eta) : dielectric->eta; - - Vec3 unit_direction = vec3_normalize(r.direction); - double cos_theta = - fmin(vec3_dot(vec3_neg(unit_direction), record->normal), 1.0); - double sin_theta = sqrt(1.0 - cos_theta * cos_theta); - - bool cannot_refract = refraction_ratio * sin_theta > 1.0; - - Vec3 direction; - if (cannot_refract || - schlick_reflectance(cos_theta, refraction_ratio) > random_double()) - direction = vec3_reflect(unit_direction, record->normal); - else - direction = vec3_refract(unit_direction, record->normal, refraction_ratio); - - *scattered = (Ray){record->p, direction, r.time}; - return true; -} diff --git a/material.h b/material.h index 9b3d985..cedb630 100644 --- a/material.h +++ b/material.h @@ -43,20 +43,10 @@ bool material_scatter(const Material *material, Ray r, Material *material_create_lambertian(const Texture *albedo, Arena *arena); Material *material_create_lambertian_color(Color albedo, Arena *arena); -bool lambertian_scatter(const Lambertian *lambertian, Ray r, - const struct HitRecord *record, Color *attenuation, - Ray *scattered); - Material *material_create_metal(const Texture *albedo, double fuzziness, Arena *arena); Material *material_create_metal_color(Color albedo, double fuzziness, Arena *arena); -bool metal_scatter(const Metal *metal, Ray r, const struct HitRecord *record, - Color *attenuation, Ray *scattered); - Material *material_create_dielectric(double eta, Arena *arena); -bool dielectric_scatter(const Dielectric *dielectric, Ray r, - const struct HitRecord *record, Color *attenuation, - Ray *scattered); #endif /* INCLUDED_MATERIAL_H */