From 7337ae4837844f52ebda442caecddb418769ce67 Mon Sep 17 00:00:00 2001 From: Godzil Date: Fri, 21 Feb 2020 17:21:06 +0000 Subject: [PATCH] Finally! We have reflections! --- source/include/intersection.h | 7 ++- source/include/material.h | 3 +- source/include/world.h | 7 ++- source/intersection.cpp | 4 +- source/world.cpp | 30 ++++++++-- tests/CMakeLists.txt | 6 ++ tests/intersect_test.cpp | 12 ++++ tests/material_test.cpp | 9 ++- tests/world_test.cpp | 108 +++++++++++++++++++++++++++++++++- 9 files changed, 174 insertions(+), 12 deletions(-) diff --git a/source/include/intersection.h b/source/include/intersection.h index 17447fb..bb98a3a 100644 --- a/source/include/intersection.h +++ b/source/include/intersection.h @@ -16,8 +16,10 @@ class Shape; struct Computation { - Computation(Shape *object, double t, Tuple point, Tuple eyev, Tuple normalv, Tuple overHitP, bool inside) : - object(object), t(t), hitPoint(point), eyeVector(eyev), normalVector(normalv), inside(inside), overHitPoint(overHitP) { }; + Computation(Shape *object, double t, Tuple point, Tuple eyev, Tuple normalv, Tuple overHitP, + bool inside, Tuple reflectV = Vector(0, 0, 0)) : + object(object), t(t), hitPoint(point), eyeVector(eyev), normalVector(normalv), inside(inside), + overHitPoint(overHitP), reflectVector(reflectV) { }; Shape *object; double t; @@ -25,6 +27,7 @@ struct Computation Tuple overHitPoint; Tuple eyeVector; Tuple normalVector; + Tuple reflectVector; bool inside; }; diff --git a/source/include/material.h b/source/include/material.h index 41924ae..6dfb65f 100644 --- a/source/include/material.h +++ b/source/include/material.h @@ -24,11 +24,12 @@ public: double diffuse; double specular; double shininess; + double reflective; Pattern *pattern; public: - Material() : colour(Colour(1, 1, 1)), ambient(0.1), diffuse(0.9), specular(0.9), shininess(200), pattern(nullptr) {}; + Material() : colour(Colour(1, 1, 1)), ambient(0.1), diffuse(0.9), specular(0.9), shininess(200), reflective(0.0), pattern(nullptr) {}; Colour lighting(Light light, Tuple point, Tuple eyeVector, Tuple normalVector, Shape *hitObject, bool inShadow = false); diff --git a/source/include/world.h b/source/include/world.h index c1cca04..e5a82ee 100644 --- a/source/include/world.h +++ b/source/include/world.h @@ -40,11 +40,14 @@ public: bool objectIsIn(Shape &s); Shape *getObject(int i) { return this->objectList[i]; }; + Light *getLight(int i) { return this->lightList[i]; }; - Tuple shadeHit(Computation comps);; - Tuple colourAt(Ray r); + Tuple shadeHit(Computation comps, uint32_t depthCount = 4); + Tuple colourAt(Ray r, uint32_t depthCount = 4); bool isShadowed(Tuple point); + Colour reflectColour(Computation comps, uint32_t depthCount = 4); + Intersect intersect(Ray r); }; diff --git a/source/intersection.cpp b/source/intersection.cpp index be4b705..e8b8aa6 100644 --- a/source/intersection.cpp +++ b/source/intersection.cpp @@ -23,6 +23,7 @@ Computation Intersection::prepareComputation(Ray r) } Tuple overHitP = hitP + normalV * getEpsilon(); + Tuple reflectV = r.direction.reflect(normalV); return Computation(this->object, this->t, @@ -30,5 +31,6 @@ Computation Intersection::prepareComputation(Ray r) eyeV, normalV, overHitP, - inside); + inside, + reflectV); } \ No newline at end of file diff --git a/source/world.cpp b/source/world.cpp index 4af2bca..38c9b1f 100644 --- a/source/world.cpp +++ b/source/world.cpp @@ -92,17 +92,21 @@ Intersect World::intersect(Ray r) return ret; } -Tuple World::shadeHit(Computation comps) +Tuple World::shadeHit(Computation comps, uint32_t depthCount) { /* TODO: Add support for more than one light */ bool isThereAnObstacle = this->isShadowed(comps.overHitPoint); - return comps.object->material.lighting(*this->lightList[0], comps.overHitPoint, comps.eyeVector, + Tuple surface = comps.object->material.lighting(*this->lightList[0], comps.overHitPoint, comps.eyeVector, comps.normalVector, comps.object, isThereAnObstacle); + + Tuple reflected = this->reflectColour(comps, depthCount); + + return surface + reflected; } -Tuple World::colourAt(Ray r) +Tuple World::colourAt(Ray r, uint32_t depthCount) { Intersection hit = this->intersect(r).hit(); @@ -112,7 +116,7 @@ Tuple World::colourAt(Ray r) } else { - return this->shadeHit(hit.prepareComputation(r)); + return this->shadeHit(hit.prepareComputation(r), depthCount); } } @@ -134,3 +138,21 @@ bool World::isShadowed(Tuple point) return false; } + +Colour World::reflectColour(Computation comps, uint32_t depthCount) +{ + if ((depthCount == 0) || (comps.object->material.reflective == 0)) + { + return Colour(0, 0, 0); + } + + /* So it is reflective, even just a bit. Let'sr reflect the ray! */ + Ray reflectedRay = Ray(comps.overHitPoint, comps.reflectVector); + + Tuple hitColour = this->colourAt(reflectedRay, depthCount - 1); + hitColour = hitColour * comps.object->material.reflective; + + return Colour(hitColour.x, hitColour.y, hitColour.z); +} + + diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 90ff541..7053c7a 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -44,8 +44,14 @@ target_include_directories(ch10_test PUBLIC ../source/include) target_sources(ch10_test PRIVATE ch10_test.cpp) target_link_libraries(ch10_test rayonnement) +add_executable(ch11_reflection) +target_include_directories(ch11_reflection PUBLIC ../source/include) +target_sources(ch11_reflection PRIVATE ch11_reflection.cpp) +target_link_libraries(ch11_reflection rayonnement) + add_test(NAME Chapter05_Test COMMAND $) add_test(NAME Chapter06_Test COMMAND $) add_test(NAME Chapter07_Test COMMAND $) add_test(NAME Chapter09_Test COMMAND $) add_test(NAME Chapter10_Test COMMAND $) +add_test(NAME Chapter11_Reflection COMMAND $) diff --git a/tests/intersect_test.cpp b/tests/intersect_test.cpp index 4f8b128..f8a20a4 100644 --- a/tests/intersect_test.cpp +++ b/tests/intersect_test.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -189,4 +190,15 @@ TEST(IntersectTest, The_hit_should_offset_the_point) ASSERT_LT(comps.overHitPoint.z, -getEpsilon() / 2); ASSERT_GT(comps.hitPoint.z, comps.overHitPoint.z); +} + +TEST(IntersectTest, Precomputing_the_reflection_vector) +{ + Plane s = Plane(); + Ray r = Ray(Point(0, 1, -1), Vector(0, -sqrt(2) / 2, sqrt(2) / 2)); + Intersection i = Intersection(sqrt(2), &s); + + Computation comps = i.prepareComputation(r); + + ASSERT_EQ(comps.reflectVector, Vector(0, sqrt(2) / 2, sqrt(2) / 2)); } \ No newline at end of file diff --git a/tests/material_test.cpp b/tests/material_test.cpp index 2226a46..9841991 100644 --- a/tests/material_test.cpp +++ b/tests/material_test.cpp @@ -106,4 +106,11 @@ TEST(MaterialTest, Lighting_with_the_surface_in_shadow) Colour result = m.lighting(light, position, eyev, normalv, &t, inShadow); ASSERT_EQ(result, Colour(0.1, 0.1, 0.1)); -} \ No newline at end of file +} + +TEST(MaterialTest, Reflectivity_for_the_default_material) +{ + Material m = Material(); + + ASSERT_EQ(m.reflective, 0); +} diff --git a/tests/world_test.cpp b/tests/world_test.cpp index 6022fbc..dff79a9 100644 --- a/tests/world_test.cpp +++ b/tests/world_test.cpp @@ -14,6 +14,7 @@ #include #include #include +#include TEST(WorldTest, Creating_a_world) @@ -167,4 +168,109 @@ TEST(WorldTest, Shade_hit_is_given_an_intersection_in_shadow) Tuple c = w.shadeHit(comps); ASSERT_EQ(c, Colour(0.1, 0.1, 0.1)); -}; \ No newline at end of file +}; + +TEST(WorldTest, The_reflected_colour_for_a_non_reflective_material) +{ + World w = DefaultWorld(); + Ray r = Ray(Point(0, 0, 0), Vector(0, 0, 1)); + + Shape *shape = w.getObject(1); /* The second object */ + shape->material.ambient = 1; /* We use this to get a predictable colour */ + + Intersection i = Intersection(1, shape); + + Computation comps = i.prepareComputation(r); + Colour colour = w.reflectColour(comps); + + ASSERT_EQ(colour, Colour(0, 0, 0)); +} + +TEST(WorldTest, The_reflected_colour_for_a_reflective_material) +{ + World w = DefaultWorld(); + Plane shape = Plane(); + shape.material.reflective = 0.5; + shape.setTransform(translation(0, -1, 0)); + w.addObject(&shape); + + Ray r = Ray(Point(0, 0, -3), Vector(0, -sqrt(2)/2, sqrt(2)/2)); + + Intersection i = Intersection(sqrt(2), &shape); + + Computation comps = i.prepareComputation(r); + Colour colour = w.reflectColour(comps); + + /* Temporary lower the precision */ + set_equal_precision(0.00002); + + ASSERT_EQ(colour, Colour(0.19032, 0.2379, 0.14274)); + + set_equal_precision(FLT_EPSILON); +} + +TEST(WorldTest, Shade_hit_with_a_reflective_material) +{ + World w = DefaultWorld(); + Plane shape = Plane(); + shape.material.reflective = 0.5; + shape.setTransform(translation(0, -1, 0)); + w.addObject(&shape); + + Ray r = Ray(Point(0, 0, -3), Vector(0, -sqrt(2)/2, sqrt(2)/2)); + + Intersection i = Intersection(sqrt(2), &shape); + + Computation comps = i.prepareComputation(r); + Tuple colour = w.shadeHit(comps); + + /* Temporary lower the precision */ + set_equal_precision(0.00005); + + ASSERT_EQ(colour, Colour(0.87677, 0.92436, 0.82918)); + + set_equal_precision(FLT_EPSILON); +} + +TEST(WorldTest, Colour_at_with_mutually_reflective_surfaces) +{ + World w = World(); + + Light l = Light(POINT_LIGHT, Point(0, 0, 0), Colour(1, 1, 1)); + + w.addLight(&l); + + Plane lower = Plane(); + lower.material.reflective = 1; + lower.setTransform(translation(0, -1, 0)); + + Plane higher = Plane(); + higher.material.reflective = 1; + higher.setTransform(translation(0, 1, 0)); + + w.addObject(&lower); + w.addObject(&higher); + + Ray r = Ray(Point(0, 0, 0), Vector(0, 1, 0)); + + /* It should just exit, we don't care about the actual colour */ + w.colourAt(r); +} + +TEST(WorldTest, The_reflected_colour_at_the_maximum_recursion_depth) +{ + World w = DefaultWorld(); + Plane shape = Plane(); + shape.material.reflective = 0.5; + shape.setTransform(translation(0, -1, 0)); + w.addObject(&shape); + + Ray r = Ray(Point(0, 0, -3), Vector(0, -sqrt(2)/2, sqrt(2)/2)); + Intersection i = Intersection(sqrt(2), &shape); + + Computation comps = i.prepareComputation(r); + Tuple colour = w.reflectColour(comps, 0); + + /* Temporary lower the precision */ + ASSERT_EQ(colour, Colour(0, 0, 0)); +} \ No newline at end of file