From c2560ccbdd6b623462fc558715b146acc7afdbcd Mon Sep 17 00:00:00 2001 From: Abdelrahman Date: Sun, 4 Feb 2024 19:07:34 +0000 Subject: [PATCH] Refactor and add reflections --- compile | 1 + include/math/math_utils.h | 8 ++ include/raytracer/raytracer.h | 54 +++++++ include/window/window.h | 3 + src/math/math_utils.c | 14 ++ src/raytracer/main.c | 261 ++-------------------------------- src/raytracer/raytracer.c | 201 ++++++++++++++++++++++++++ src/window/window.c | 25 ++++ 8 files changed, 314 insertions(+), 253 deletions(-) create mode 100644 include/math/math_utils.h create mode 100644 include/raytracer/raytracer.h create mode 100644 src/math/math_utils.c create mode 100644 src/raytracer/raytracer.c diff --git a/compile b/compile index 3a30b7b..8fca7e2 100755 --- a/compile +++ b/compile @@ -7,6 +7,7 @@ LIBS="$(pkg-config --libs sdl2) -lm" RAYTRACER_SRC="src/window/*.c \ src/vector/*.c \ src/raytracer/*.c \ + src/math/*.c \ " BUILD=build_dir diff --git a/include/math/math_utils.h b/include/math/math_utils.h new file mode 100644 index 0000000..4d34e5f --- /dev/null +++ b/include/math/math_utils.h @@ -0,0 +1,8 @@ +#ifndef MATH_UTILS_H +#define MATH_UTILS_H + +#include "c_cpp_aliases/aliases.h" + +f32 clamp(f32 value, f32 min, f32 max); + +#endif // !MATH_UTILS_H diff --git a/include/raytracer/raytracer.h b/include/raytracer/raytracer.h new file mode 100644 index 0000000..ba68db2 --- /dev/null +++ b/include/raytracer/raytracer.h @@ -0,0 +1,54 @@ +#ifndef RAYTRACER_H +#define RAYTRACER_H + +#include "c_cpp_aliases/aliases.h" +#include "vector/vec.h" +#include "window/window.h" + +typedef struct { + f32 radius; + vec3f_t centre; + colour_t colour; + f32 specular; + f32 reflective; +} sphere_t; + +typedef enum { + LIGHT_TYPE_POINT, + LIGHT_TYPE_DIRECTIONAL, + LIGHT_TYPE_AMBIENT, + + COUNT_LIGHT_TYPE, +} light_type_t; + +typedef struct { + light_type_t type; + f32 intensity; + union { + vec3f_t position; + vec3f_t direction; + }; +} light_t; + +typedef struct { + sphere_t *spheres; + light_t *lights; + u32 spheres_count; + u32 lights_count; +} scene_t; + +typedef struct { + f32 t1; + f32 t2; +} solutions_t; + +typedef struct { + f32 closest_t; + sphere_t *closest_sphere; +} intersection_t; + +colour_t trace_ray(vec3f_t origin, vec3f_t direction, f32 t_min, f32 t_max, + const scene_t *scene, colour_t default_colour, + u32 recursion_depth); + +#endif // !RAYTRACER_H diff --git a/include/window/window.h b/include/window/window.h index d0b2370..3c2f4b1 100644 --- a/include/window/window.h +++ b/include/window/window.h @@ -40,4 +40,7 @@ void swap_buffers(window_t *wnd); vec3f_t window_to_viewport(window_t *wnd, i32 x, i32 y, vec3f_t viewport); +colour_t colour_add_colour(colour_t a, colour_t b); +colour_t colour_mul(colour_t colour, f32 scalar); + #endif // !WINDOW_H diff --git a/src/math/math_utils.c b/src/math/math_utils.c new file mode 100644 index 0000000..6307718 --- /dev/null +++ b/src/math/math_utils.c @@ -0,0 +1,14 @@ +#include "math/math_utils.h" +#include "c_cpp_aliases/aliases.h" + +f32 clamp(f32 value, f32 min, f32 max) { + if (value < min) { + return min; + } + + if (value > max) { + return max; + } + + return value; +} diff --git a/src/raytracer/main.c b/src/raytracer/main.c index 15d2f21..0723064 100644 --- a/src/raytracer/main.c +++ b/src/raytracer/main.c @@ -1,78 +1,14 @@ #include "c_cpp_aliases/aliases.h" +#include "raytracer/raytracer.h" #include "vector/vec.h" #include "window/window.h" #include #include #include -#include -#include #include -#define EPSILON 0.001f - #define ARR_LEN(ARR) sizeof(ARR) / sizeof(ARR[0]) - -typedef struct { - f32 radius; - vec3f_t centre; - colour_t colour; - f32 specular; -} sphere_t; - -typedef enum { - LIGHT_TYPE_POINT, - LIGHT_TYPE_DIRECTIONAL, - LIGHT_TYPE_AMBIENT, - - COUNT_LIGHT_TYPE, -} light_type_t; - -typedef struct { - light_type_t type; - f32 intensity; - union { - vec3f_t position; - vec3f_t direction; - }; -} light_t; - -typedef struct { - sphere_t *spheres; - light_t *lights; - u32 spheres_count; - u32 lights_count; -} scene_t; - -typedef struct { - f32 t1; - f32 t2; -} solutions_t; - -typedef struct { - f32 closest_t; - sphere_t *closest_sphere; -} intersection_t; - -colour_t trace_ray(vec3f_t origin, vec3f_t direction, f32 t_min, f32 t_max, - const scene_t *scene, colour_t default_colour); -intersection_t find_closest_intersection(vec3f_t origin, vec3f_t direction, - f32 t_min, f32 t_max, - const scene_t *scene); -f32 calculate_lighting_for_intersection(vec3f_t origin, vec3f_t direction, - intersection_t intersection, - const scene_t *scene); -solutions_t ray_intersects_sphere(vec3f_t origin, vec3f_t direction, - sphere_t sphere); -f32 compute_lighting(vec3f_t position, vec3f_t surface_normal, - vec3f_t view_vector, f32 specular_exponent, - const scene_t *scene); -f32 light_diffuse(f32 light_intensity, vec3f_t light_direction, - vec3f_t surface_normal); -f32 light_specular(f32 light_intensity, vec3f_t light_direction, - vec3f_t surface_normal, vec3f_t view_vector, - f32 specular_exponent); -f32 cos_angle_between_vectors(vec3f_t v1, vec3f_t v2); -f32 clamp(f32 value, f32 min, f32 max); +#define RECURSION_DEPTH 3 i32 main(i32 argc, char *argv[]) { colour_t bg = @@ -97,6 +33,7 @@ i32 main(i32 argc, char *argv[]) { (colour_t){ .rgba.r = 245, .rgba.g = 238, .rgba.b = 158, .rgba.a = 255}, .specular = 500.0f, + .reflective = 0.3f, }, (sphere_t){ .radius = 1.0f, @@ -105,6 +42,7 @@ i32 main(i32 argc, char *argv[]) { (colour_t){ .rgba.r = 59, .rgba.g = 142, .rgba.b = 165, .rgba.a = 255}, .specular = 10.0f, + .reflective = 0.1f, }, (sphere_t){ .radius = 1.0f, @@ -113,6 +51,7 @@ i32 main(i32 argc, char *argv[]) { (colour_t){ .rgba.r = 171, .rgba.g = 52, .rgba.b = 40, .rgba.a = 255}, .specular = 500.0f, + .reflective = 0.4f, }, (sphere_t){ .radius = 5000.0f, @@ -121,6 +60,7 @@ i32 main(i32 argc, char *argv[]) { (colour_t){ .rgba.r = 255, .rgba.g = 255, .rgba.b = 0, .rgba.a = 255}, .specular = 1000.0f, + .reflective = 0.5f, }, }; @@ -167,7 +107,8 @@ i32 main(i32 argc, char *argv[]) { for (i32 y = h_min; y < h_max; ++y) { for (i32 x = w_min; x < w_max; ++x) { vec3f_t direction = window_to_viewport(&window, x, y, viewport); - colour_t colour = trace_ray(camera, direction, 1, INFINITY, &scene, bg); + colour_t colour = trace_ray(camera, direction, 1, INFINITY, &scene, bg, + RECURSION_DEPTH); set_pixel(&window, x, y, colour); } } @@ -179,189 +120,3 @@ i32 main(i32 argc, char *argv[]) { return EXIT_SUCCESS; } - -colour_t trace_ray(vec3f_t origin, vec3f_t direction, f32 t_min, f32 t_max, - const scene_t *scene, colour_t default_colour) { - - intersection_t intersection = - find_closest_intersection(origin, direction, t_min, t_max, scene); - - if (!intersection.closest_sphere) { - return default_colour; - } - - f32 light = calculate_lighting_for_intersection(origin, direction, - intersection, scene); - - f32 r = (f32)(intersection.closest_sphere->colour.rgba.r) * light; - r = clamp(r, 0.0f, (f32)UINT8_MAX); - f32 g = (f32)(intersection.closest_sphere->colour.rgba.g) * light; - g = clamp(g, 0.0f, (f32)UINT8_MAX); - f32 b = (f32)(intersection.closest_sphere->colour.rgba.b) * light; - b = clamp(b, 0.0f, (f32)UINT8_MAX); - - return (colour_t){ - .rgba.r = (u8)r, - .rgba.g = (u8)g, - .rgba.b = (u8)b, - .rgba.a = intersection.closest_sphere->colour.rgba.a, - }; -} - -intersection_t find_closest_intersection(vec3f_t origin, vec3f_t direction, - f32 t_min, f32 t_max, - const scene_t *scene) { - f32 closest_t = INFINITY; - sphere_t *closest_sphere = NULL; - - for (u32 i = 0; i < scene->spheres_count; ++i) { - solutions_t solutions = - ray_intersects_sphere(origin, direction, scene->spheres[i]); - - if (solutions.t1 >= t_min && solutions.t1 <= t_max && - solutions.t1 < closest_t) { - closest_t = solutions.t1; - closest_sphere = &(scene->spheres[i]); - } - - if (solutions.t2 >= t_min && solutions.t2 <= t_max && - solutions.t2 < closest_t) { - closest_t = solutions.t2; - closest_sphere = &(scene->spheres[i]); - } - } - - return (intersection_t){closest_t, closest_sphere}; -} - -f32 calculate_lighting_for_intersection(vec3f_t origin, vec3f_t direction, - intersection_t intersection, - const scene_t *scene) { - vec3f_t _direction = vec_mul_num(vec3f_t, direction, intersection.closest_t); - vec3f_t position = vec_add(vec3f_t, origin, _direction); - - vec3f_t surface_normal = - vec_sub(vec3f_t, position, intersection.closest_sphere->centre); - - f32 normal_magnitude = vec_magnitude(vec3f_t, surface_normal); - surface_normal = vec_div_num(vec3f_t, surface_normal, normal_magnitude); - - vec3f_t view_vector = vec_mul_num(vec3f_t, direction, -1.0f); - - return compute_lighting(position, surface_normal, view_vector, - intersection.closest_sphere->specular, scene); -} - -solutions_t ray_intersects_sphere(vec3f_t origin, vec3f_t direction, - sphere_t sphere) { - f32 r = sphere.radius; - vec3f_t CO = vec_sub(vec3f_t, origin, sphere.centre); - - f32 a = vec_dot(vec3f_t, direction, direction); - f32 b = 2.0f * vec_dot(vec3f_t, CO, direction); - f32 c = vec_dot(vec3f_t, CO, CO) - r * r; - - f32 discriminant = b * b - 4 * a * c; - if (discriminant < 0) { - return (solutions_t){INFINITY, INFINITY}; - } - - f32 t1 = (-b + sqrtf(discriminant)) / (2 * a); - f32 t2 = (-b - sqrtf(discriminant)) / (2 * a); - - return (solutions_t){t1, t2}; -} - -f32 compute_lighting(vec3f_t position, vec3f_t surface_normal, - vec3f_t view_vector, f32 specular_exponent, - const scene_t *scene) { - f32 I = 0.0f; - light_t light = {0}; - - for (u32 i = 0; i < scene->lights_count; ++i) { - light = scene->lights[i]; - - if (light.type == LIGHT_TYPE_AMBIENT) { - I += light.intensity; - } else { - vec3f_t light_direction = {0}; - f32 t_max = EPSILON; - - switch (light.type) { - case LIGHT_TYPE_POINT: - light_direction = vec_sub(vec3f_t, light.position, position); - t_max = 1; - break; - case LIGHT_TYPE_DIRECTIONAL: - light_direction = light.direction; - t_max = INFINITY; - break; - default: - break; - } - - intersection_t shadow = find_closest_intersection( - position, light_direction, EPSILON, t_max, scene); - if (shadow.closest_sphere != NULL) { - continue; - } - - I += light_diffuse(light.intensity, light_direction, surface_normal); - - if (specular_exponent != -1.0f) { - I += light_specular(light.intensity, light_direction, surface_normal, - view_vector, specular_exponent); - } - } - } - - return I; -} - -f32 light_diffuse(f32 light_intensity, vec3f_t light_direction, - vec3f_t surface_normal) { - return light_intensity * - cos_angle_between_vectors(light_direction, surface_normal); -} - -f32 light_specular(f32 light_intensity, vec3f_t light_direction, - vec3f_t surface_normal, vec3f_t view_vector, - f32 specular_exponent) { - vec3f_t _2N = vec_mul_num(vec3f_t, surface_normal, 2.0f); - f32 dot_product = vec_dot(vec3f_t, light_direction, surface_normal); - - vec3f_t _2N_mul_dot = vec_mul_num(vec3f_t, _2N, dot_product); - - vec3f_t R = vec_sub(vec3f_t, _2N_mul_dot, light_direction); - - return light_intensity * - powf(cos_angle_between_vectors(R, view_vector), specular_exponent); -} - -f32 cos_angle_between_vectors(vec3f_t v1, vec3f_t v2) { - f32 dot_product = vec_dot(vec3f_t, v1, v2); - - if (dot_product < 0.0f) { - return 0.0f; - } - - f32 divisor = vec_magnitude(vec3f_t, v1) * vec_magnitude(vec3f_t, v2); - - if (divisor == 0.0f) { - return 0.0f; - } - - return dot_product / divisor; -} - -f32 clamp(f32 value, f32 min, f32 max) { - if (value < min) { - return min; - } - - if (value > max) { - return max; - } - - return value; -} diff --git a/src/raytracer/raytracer.c b/src/raytracer/raytracer.c new file mode 100644 index 0000000..78d8b92 --- /dev/null +++ b/src/raytracer/raytracer.c @@ -0,0 +1,201 @@ +#include "raytracer/raytracer.h" +#include "c_cpp_aliases/aliases.h" +#include "vector/vec.h" +#include "window/window.h" +#include + +#define SPECULAR_EPSILON 0.001f +#define REFLECTIVE_EPSILON 0.1f + +INTERNAL intersection_t find_closest_intersection(vec3f_t origin, + vec3f_t direction, f32 t_min, + f32 t_max, + const scene_t *scene); +INTERNAL solutions_t ray_intersects_sphere(vec3f_t origin, vec3f_t direction, + sphere_t sphere); +INTERNAL f32 compute_lighting(vec3f_t position, vec3f_t surface_normal, + vec3f_t view_vector, f32 specular_exponent, + const scene_t *scene); +INTERNAL f32 light_diffuse(f32 light_intensity, vec3f_t light_direction, + vec3f_t surface_normal); +INTERNAL f32 light_specular(f32 light_intensity, vec3f_t light_direction, + vec3f_t surface_normal, vec3f_t view_vector, + f32 specular_exponent); +INTERNAL vec3f_t reflect_ray(vec3f_t light_direction, vec3f_t surface_normal); +INTERNAL f32 cos_angle_between_vectors(vec3f_t v1, vec3f_t v2); + +colour_t trace_ray(vec3f_t origin, vec3f_t direction, f32 t_min, f32 t_max, + const scene_t *scene, colour_t default_colour, + u32 recursion_depth) { + intersection_t intersection = + find_closest_intersection(origin, direction, t_min, t_max, scene); + + if (!intersection.closest_sphere) { + return default_colour; + } + + vec3f_t _direction = vec_mul_num(vec3f_t, direction, intersection.closest_t); + vec3f_t position = vec_add(vec3f_t, origin, _direction); + + vec3f_t surface_normal = + vec_sub(vec3f_t, position, intersection.closest_sphere->centre); + + f32 normal_magnitude = vec_magnitude(vec3f_t, surface_normal); + surface_normal = vec_div_num(vec3f_t, surface_normal, normal_magnitude); + + vec3f_t view_vector = vec_mul_num(vec3f_t, direction, -1.0f); + + f32 light = compute_lighting(position, surface_normal, view_vector, + intersection.closest_sphere->specular, scene); + + colour_t local_colour = + colour_mul(intersection.closest_sphere->colour, light); + + f32 reflective = intersection.closest_sphere->reflective; + if (recursion_depth <= 0 || reflective <= 0) { + return local_colour; + } + + vec3f_t R = reflect_ray(view_vector, surface_normal); + colour_t reflected_colour = + trace_ray(position, R, REFLECTIVE_EPSILON, INFINITY, scene, + default_colour, recursion_depth - 1); + + local_colour = colour_mul(local_colour, 1.0f - reflective); + reflected_colour = colour_mul(reflected_colour, reflective); + + return colour_add_colour(local_colour, reflected_colour); +} + +INTERNAL intersection_t find_closest_intersection(vec3f_t origin, + vec3f_t direction, f32 t_min, + f32 t_max, + const scene_t *scene) { + f32 closest_t = INFINITY; + sphere_t *closest_sphere = NULL; + + for (u32 i = 0; i < scene->spheres_count; ++i) { + solutions_t solutions = + ray_intersects_sphere(origin, direction, scene->spheres[i]); + + if (solutions.t1 >= t_min && solutions.t1 <= t_max && + solutions.t1 < closest_t) { + closest_t = solutions.t1; + closest_sphere = &(scene->spheres[i]); + } + + if (solutions.t2 >= t_min && solutions.t2 <= t_max && + solutions.t2 < closest_t) { + closest_t = solutions.t2; + closest_sphere = &(scene->spheres[i]); + } + } + + return (intersection_t){closest_t, closest_sphere}; +} + +INTERNAL solutions_t ray_intersects_sphere(vec3f_t origin, vec3f_t direction, + sphere_t sphere) { + f32 r = sphere.radius; + vec3f_t CO = vec_sub(vec3f_t, origin, sphere.centre); + + f32 a = vec_dot(vec3f_t, direction, direction); + f32 b = 2.0f * vec_dot(vec3f_t, CO, direction); + f32 c = vec_dot(vec3f_t, CO, CO) - r * r; + + f32 discriminant = b * b - 4 * a * c; + if (discriminant < 0) { + return (solutions_t){INFINITY, INFINITY}; + } + + f32 t1 = (-b + sqrtf(discriminant)) / (2 * a); + f32 t2 = (-b - sqrtf(discriminant)) / (2 * a); + + return (solutions_t){t1, t2}; +} + +INTERNAL f32 compute_lighting(vec3f_t position, vec3f_t surface_normal, + vec3f_t view_vector, f32 specular_exponent, + const scene_t *scene) { + f32 I = 0.0f; + light_t light = {0}; + + for (u32 i = 0; i < scene->lights_count; ++i) { + light = scene->lights[i]; + + if (light.type == LIGHT_TYPE_AMBIENT) { + I += light.intensity; + } else { + vec3f_t light_direction = {0}; + f32 t_max = SPECULAR_EPSILON; + + switch (light.type) { + case LIGHT_TYPE_POINT: + light_direction = vec_sub(vec3f_t, light.position, position); + t_max = 1; + break; + case LIGHT_TYPE_DIRECTIONAL: + light_direction = light.direction; + t_max = INFINITY; + break; + default: + break; + } + + intersection_t shadow = find_closest_intersection( + position, light_direction, SPECULAR_EPSILON, t_max, scene); + if (shadow.closest_sphere != NULL) { + continue; + } + + I += light_diffuse(light.intensity, light_direction, surface_normal); + + if (specular_exponent != -1.0f) { + I += light_specular(light.intensity, light_direction, surface_normal, + view_vector, specular_exponent); + } + } + } + + return I; +} + +INTERNAL f32 light_diffuse(f32 light_intensity, vec3f_t light_direction, + vec3f_t surface_normal) { + return light_intensity * + cos_angle_between_vectors(light_direction, surface_normal); +} + +INTERNAL f32 light_specular(f32 light_intensity, vec3f_t light_direction, + vec3f_t surface_normal, vec3f_t view_vector, + f32 specular_exponent) { + vec3f_t R = reflect_ray(light_direction, surface_normal); + + return light_intensity * + powf(cos_angle_between_vectors(R, view_vector), specular_exponent); +} + +INTERNAL vec3f_t reflect_ray(vec3f_t light_direction, vec3f_t surface_normal) { + vec3f_t _2N = vec_mul_num(vec3f_t, surface_normal, 2.0f); + f32 dot_product = vec_dot(vec3f_t, light_direction, surface_normal); + + vec3f_t _2N_mul_dot = vec_mul_num(vec3f_t, _2N, dot_product); + + return vec_sub(vec3f_t, _2N_mul_dot, light_direction); +} + +INTERNAL f32 cos_angle_between_vectors(vec3f_t v1, vec3f_t v2) { + f32 dot_product = vec_dot(vec3f_t, v1, v2); + + if (dot_product < 0.0f) { + return 0.0f; + } + + f32 divisor = vec_magnitude(vec3f_t, v1) * vec_magnitude(vec3f_t, v2); + + if (divisor == 0.0f) { + return 0.0f; + } + + return dot_product / divisor; +} diff --git a/src/window/window.c b/src/window/window.c index 81308b3..962bbcd 100644 --- a/src/window/window.c +++ b/src/window/window.c @@ -1,11 +1,13 @@ #include "window/window.h" #include "c_cpp_aliases/aliases.h" +#include "math/math_utils.h" #include "vector/vec.h" #include #include #include #include #include +#include #include u32 index_from_coordinates(window_t *wnd, u32 x, u32 y); @@ -164,3 +166,26 @@ vec3f_t window_to_viewport(window_t *wnd, i32 x, i32 y, vec3f_t viewport) { .z = viewport.z, }; } + +colour_t colour_add_colour(colour_t a, colour_t b) { + f32 alpha = clamp((f32)(a.rgba.a + b.rgba.a), 0.0f, (f32)UINT8_MAX); + + return (colour_t){.rgba.r = a.rgba.r + b.rgba.r, + .rgba.g = a.rgba.g + b.rgba.g, + .rgba.b = a.rgba.b + b.rgba.b, + .rgba.a = (u8)alpha}; +} + +colour_t colour_mul(colour_t colour, f32 scalar) { + f32 r = (f32)colour.rgba.r * scalar; + r = clamp(r, 0.0f, (f32)UINT8_MAX); + f32 g = (f32)colour.rgba.g * scalar; + g = clamp(g, 0.0f, (f32)UINT8_MAX); + f32 b = (f32)colour.rgba.b * scalar; + b = clamp(b, 0.0f, (f32)UINT8_MAX); + + return (colour_t){.rgba.r = (u8)r, + .rgba.g = (u8)g, + .rgba.b = (u8)b, + .rgba.a = colour.rgba.a}; +}