diff --git a/.gitignore b/.gitignore index fd8c240..50229d2 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ test/test_world test/test_camera test/test_shapes test/test_planes +test/test_patterns raytracer *.ppm *.png diff --git a/lighting.h b/lighting.h new file mode 100644 index 0000000..c5d207a --- /dev/null +++ b/lighting.h @@ -0,0 +1,48 @@ +#pragma once + +#include "materials.h" +#include "shapes.h" +#include "lights.h" +#include "patterns.h" + +inline static struct tuple lighting(struct material material, + struct shape const * const object, + struct light light, + struct tuple point, + struct tuple eyev, + struct tuple normalv, + bool in_shadow) +{ + struct tuple color; + if (material.has_pattern) { + color = pattern_at_object(material.pattern, object->transform, point); + } else { + color = material.color; + } + struct tuple effective_color = hadmard_product(color, light.intensity); + + struct tuple lightv = tuple_normalize(tuple_sub(light.position, point)); + + struct tuple ambient = tuple_mul(effective_color, material.ambient); + + if (in_shadow) { + return ambient; + } + + float light_dot_normal = tuple_dot(lightv, normalv); + if (light_dot_normal < 0.0f) { + return ambient; + } else { + struct tuple 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) { + return tuple_add(ambient, diffuse); + } else { + float factor = powf(reflect_dot_eye, material.shininess); + struct tuple specular = tuple_mul(light.intensity, material.specular * factor); + return tuple_add(tuple_add(ambient, diffuse), specular); + } + } +} diff --git a/materials.h b/materials.h index 49e3054..3ef3f18 100644 --- a/materials.h +++ b/materials.h @@ -2,24 +2,30 @@ #include "tuples.h" #include "lights.h" +#include "patterns.h" #include "math.h" struct material { - struct tuple color; float ambient; float diffuse; float specular; float shininess; + bool has_pattern; + union { + struct tuple color; + struct pattern pattern; + }; }; inline static struct material material() { return (struct material){ - color(1.0f, 1.0f, 1.0f), 0.1f, 0.9f, 0.9f, - 200.0f + 200.0f, + .has_pattern = false, + .color = color(1.0f, 1.0f, 1.0f), }; } @@ -32,38 +38,3 @@ inline static bool material_equal(struct material a, struct material b) 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, - bool in_shadow) -{ - 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); - - if (in_shadow) { - return ambient; - } - - float light_dot_normal = tuple_dot(lightv, normalv); - if (light_dot_normal < 0.0f) { - return ambient; - } else { - struct tuple 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) { - return tuple_add(ambient, diffuse); - } else { - float factor = powf(reflect_dot_eye, material.shininess); - struct tuple 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 698e9d0..b7d60a0 100644 --- a/math.h +++ b/math.h @@ -7,6 +7,7 @@ #define powf __builtin_powf #define tanf __builtin_tanf #define fabsf __builtin_fabsf +#define floorf __builtin_floorf static const float tau = 6.283185307179586f; static const float pi = tau / 2.0f; diff --git a/patterns.h b/patterns.h new file mode 100644 index 0000000..86958dc --- /dev/null +++ b/patterns.h @@ -0,0 +1,102 @@ +#pragma once + +#include +#include "tuples.h" +#include "matrices.h" + +enum pattern_type { + PATTERN_STRIPE, + PATTERN_GRADIENT, + PATTERN_RING, + PATTERN_CHECKERS +}; + +struct pattern { + enum pattern_type type; + struct tuple a; + struct tuple b; + struct mat4x4 transform; +}; + +inline static struct pattern stripe_pattern(struct tuple a, struct tuple b) +{ + return (struct pattern) { + PATTERN_STRIPE, + a, + b, + mat4x4_identity() + }; +} + +inline static struct pattern gradient_pattern(struct tuple a, struct tuple b) +{ + return (struct pattern) { + PATTERN_GRADIENT, + a, + b, + mat4x4_identity() + }; +} + +inline static struct pattern ring_pattern(struct tuple a, struct tuple b) +{ + return (struct pattern) { + PATTERN_RING, + a, + b, + mat4x4_identity() + }; +} + +inline static struct pattern checkers_pattern(struct tuple a, struct tuple b) +{ + return (struct pattern) { + PATTERN_CHECKERS, + a, + b, + mat4x4_identity() + }; +} + +inline static struct tuple stripe_at(struct pattern pattern, struct tuple point) +{ + return ((int)floorf(point.x)) % 2 == 0 ? pattern.a : pattern.b; +} + +inline static struct tuple gradient_at(struct pattern pattern, struct tuple point) +{ + struct tuple distance = tuple_sub(pattern.b, pattern.a); + float fraction = point.x - floorf(point.x); + + return tuple_add(pattern.a, tuple_mul(distance, fraction)); +} + +inline static struct tuple ring_at(struct pattern pattern, struct tuple point) +{ + return ((int)floorf(sqrtf(point.x * point.x + point.z * point.z))) % 2 == 0 ? pattern.a : pattern.b; +} + +inline static struct tuple checkers_at(struct pattern pattern, struct tuple point) +{ + return (int)(floorf(point.x) + floorf(point.y) + floorf(point.z)) % 2 == 0 ? pattern.a : pattern.b; +} + +inline static struct tuple pattern_at_object(struct pattern pattern, struct mat4x4 object_transform, struct tuple point) +{ + struct tuple object_point = mat4x4_mul_t(mat4x4_inverse(object_transform), point); + struct tuple pattern_point = mat4x4_mul_t(mat4x4_inverse(pattern.transform), object_point); + + switch (pattern.type) { + case PATTERN_STRIPE: + return stripe_at(pattern, pattern_point); + case PATTERN_GRADIENT: + return gradient_at(pattern, pattern_point); + case PATTERN_RING: + return ring_at(pattern, pattern_point); + case PATTERN_CHECKERS: + return checkers_at(pattern, pattern_point); + default: + assert(false); + break; + } +} diff --git a/raytracer.c b/raytracer.c index f830ced..543ae9d 100644 --- a/raytracer.c +++ b/raytracer.c @@ -23,7 +23,11 @@ int main() float half = wall_size / 2.0f; struct shape floor = plane(); - floor.material.color = color(1.0f, 0.9f, 0.9f); + floor.material.has_pattern = true; + floor.material.pattern.a = color(1.0f, 0.3f, 0.9f); + floor.material.pattern.b = color(0.2f, 0.9f, 0.5f); + floor.material.pattern.type = PATTERN_CHECKERS; + //floor.material.pattern.transform = scaling(10.0f, 2.0f, 2.0f); floor.material.specular = 0.0f; /* @@ -128,17 +132,17 @@ int main() h6.transform = mat4x4_mul_m(h6_rr, h6_r); h6.transform = mat4x4_mul_m(h6_t, h6.transform); - h1.material = floor.material; + h1.material.specular = 0.0f; h1.material.color = color(1.0f, 0.0f, 0.0f); - h2.material = floor.material; + h2.material.specular = 0.0f; h2.material.color = color(0.0f, 1.0f, 0.0f); - h3.material = floor.material; + h3.material.specular = 0.0f; h3.material.color = color(0.0f, 0.0f, 1.0f); - h4.material = floor.material; + h4.material.specular = 0.0f; h4.material.color = color(1.0f, 1.0f, 0.0f); - h5.material = floor.material; + h5.material.specular = 0.0f; h5.material.color = color(0.0f, 1.0f, 1.0f); - h6.material = floor.material; + h6.material.specular = 0.0f; h6.material.color = color(1.0f, 0.0f, 1.0f); struct world world; @@ -165,6 +169,9 @@ int main() for (int i = 0; i < 360; i++) { + world.objects[0].material.pattern.transform = + mat4x4_mul_m(rotation_y(-tau / 360 * (float)i * 2.0f), mat4x4_mul_m(translation((float)i / 360.0f * 10.0f, 0.0f, 0.0f), scaling(3.0f, 3.0f, 3.0f))); + _camera.transform = view_transform(point(2.0f * cosf(pi / 360 * (float)i * 0.5), 8.5f, 1.0f * sinf(pi / 360 * (float)i * 0.5)), diff --git a/test/run.sh b/test/run.sh index 54db03e..746ba2a 100644 --- a/test/run.sh +++ b/test/run.sh @@ -3,7 +3,7 @@ set -eux -for name in tuples canvas matrices transformations rays intersections shapes spheres planes lights materials world camera; do +for name in tuples canvas matrices transformations rays intersections shapes spheres planes lights materials world camera patterns; do gcc -g -gdwarf-5 \ -Wall -Werror -Wfatal-errors \ -Wno-error=unused-variable \ diff --git a/test/test_materials.c b/test/test_materials.c index 802c9bd..5485cfb 100644 --- a/test/test_materials.c +++ b/test/test_materials.c @@ -2,8 +2,9 @@ #include #include "materials.h" +#include "lighting.h" #include "runner.h" -#include "lights.h" +#include "shapes.h" static bool materials_test_0(const char ** scenario) { @@ -28,7 +29,8 @@ static bool materials_test_1(const char ** scenario) 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, false); + struct shape object = shape(); + struct tuple result = lighting(m, &object, light, position, eyev, normalv, false); return tuple_equal(result, color(1.9f, 1.9f, 1.9f)); } @@ -43,7 +45,8 @@ static bool materials_test_2(const char ** scenario) 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, false); + struct shape object = shape(); + struct tuple result = lighting(m, &object, light, position, eyev, normalv, false); return tuple_equal(result, color(1.0f, 1.0f, 1.0f)); } @@ -58,7 +61,8 @@ static bool materials_test_3(const char ** scenario) 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, false); + struct shape object = shape(); + struct tuple result = lighting(m, &object, light, position, eyev, normalv, false); return tuple_equal(result, color(0.7364f, 0.7364f, 0.7364f)); } @@ -73,7 +77,8 @@ static bool materials_test_4(const char ** scenario) 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, false); + struct shape object = shape(); + struct tuple result = lighting(m, &object, light, position, eyev, normalv, false); return tuple_equal(result, color(1.63639, 1.63639, 1.63639)); } @@ -88,7 +93,8 @@ static bool materials_test_5(const char ** scenario) 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, false); + struct shape object = shape(); + struct tuple result = lighting(m, &object, light, position, eyev, normalv, false); return tuple_equal(result, color(0.1f, 0.1f, 0.1f)); } @@ -105,11 +111,37 @@ static bool materials_test_6(const char ** scenario) struct light light = point_light(point(0.0f, 0.0f, -10.0f), color(1.0f, 1.0f, 1.0f)); bool in_shadow = true; - struct tuple result = lighting(m, light, position, eyev, normalv, in_shadow); + struct shape object = shape(); + struct tuple result = lighting(m, &object, light, position, eyev, normalv, in_shadow); return tuple_equal(result, color(0.1f, 0.1f, 0.1f)); } +static bool materials_test_7(const char ** scenario) +{ + *scenario = "Lighting with a pattern applied"; + + struct material m = material(); + + struct pattern pattern = stripe_pattern(color(1.0f, 1.0f, 1.0f), color(0.0f, 0.0f, 0.0f)); + m.has_pattern = true; + m.pattern = pattern; + m.ambient = 1.0f; + m.diffuse = 0.0f; + m.specular = 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 shape object = shape(); + struct tuple c1 = lighting(m, &object, light, point(0.9f, 0.0f, 0.0f), eyev, normalv, false); + struct tuple c2 = lighting(m, &object, light, point(1.1f, 0.0f, 0.0f), eyev, normalv, false); + + return + tuple_equal(c1, color(1.0f, 1.0f, 1.0f)) && + tuple_equal(c2, color(0.0f, 0.0f, 0.0f)); +} + test_t materials_tests[] = { materials_test_0, materials_test_1, @@ -118,6 +150,7 @@ test_t materials_tests[] = { materials_test_4, materials_test_5, materials_test_6, + materials_test_7, }; RUNNER(materials_tests) diff --git a/test/test_patterns.c b/test/test_patterns.c new file mode 100644 index 0000000..14418c9 --- /dev/null +++ b/test/test_patterns.c @@ -0,0 +1,176 @@ +#include +#include + +#include "patterns.h" +#include "spheres.h" +#include "transformations.h" +#include "runner.h" + +#define black color(0.0f, 0.0f, 0.0f) +#define white color(1.0f, 1.0f, 1.0f) + +static bool patterns_test_0(const char ** scenario) +{ + *scenario = "Creating a stripe pattern"; + + struct pattern pattern = stripe_pattern(white, black); + + return + tuple_equal(pattern.a, white) && + tuple_equal(pattern.b, black); +} + +static bool patterns_test_1(const char ** scenario) +{ + *scenario = "A stripe pattern is constant in y"; + + struct pattern pattern = stripe_pattern(white, black); + + return + tuple_equal(stripe_at(pattern, point(0.0f, 0.0f, 0.0f)), white) && + tuple_equal(stripe_at(pattern, point(0.0f, 1.0f, 0.0f)), white) && + tuple_equal(stripe_at(pattern, point(0.0f, 2.0f, 0.0f)), white); +} + +static bool patterns_test_2(const char ** scenario) +{ + *scenario = "A stripe pattern is constant in z"; + + struct pattern pattern = stripe_pattern(white, black); + + return + tuple_equal(stripe_at(pattern, point(0.0f, 0.0f, 0.0f)), white) && + tuple_equal(stripe_at(pattern, point(0.0f, 0.0f, 1.0f)), white) && + tuple_equal(stripe_at(pattern, point(0.0f, 0.0f, 2.0f)), white); +} + +static bool patterns_test_3(const char ** scenario) +{ + *scenario = "A stripe pattern alternates in x"; + + struct pattern pattern = stripe_pattern(white, black); + + return + tuple_equal(stripe_at(pattern, point(0.0f, 0.0f, 0.0f)), white) && + tuple_equal(stripe_at(pattern, point(0.9f, 0.0f, 0.0f)), white) && + tuple_equal(stripe_at(pattern, point(1.0f, 0.0f, 0.0f)), black) && + tuple_equal(stripe_at(pattern, point(-0.1f, 0.0f, 0.0f)), black) && + tuple_equal(stripe_at(pattern, point(-1.0f, 0.0f, 0.0f)), black) && + tuple_equal(stripe_at(pattern, point(-1.1f, 0.0f, 0.0f)), white); +} + +static bool patterns_test_4(const char ** scenario) +{ + *scenario = "Stripes with an object transformation"; + + struct shape object = sphere(); + object.transform = scaling(2.0f, 2.0f, 2.0f); + struct pattern pattern = stripe_pattern(white, black); + struct tuple c = pattern_at_object(pattern, object.transform, point(1.5f, 0.0f, 0.0f)); + + return tuple_equal(c, white); +} + +static bool patterns_test_5(const char ** scenario) +{ + *scenario = "Stripes with a pattern transformation"; + + struct shape object = sphere(); + struct pattern pattern = stripe_pattern(white, black); + pattern.transform = scaling(2.0f, 2.0f, 2.0f); + struct tuple c = pattern_at_object(pattern, object.transform, point(1.5f, 0.0f, 0.0f)); + + return tuple_equal(c, white); +} + +static bool patterns_test_6(const char ** scenario) +{ + *scenario = "Stripes with both an object and a pattern transformation"; + + struct shape object = sphere(); + object.transform = scaling(2.0f, 2.0f, 2.0f); + struct pattern pattern = stripe_pattern(white, black); + pattern.transform = translation(0.5f, 0.0f, 0.0f); + struct tuple c = pattern_at_object(pattern, object.transform, point(2.5f, 0.0f, 0.0f)); + + return tuple_equal(c, white); +} + +static bool patterns_test_7(const char ** scenario) +{ + *scenario = "A gradient linearly interpolates between colors"; + + struct pattern pattern = gradient_pattern(white, black); + + return + tuple_equal(gradient_at(pattern, point(0.0f, 0.0f, 0.0f)), white) && + tuple_equal(gradient_at(pattern, point(0.25f, 0.0f, 0.0f)), color(0.75f, 0.75f, 0.75f)) && + tuple_equal(gradient_at(pattern, point(0.5f, 0.0f, 0.0f)), color(0.5f, 0.5f, 0.5f)) && + tuple_equal(gradient_at(pattern, point(0.75f, 0.0f, 0.0f)), color(0.25f, 0.25f, 0.25f)); +} + +static bool patterns_test_8(const char ** scenario) +{ + *scenario = "A ring should extend in both x and z"; + + struct pattern pattern = ring_pattern(white, black); + + return + tuple_equal(ring_at(pattern, point(0.0f, 0.0f, 0.0f)), white) && + tuple_equal(ring_at(pattern, point(1.0f, 0.0f, 0.0f)), black) && + tuple_equal(ring_at(pattern, point(0.0f, 0.0f, 1.0f)), black) && + tuple_equal(ring_at(pattern, point(0.708f, 0.0f, 0.708f)), black); +} + +static bool patterns_test_9(const char ** scenario) +{ + *scenario = "Checkers should repeat in x"; + + struct pattern pattern = checkers_pattern(white, black); + + return + tuple_equal(checkers_at(pattern, point(0.0f, 0.0f, 0.0f)), white) && + tuple_equal(checkers_at(pattern, point(0.99f, 0.0f, 0.0f)), white) && + tuple_equal(checkers_at(pattern, point(1.01f, 0.0f, 0.0f)), black); +} + +static bool patterns_test_10(const char ** scenario) +{ + *scenario = "Checkers should repeat in y"; + + struct pattern pattern = checkers_pattern(white, black); + + return + tuple_equal(checkers_at(pattern, point(0.0f, 0.0f, 0.0f)), white) && + tuple_equal(checkers_at(pattern, point(0.0f, 0.99f, 0.0f)), white) && + tuple_equal(checkers_at(pattern, point(0.0f, 1.01f, 0.0f)), black); +} + +static bool patterns_test_11(const char ** scenario) +{ + *scenario = "Checkers should repeat in z"; + + struct pattern pattern = checkers_pattern(white, black); + + return + tuple_equal(checkers_at(pattern, point(0.0f, 0.0f, 0.0f)), white) && + tuple_equal(checkers_at(pattern, point(0.0f, 0.0f, 0.99f)), white) && + tuple_equal(checkers_at(pattern, point(0.0f, 0.0f, 1.01f)), black); +} + +test_t patterns_tests[] = { + patterns_test_0, + patterns_test_1, + patterns_test_2, + patterns_test_3, + patterns_test_4, + patterns_test_5, + patterns_test_6, + patterns_test_7, + patterns_test_8, + patterns_test_9, + patterns_test_10, + patterns_test_11 +}; + +RUNNER(patterns_tests); diff --git a/world.h b/world.h index 39922fe..1396745 100644 --- a/world.h +++ b/world.h @@ -6,6 +6,7 @@ #include "intersections_shapes.h" #include "rays.h" #include "materials.h" +#include "lighting.h" #define WORLD_MAX_OBJECTS 128 @@ -72,6 +73,7 @@ inline static struct tuple world_shade_hit(struct world * world, struct computat bool is_shadowed = world_is_shadowed(world, computations->over_point); struct tuple color = lighting(computations->object->material, + computations->object, world->light, computations->point, computations->eyev,