From 15ac0aa0318dca5c736299ad6ef6f0a25697bb3a Mon Sep 17 00:00:00 2001 From: Zack Buhman Date: Mon, 5 Aug 2024 21:25:15 -0500 Subject: [PATCH] chapter 6 --- .gitignore | 8 +++- lights.h | 16 +++++++ materials.h | 68 +++++++++++++++++++++++++++ math.h | 1 + raytracer.c | 25 +++++++--- spheres.h | 16 ++++++- test/run.sh | 2 +- test/test_lights.c | 23 +++++++++ test/test_materials.c | 105 +++++++++++++++++++++++++++++++++++++++++ test/test_spheres.c | 106 ++++++++++++++++++++++++++++++++++++++++++ test/test_tuples.c | 22 +++++++++ transformations.h | 1 + tuples.h | 6 +++ 13 files changed, 390 insertions(+), 9 deletions(-) create mode 100644 lights.h create mode 100644 materials.h create mode 100644 test/test_lights.c create mode 100644 test/test_materials.c diff --git a/.gitignore b/.gitignore index bbe7f47..8be3252 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ *.o +*.d *.gch test/test_tuples test/test_canvas @@ -6,4 +7,9 @@ test/test_matrices test/test_transformations test/test_intersections test/test_rays -*.ppm \ No newline at end of file +test/test_lights +test/test_materials +test/test_spheres +raytracer +*.ppm +*.png \ No newline at end of file diff --git a/lights.h b/lights.h new file mode 100644 index 0000000..7602c0d --- /dev/null +++ b/lights.h @@ -0,0 +1,16 @@ +#pragma once + +#include "tuples.h" + +struct light { + struct tuple position; + struct tuple intensity; +}; + +struct light point_light(struct tuple position, struct tuple intensity) +{ + return (struct light){ + position, + intensity + }; +} diff --git a/materials.h b/materials.h new file mode 100644 index 0000000..fe2ed55 --- /dev/null +++ b/materials.h @@ -0,0 +1,68 @@ +#pragma once + +#include "tuples.h" +#include "lights.h" +#include "math.h" + +struct material { + struct tuple color; + float ambient; + float diffuse; + float specular; + float shininess; +}; + +inline static struct material material() +{ + return (struct material){ + color(1.0f, 1.0f, 1.0f), + 0.1f, + 0.9f, + 0.9f, + 200.0f + }; +} + +inline static bool material_equal(struct material a, struct material b) +{ + return + tuple_equal(a.color, b.color) && + float_equal(a.ambient, b.ambient) && + float_equal(a.diffuse, b.diffuse) && + float_equal(a.specular, b.specular) && + float_equal(a.shininess, b.shininess); +} + +inline static struct tuple lighting(struct material material, + struct light light, + struct tuple point, + struct tuple eyev, + struct tuple normalv) +{ + struct tuple effective_color = hadmard_product(material.color, light.intensity); + + struct tuple lightv = tuple_normalize(tuple_sub(light.position, point)); + + struct tuple ambient = tuple_mul(effective_color, material.ambient); + + float light_dot_normal = tuple_dot(lightv, normalv); + struct tuple diffuse; + struct tuple specular; + if (light_dot_normal < 0.0f) { + diffuse = color(0.0f, 0.0f, 0.0f); // black + specular = color(0.0f, 0.0f, 0.0f); // black + } else { + diffuse = tuple_mul(effective_color, material.diffuse * light_dot_normal); + + struct tuple reflectv = tuple_reflect(tuple_neg(lightv), normalv); + float reflect_dot_eye = tuple_dot(reflectv, eyev); + if (reflect_dot_eye <= 0.0f) { + specular = color(0.0f, 0.0f, 0.0f); // black + } else { + float factor = powf(reflect_dot_eye, material.shininess); + specular = tuple_mul(light.intensity, material.specular * factor); + } + } + + return tuple_add(tuple_add(ambient, diffuse), specular); +} diff --git a/math.h b/math.h index d7953fb..15e497a 100644 --- a/math.h +++ b/math.h @@ -4,3 +4,4 @@ #define fabsf __builtin_fabsf #define cosf __builtin_cosf #define sinf __builtin_sinf +#define powf __builtin_powf diff --git a/raytracer.c b/raytracer.c index 2692887..bc66e3e 100644 --- a/raytracer.c +++ b/raytracer.c @@ -4,6 +4,7 @@ #include "spheres.h" #include "intersections.h" #include "transformations.h" +#include "materials.h" int main() { @@ -15,12 +16,18 @@ int main() float pixel_size = wall_size / (float)canvas_pixels; float half = wall_size / 2.0f; struct canvas c = canvas(canvas_pixels, canvas_pixels); - struct tuple red = color(1.0f, 0.0f, 0.0f); + //struct tuple red = color(1.0f, 0.0f, 0.0f); struct sphere shape = sphere(); - struct mat4x4 shear = shearing(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f); - struct mat4x4 scale = scaling(0.5f, 1.0f, 1.0f); - struct mat4x4 m = mat4x4_mul_m(&shear, &scale); - shape.transform = m; + shape.material = material(); + shape.material.color = color(1.0f, 0.2f, 1.0f); + //struct mat4x4 shear = shearing(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f); + //struct mat4x4 scale = scaling(0.5f, 1.0f, 1.0f); + //struct mat4x4 m = mat4x4_mul_m(&shear, &scale); + //shape.transform = m; + + struct tuple light_position = point(-10.0f, 10.0f, -10.0f); + struct tuple light_color = color(1.0f, 1.0f, 1.0f); + struct light light = point_light(light_position, light_color); for (int y = 0; y < canvas_pixels; y++) { float world_y = half - pixel_size * y; @@ -31,8 +38,14 @@ int main() struct ray r = ray(ray_origin, tuple_normalize(tuple_sub(position, ray_origin))); struct intersections2 xs = intersect(&shape, r); struct intersection * h = hit((struct intersections *)&xs); + if (h != NULL) { - canvas_write_pixel(c, x, y, red); + struct tuple point = ray_position(r, h->t); + struct tuple normal = sphere_normal_at(h->object, point); + struct tuple eye = tuple_neg(r.direction); + + struct tuple color = lighting(h->object->material, light, point, eye, normal); + canvas_write_pixel(c, x, y, color); } } } diff --git a/spheres.h b/spheres.h index 6ee9dba..d912c29 100644 --- a/spheres.h +++ b/spheres.h @@ -1,14 +1,28 @@ #pragma once #include "matrices.h" +#include "materials.h" struct sphere { struct mat4x4 transform; + struct material material; }; inline static struct sphere sphere() { return (struct sphere){ - mat4x4_identity() + mat4x4_identity(), + material() }; } + +inline static struct tuple sphere_normal_at(struct sphere const * const s, struct tuple world_point) +{ + struct mat4x4 inv = mat4x4_inverse(&s->transform); + struct tuple object_point = mat4x4_mul_t(&inv, &world_point); + struct tuple object_normal = tuple_sub(object_point, point(0.0f, 0.0f, 0.0f)); + struct mat4x4 inv_t = mat4x4_transpose(&inv); + struct tuple world_normal = mat4x4_mul_t(&inv_t, &object_normal); + world_normal.w = 0.0f; + return tuple_normalize(world_normal); +} diff --git a/test/run.sh b/test/run.sh index 8611f48..951be59 100644 --- a/test/run.sh +++ b/test/run.sh @@ -2,7 +2,7 @@ set -eux -for name in tuples canvas matrices transformations rays intersections spheres; do +for name in tuples canvas matrices transformations rays intersections spheres lights materials; do gcc -g -gdwarf-5 \ -Wall -Werror -Wfatal-errors \ -I. \ diff --git a/test/test_lights.c b/test/test_lights.c new file mode 100644 index 0000000..ea5e058 --- /dev/null +++ b/test/test_lights.c @@ -0,0 +1,23 @@ +#include +#include + +#include "lights.h" +#include "runner.h" + +static bool lights_test_0(const char ** scenario) +{ + *scenario = "A point light has a position and intensity"; + + struct tuple position = color(0.0f, 0.0f, 0.0f); + struct tuple intensity = color(1.0f, 1.0f, 1.0f); + struct light light = point_light(position, intensity); + return + tuple_equal(light.position, position) && + tuple_equal(light.intensity, intensity); +} + +test_t lights_tests[] = { + lights_test_0, +}; + +RUNNER(lights_tests) diff --git a/test/test_materials.c b/test/test_materials.c new file mode 100644 index 0000000..916baba --- /dev/null +++ b/test/test_materials.c @@ -0,0 +1,105 @@ +#include +#include + +#include "materials.h" +#include "runner.h" +#include "lights.h" + +static bool materials_test_0(const char ** scenario) +{ + *scenario = "The default material"; + + struct material m = material(); + return + tuple_equal(m.color, color(1.0f, 1.0f, 1.0f)) && + float_equal(m.ambient, 0.1f) && + float_equal(m.diffuse, 0.9f) && + float_equal(m.specular, 0.9f) && + float_equal(m.shininess, 200.0f); +} + +static bool materials_test_1(const char ** scenario) +{ + *scenario = "Lighting with the eye between the light and the surface"; + + struct material m = material(); + struct tuple position = point(0.0f, 0.0f, 0.0f); + + struct tuple eyev = vector(0.0f, 0.0f, -1.0f); + struct tuple normalv = vector(0.0f, 0.0f, -1.0f); + struct light light = point_light(point(0.0f, 0.0f, -10.0f), color(1.0f, 1.0f, 1.0f)); + struct tuple result = lighting(m, light, position, eyev, normalv); + + return tuple_equal(result, color(1.9f, 1.9f, 1.9f)); +} + +static bool materials_test_2(const char ** scenario) +{ + *scenario = "Lighting with the eye between the light and surface, eye offset 45 degrees"; + + struct material m = material(); + struct tuple position = point(0.0f, 0.0f, 0.0f); + + struct tuple eyev = vector(0.0f, 0.7071067811865476, -0.7071067811865476); + struct tuple normalv = vector(0.0f, 0.0f, -1.0f); + struct light light = point_light(point(0.0f, 0.0f, -10.0f), color(1.0f, 1.0f, 1.0f)); + struct tuple result = lighting(m, light, position, eyev, normalv); + + return tuple_equal(result, color(1.0f, 1.0f, 1.0f)); +} + +static bool materials_test_3(const char ** scenario) +{ + *scenario = "Lighting with eye opposite surface, light offset 45 degress"; + + struct material m = material(); + struct tuple position = point(0.0f, 0.0f, 0.0f); + + struct tuple eyev = vector(0.0f, 0.0f, -1.0f); + struct tuple normalv = vector(0.0f, 0.0f, -1.0f); + struct light light = point_light(point(0.0f, 10.0f, -10.0f), color(1.0f, 1.0f, 1.0f)); + struct tuple result = lighting(m, light, position, eyev, normalv); + + return tuple_equal(result, color(0.7364f, 0.7364f, 0.7364f)); +} + +static bool materials_test_4(const char ** scenario) +{ + *scenario = "Lighting with eye in the path of the reflection vector"; + + struct material m = material(); + struct tuple position = point(0.0f, 0.0f, 0.0f); + + struct tuple eyev = vector(0.0f, -0.7071067811865476, -0.7071067811865476); + struct tuple normalv = vector(0.0f, 0.0f, -1.0f); + struct light light = point_light(point(0.0f, 10.0f, -10.0f), color(1.0f, 1.0f, 1.0f)); + struct tuple result = lighting(m, light, position, eyev, normalv); + + return tuple_equal(result, color(1.63639, 1.63639, 1.63639)); +} + +static bool materials_test_5(const char ** scenario) +{ + *scenario = "Lighting with the light behind the surface"; + + struct material m = material(); + struct tuple position = point(0.0f, 0.0f, 0.0f); + + struct tuple eyev = vector(0.0f, 0.0f, -1.0f); + struct tuple normalv = vector(0.0f, 0.0f, -1.0f); + struct light light = point_light(point(0.0f, 0.0f, 10.0f), color(1.0f, 1.0f, 1.0f)); + struct tuple result = lighting(m, light, position, eyev, normalv); + + return tuple_equal(result, color(0.1f, 0.1f, 0.1f)); +} + +test_t materials_tests[] = { + materials_test_0, + materials_test_1, + materials_test_2, + materials_test_3, + materials_test_4, + materials_test_5, +}; + +RUNNER(materials_tests) diff --git a/test/test_spheres.c b/test/test_spheres.c index 3d1f954..5020da2 100644 --- a/test/test_spheres.c +++ b/test/test_spheres.c @@ -137,6 +137,103 @@ static bool spheres_test_9(const char ** scenario) return xs.count == 0; } +static bool spheres_test_10(const char ** scenario) +{ + *scenario = "The normal on a sphere at a point on the x axis"; + + struct sphere s = sphere(); + struct tuple n = sphere_normal_at(&s, point(1.0f, 0.0f, 0.0f)); + return tuple_equal(n, vector(1.0f, 0.0f, 0.0f)); +} + +static bool spheres_test_11(const char ** scenario) +{ + *scenario = "The normal on a sphere at a point on the y axis"; + + struct sphere s = sphere(); + struct tuple n = sphere_normal_at(&s, point(0.0f, 1.0f, 0.0f)); + return tuple_equal(n, vector(0.0f, 1.0f, 0.0f)); +} + +static bool spheres_test_12(const char ** scenario) +{ + *scenario = "The normal on a sphere at a point on the z axis"; + + struct sphere s = sphere(); + struct tuple n = sphere_normal_at(&s, point(0.0f, 0.0f, 1.0f)); + return tuple_equal(n, vector(0.0f, 0.0f, 1.0f)); +} + +static bool spheres_test_13(const char ** scenario) +{ + *scenario = "The normal on a sphere at a nonaxial point"; + + struct sphere s = sphere(); + struct tuple n = sphere_normal_at(&s, point(0.5773502691896257, + 0.5773502691896257, + 0.5773502691896257)); + return tuple_equal(n, vector(0.5773502691896257, + 0.5773502691896257, + 0.5773502691896257)); +} + +static bool spheres_test_14(const char ** scenario) +{ + *scenario = "The normal is a normalized vector"; + + struct sphere s = sphere(); + struct tuple n = sphere_normal_at(&s, point(0.5773502691896257, + 0.5773502691896257, + 0.5773502691896257)); + return tuple_equal(n, tuple_normalize(n)); +} + +static bool spheres_test_15(const char ** scenario) +{ + *scenario = "Computing the normal on a translated sphere"; + + struct sphere s = sphere(); + s.transform = translation(0.0f, 1.0f, 0.0f); + struct tuple n = sphere_normal_at(&s, point(0.0f, 1.70711f, -0.70711)); + + return tuple_equal(n, vector(0.0f, 0.70711f, -0.70711f)); +} + +static bool spheres_test_16(const char ** scenario) +{ + *scenario = "Computing the normal on a transformed sphere"; + + struct sphere s = sphere(); + struct mat4x4 scale = scaling(1.0f, 0.5f, 1.0f); + struct mat4x4 rotate = rotation_z(pi / 5.0f); + struct mat4x4 m = mat4x4_mul_m(&scale, &rotate); + s.transform = m; + + struct tuple n = sphere_normal_at(&s, point(0.0f, 0.7071067811865476, -0.7071067811865476)); + + return tuple_equal(n, vector(0.0f, 0.97014f, -0.24254f)); +} + +static bool spheres_test_17(const char ** scenario) +{ + *scenario = "A sphere has a default material"; + + struct sphere s = sphere(); + struct material m = s.material; + return material_equal(m, material()); +} + +static bool spheres_test_18(const char ** scenario) +{ + *scenario = "A sphere may be assigned a material"; + + struct sphere s = sphere(); + struct material m = material(); + m.ambient = 1.0f; + s.material = m; + return material_equal(s.material, m); +} + test_t spheres_tests[] = { spheres_test_0, spheres_test_1, @@ -148,6 +245,15 @@ test_t spheres_tests[] = { spheres_test_7, spheres_test_8, spheres_test_9, + spheres_test_10, + spheres_test_11, + spheres_test_12, + spheres_test_13, + spheres_test_14, + spheres_test_15, + spheres_test_16, + spheres_test_17, + spheres_test_18, }; RUNNER(spheres_tests); diff --git a/test/test_tuples.c b/test/test_tuples.c index ffcd6c8..8a6f3ed 100644 --- a/test/test_tuples.c +++ b/test/test_tuples.c @@ -235,6 +235,26 @@ static bool tuple_test_27(const char ** scenario) return tuple_equal(hadmard_product(c1, c2), color(0.9f, 0.2f, 0.04f)); } +static bool tuple_test_28(const char ** scenario) +{ + *scenario = "Reflecting a vector approacing at 45 degrees"; + + struct tuple v = vector(1.0f, -1.0f, 0.0f); + struct tuple n = vector(0.0f, 1.0f, 0.0f); + struct tuple r = tuple_reflect(v, n); + return tuple_equal(r, vector(1.0f, 1.0f, 0.0f)); +} + +static bool tuple_test_29(const char ** scenario) +{ + *scenario = "Reflecting a vector off a slanted surface"; + + struct tuple v = vector(0.0f, -1.0f, 0.0f); + struct tuple n = vector(0.7071067811865476f, 0.7071067811865476f, 0.0f); + struct tuple r = tuple_reflect(v, n); + return tuple_equal(r, vector(1.0f, 0.0f, 0.0f)); +} + test_t tuple_tests[] = { tuple_test_0, tuple_test_1, @@ -264,6 +284,8 @@ test_t tuple_tests[] = { tuple_test_25, tuple_test_26, tuple_test_27, + tuple_test_28, + tuple_test_29, }; RUNNER(tuple_tests) diff --git a/transformations.h b/transformations.h index d9c5e9a..09a0ab4 100644 --- a/transformations.h +++ b/transformations.h @@ -20,6 +20,7 @@ inline static struct mat4x4 scaling(float x, float y, float z) } static const float tau = 6.283185307179586; +static const float pi = tau / 2.0f; inline static struct mat4x4 rotation_x(float r) { diff --git a/tuples.h b/tuples.h index 202895b..69621fa 100644 --- a/tuples.h +++ b/tuples.h @@ -153,3 +153,9 @@ inline static struct tuple hadmard_product(struct tuple c1, struct tuple c2) 0.0f ); } + +inline static struct tuple tuple_reflect(struct tuple in, struct tuple normal) +{ + float dot = tuple_dot(in, normal); + return tuple_sub(in, tuple_mul(tuple_mul(normal, 2.0f), dot)); +}