Add a bounding volume hierarchy

This commit is contained in:
Jean-Michel Gorius 2022-11-12 18:59:57 +01:00
parent 1034668c66
commit f23e454358
7 changed files with 285 additions and 9 deletions

64
aabb.c Normal file
View File

@ -0,0 +1,64 @@
#include "aabb.h"
#include <math.h>
bool aabb_hit(const AABB *aabb, Ray r, double t_min, double t_max) {
/* X */
{
double inv_d = 1.0 / r.direction.x;
double t0 = (aabb->min.x - r.origin.x) * inv_d;
double t1 = (aabb->max.x - r.origin.x) * inv_d;
if (inv_d < 0.0) {
double tmp = t0;
t0 = t1;
t1 = tmp;
}
t_min = t0 > t_min ? t0 : t_min;
t_max = t1 < t_max ? t1 : t_max;
if (t_max <= t_min)
return false;
}
/* Y */
{
double inv_d = 1.0 / r.direction.y;
double t0 = (aabb->min.y - r.origin.y) * inv_d;
double t1 = (aabb->max.y - r.origin.y) * inv_d;
if (inv_d < 0.0) {
double tmp = t0;
t0 = t1;
t1 = tmp;
}
t_min = t0 > t_min ? t0 : t_min;
t_max = t1 < t_max ? t1 : t_max;
if (t_max <= t_min)
return false;
}
/* Z */
{
double inv_d = 1.0 / r.direction.z;
double t0 = (aabb->min.z - r.origin.z) * inv_d;
double t1 = (aabb->max.z - r.origin.z) * inv_d;
if (inv_d < 0.0) {
double tmp = t0;
t0 = t1;
t1 = tmp;
}
t_min = t0 > t_min ? t0 : t_min;
t_max = t1 < t_max ? t1 : t_max;
if (t_max <= t_min)
return false;
}
return true;
}
AABB aabb_surrounding_box(const AABB *first, const AABB *second) {
return (AABB){
.min = {fmin(first->min.x, second->min.x),
fmin(first->min.y, second->min.y),
fmin(first->min.z, second->min.z)},
.max = {fmax(first->max.x, second->max.x),
fmax(first->max.y, second->max.y),
fmax(first->max.z, second->max.z)},
};
}

17
aabb.h Normal file
View File

@ -0,0 +1,17 @@
#ifndef INCLUDED_AABB_H
#define INCLUDED_AABB_H
#include "point3.h"
#include "ray.h"
#include <stdbool.h>
typedef struct AABB {
Point3 min;
Point3 max;
} AABB;
bool aabb_hit(const AABB *aabb, Ray r, double t_min, double t_max);
AABB aabb_surrounding_box(const AABB *first, const AABB *second);
#endif /* INCLUDED_AABB_H */

View File

@ -1,6 +1,8 @@
#include "hittable.h" #include "hittable.h"
#include "aabb.h"
#include "arena.h" #include "arena.h"
#include "point3.h" #include "point3.h"
#include "utils.h"
#include "vec3.h" #include "vec3.h"
#include <assert.h> #include <assert.h>
@ -25,10 +27,37 @@ bool hittable_hit(const Hittable *hittable, Ray r, double t_min, double t_max,
case HITTABLE_MOVING_SPHERE: case HITTABLE_MOVING_SPHERE:
return moving_sphere_hit((const MovingSphere *)hittable, r, t_min, t_max, return moving_sphere_hit((const MovingSphere *)hittable, r, t_min, t_max,
record); record);
case HITTABLE_BVH_NODE:
return bvh_node_hit((const BVHNode *)hittable, r, t_min, t_max, record);
} }
return false; return false;
} }
bool hittable_bounding_box(const Hittable *hittable, double time_start,
double time_end, AABB *bounding_box) {
switch (hittable->type) {
case HITTABLE_LIST:
return hittable_list_bounding_box((const HittableList *)hittable,
time_start, time_end, bounding_box);
case HITTABLE_SPHERE:
return sphere_bounding_box((const Sphere *)hittable, time_start, time_end,
bounding_box);
case HITTABLE_MOVING_SPHERE:
return moving_sphere_bounding_box((const MovingSphere *)hittable,
time_start, time_end, bounding_box);
case HITTABLE_BVH_NODE:
return bvh_node_bounding_box((const BVHNode *)hittable, time_start,
time_end, bounding_box);
}
return false;
}
HittableList *hittable_list_create(Arena *arena) {
HittableList *list = arena_alloc(arena, sizeof(HittableList));
list->type = HITTABLE_LIST;
return list;
}
static void hittable_list_grow(HittableList *list, size_t n, Arena *arena) { static void hittable_list_grow(HittableList *list, size_t n, Arena *arena) {
if (list->objects) { if (list->objects) {
const Hittable **new_objects = const Hittable **new_objects =
@ -65,6 +94,26 @@ bool hittable_list_hit(const HittableList *list, Ray r, double t_min,
return hit_anything; 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;
}
Sphere *sphere_create(Point3 center, double radius, const Material *material, Sphere *sphere_create(Point3 center, double radius, const Material *material,
Arena *arena) { Arena *arena) {
Sphere *sphere = arena_alloc(arena, sizeof(Sphere)); Sphere *sphere = arena_alloc(arena, sizeof(Sphere));
@ -102,6 +151,18 @@ bool sphere_hit(const Sphere *sphere, Ray r, double t_min, double t_max,
return true; 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;
}
MovingSphere *moving_sphere_create(Point3 center_start, Point3 center_end, MovingSphere *moving_sphere_create(Point3 center_start, Point3 center_end,
double start, double end, double radius, double start, double end, double radius,
const Material *material, Arena *arena) { const Material *material, Arena *arena) {
@ -150,3 +211,102 @@ bool moving_sphere_hit(const MovingSphere *sphere, Ray r, double t_min,
record->material = sphere->material; record->material = sphere->material;
return true; 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;
}
typedef int BoxCompareFunc(const void *lhs, const void *rhs);
#define BOX_COMPARATOR(axis) \
static int box_##axis##_compare(const void *lhs, const void *rhs) { \
AABB lhs_box, rhs_box; \
if (!hittable_bounding_box(*(const Hittable **)lhs, 0, 0, &lhs_box) || \
!hittable_bounding_box(*(const Hittable **)rhs, 0, 0, &rhs_box)) { \
fprintf(stderr, "No bounding-box in BVH node"); \
exit(1); \
} \
\
return lhs_box.min.axis < rhs_box.min.axis; \
}
BOX_COMPARATOR(x)
BOX_COMPARATOR(y)
BOX_COMPARATOR(z)
#undef BOX_COMPARATOR
BVHNode *bvh_node_create(const Hittable **objects, size_t start, size_t end,
double time_start, double time_end, Arena *arena) {
BVHNode *node = arena_alloc(arena, sizeof(BVHNode));
node->type = HITTABLE_BVH_NODE;
int axis = random_int_in_range(0, 2);
BoxCompareFunc *comparator = (axis == 0) ? box_x_compare
: (axis == 1) ? box_y_compare
: box_z_compare;
size_t object_span = end - start;
if (object_span == 1) {
node->left = node->right = objects[start];
} else if (object_span == 2) {
if (comparator(&objects[start], &objects[start + 1])) {
node->left = objects[start];
node->right = objects[start + 1];
} else {
node->left = objects[start + 1];
node->right = objects[start];
}
} else {
qsort(objects + start, object_span, sizeof(const Hittable *), comparator);
size_t mid = start + object_span / 2;
node->left = (const Hittable *)bvh_node_create(objects, start, mid,
time_start, time_end, arena);
node->right = (const Hittable *)bvh_node_create(
objects, mid, end, time_start, time_end, arena);
}
AABB left_box, right_box;
if (!hittable_bounding_box(node->left, time_start, time_end, &left_box) ||
!hittable_bounding_box(node->right, time_start, time_end, &right_box)) {
fprintf(stderr, "No bounding-box in BVH node");
exit(1);
}
node->box = aabb_surrounding_box(&left_box, &right_box);
return node;
}
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;
}

View File

@ -1,6 +1,7 @@
#ifndef INCLUDED_HITTABLE_H #ifndef INCLUDED_HITTABLE_H
#define INCLUDED_HITTABLE_H #define INCLUDED_HITTABLE_H
#include "aabb.h"
#include "arena.h" #include "arena.h"
#include "material.h" #include "material.h"
#include "point3.h" #include "point3.h"
@ -24,6 +25,7 @@ typedef enum HittableType {
HITTABLE_LIST, HITTABLE_LIST,
HITTABLE_SPHERE, HITTABLE_SPHERE,
HITTABLE_MOVING_SPHERE, HITTABLE_MOVING_SPHERE,
HITTABLE_BVH_NODE,
} HittableType; } HittableType;
typedef struct Hittable { typedef struct Hittable {
@ -32,6 +34,8 @@ typedef struct Hittable {
bool hittable_hit(const Hittable *hittable, Ray r, double t_min, double t_max, bool hittable_hit(const Hittable *hittable, Ray r, double t_min, double t_max,
HitRecord *record); HitRecord *record);
bool hittable_bounding_box(const Hittable *hittable, double time_start,
double time_end, AABB *bounding_box);
typedef struct HittableList { typedef struct HittableList {
HittableType type; HittableType type;
@ -40,10 +44,13 @@ typedef struct HittableList {
size_t capacity; size_t capacity;
} HittableList; } HittableList;
HittableList *hittable_list_create(Arena *arena);
void hittable_list_add(HittableList *list, const Hittable *hittable, void hittable_list_add(HittableList *list, const Hittable *hittable,
Arena *arena); Arena *arena);
bool hittable_list_hit(const HittableList *list, Ray r, double t_min, bool hittable_list_hit(const HittableList *list, Ray r, double t_min,
double t_max, HitRecord *record); double t_max, HitRecord *record);
bool hittable_list_bounding_box(const HittableList *list, double time_start,
double time_end, AABB *bounding_box);
typedef struct Sphere { typedef struct Sphere {
HittableType type; HittableType type;
@ -56,6 +63,8 @@ Sphere *sphere_create(Point3 center, double radius, const Material *material,
Arena *arena); Arena *arena);
bool sphere_hit(const Sphere *sphere, Ray r, double t_min, double t_max, bool sphere_hit(const Sphere *sphere, Ray r, double t_min, double t_max,
HitRecord *record); HitRecord *record);
bool sphere_bounding_box(const Sphere *sphere, double time_start,
double time_end, AABB *bounding_box);
typedef struct MovingSphere { typedef struct MovingSphere {
HittableType type; HittableType type;
@ -71,5 +80,21 @@ MovingSphere *moving_sphere_create(Point3 center_start, Point3 center_end,
Point3 moving_sphere_center(const MovingSphere *sphere, double t); Point3 moving_sphere_center(const MovingSphere *sphere, double t);
bool moving_sphere_hit(const MovingSphere *sphere, Ray r, double t_min, bool moving_sphere_hit(const MovingSphere *sphere, Ray r, double t_min,
double t_max, HitRecord *record); double t_max, HitRecord *record);
bool moving_sphere_bounding_box(const MovingSphere *sphere, double time_start,
double time_end, AABB *bounding_box);
typedef struct BVHNode {
HittableType type;
const Hittable *left;
const Hittable *right;
AABB box;
} BVHNode;
BVHNode *bvh_node_create(const Hittable **objects, size_t start, size_t end,
double time_start, double time_end, Arena *arena);
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 */ #endif /* INCLUDED_HITTABLE_H */

23
main.c
View File

@ -35,14 +35,14 @@ static Color ray_color(Ray r, const Hittable *world, int depth) {
} }
static const Hittable *generate_random_scene(Arena *arena) { static const Hittable *generate_random_scene(Arena *arena) {
static HittableList world = {.type = HITTABLE_LIST}; HittableList *world = hittable_list_create(arena);
const Lambertian *ground_material = const Lambertian *ground_material =
lambertian_create((Color){0.5, 0.5, 0.5}, arena); lambertian_create((Color){0.5, 0.5, 0.5}, arena);
const Sphere *ground_sphere = const Sphere *ground_sphere =
sphere_create((Point3){0.0, -1000.0, 0.0}, 1000.0, sphere_create((Point3){0.0, -1000.0, 0.0}, 1000.0,
(const Material *)ground_material, arena); (const Material *)ground_material, arena);
hittable_list_add(&world, (const Hittable *)ground_sphere, arena); hittable_list_add(world, (const Hittable *)ground_sphere, arena);
const Dielectric *glass = dielectric_create(1.5, arena); const Dielectric *glass = dielectric_create(1.5, arena);
@ -61,18 +61,18 @@ static const Hittable *generate_random_scene(Arena *arena) {
const MovingSphere *sphere = const MovingSphere *sphere =
moving_sphere_create(center, center2, 0.0, 1.0, 0.2, moving_sphere_create(center, center2, 0.0, 1.0, 0.2,
(const Material *)material, arena); (const Material *)material, arena);
hittable_list_add(&world, (const Hittable *)sphere, arena); hittable_list_add(world, (const Hittable *)sphere, arena);
} else if (choose_material < 0.95) { } else if (choose_material < 0.95) {
const Metal *material = const Metal *material =
metal_create(color_random_in_range(0.5, 1), metal_create(color_random_in_range(0.5, 1),
random_double_in_range(0.5, 1.0), arena); random_double_in_range(0.5, 1.0), arena);
const Sphere *sphere = const Sphere *sphere =
sphere_create(center, 0.2, (const Material *)material, arena); sphere_create(center, 0.2, (const Material *)material, arena);
hittable_list_add(&world, (const Hittable *)sphere, arena); hittable_list_add(world, (const Hittable *)sphere, arena);
} else { } else {
const Sphere *sphere = const Sphere *sphere =
sphere_create(center, 0.2, (const Material *)glass, arena); sphere_create(center, 0.2, (const Material *)glass, arena);
hittable_list_add(&world, (const Hittable *)sphere, arena); hittable_list_add(world, (const Hittable *)sphere, arena);
} }
} }
} }
@ -84,18 +84,22 @@ static const Hittable *generate_random_scene(Arena *arena) {
const Sphere *sphere1 = sphere_create((Point3){0.0, 1.0, 0.0}, 1.0, const Sphere *sphere1 = sphere_create((Point3){0.0, 1.0, 0.0}, 1.0,
(const Material *)glass, arena); (const Material *)glass, arena);
hittable_list_add(&world, (const Hittable *)sphere1, arena); hittable_list_add(world, (const Hittable *)sphere1, arena);
const Sphere *sphere2 = sphere_create((Point3){-4.0, 1.0, 0.0}, 1.0, const Sphere *sphere2 = sphere_create((Point3){-4.0, 1.0, 0.0}, 1.0,
(const Material *)lambertian, arena); (const Material *)lambertian, arena);
hittable_list_add(&world, (const Hittable *)sphere2, arena); hittable_list_add(world, (const Hittable *)sphere2, arena);
const Sphere *sphere3 = sphere_create((Point3){4.0, 1.0, 0.0}, 1.0, const Sphere *sphere3 = sphere_create((Point3){4.0, 1.0, 0.0}, 1.0,
(const Material *)metal, arena); (const Material *)metal, arena);
hittable_list_add(&world, (const Hittable *)sphere3, arena); hittable_list_add(world, (const Hittable *)sphere3, arena);
return (const Hittable *)&world; BVHNode *bvh_root =
bvh_node_create(world->objects, 0, world->size, 0.0, 1.0, arena);
return (const Hittable *)bvh_root;
} }
int main(void) { int main(void) {
srand(42);
/* Memory management */ /* Memory management */
const unsigned int buffer_size = 1 * 1024 * 1024; const unsigned int buffer_size = 1 * 1024 * 1024;
@ -152,6 +156,7 @@ int main(void) {
return 0; return 0;
} }
#include "aabb.c"
#include "arena.c" #include "arena.c"
#include "camera.c" #include "camera.c"
#include "color.c" #include "color.c"

View File

@ -11,6 +11,10 @@ double random_double_in_range(double min, double max) {
return min + (max - min) * random_double(); return min + (max - min) * random_double();
} }
int random_int_in_range(int min, int max) {
return (int)(random_double_in_range(min, max + 1));
}
double clamp(double x, double min, double max) { double clamp(double x, double min, double max) {
if (x < min) if (x < min)
return min; return min;

View File

@ -5,6 +5,7 @@ double degrees_to_radians(double degrees);
double random_double(void); double random_double(void);
double random_double_in_range(double min, double max); double random_double_in_range(double min, double max);
int random_int_in_range(int min, int max);
double clamp(double x, double min, double max); double clamp(double x, double min, double max);