Finally! We have reflections!

This commit is contained in:
Godzil
2020-02-21 17:21:06 +00:00
parent 9fffb68026
commit 7337ae4837
9 changed files with 174 additions and 12 deletions

View File

@@ -16,8 +16,10 @@ class Shape;
struct Computation struct Computation
{ {
Computation(Shape *object, double t, Tuple point, Tuple eyev, Tuple normalv, Tuple overHitP, bool inside) : Computation(Shape *object, double t, Tuple point, Tuple eyev, Tuple normalv, Tuple overHitP,
object(object), t(t), hitPoint(point), eyeVector(eyev), normalVector(normalv), inside(inside), overHitPoint(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; Shape *object;
double t; double t;
@@ -25,6 +27,7 @@ struct Computation
Tuple overHitPoint; Tuple overHitPoint;
Tuple eyeVector; Tuple eyeVector;
Tuple normalVector; Tuple normalVector;
Tuple reflectVector;
bool inside; bool inside;
}; };

View File

@@ -24,11 +24,12 @@ public:
double diffuse; double diffuse;
double specular; double specular;
double shininess; double shininess;
double reflective;
Pattern *pattern; Pattern *pattern;
public: 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); Colour lighting(Light light, Tuple point, Tuple eyeVector, Tuple normalVector, Shape *hitObject, bool inShadow = false);

View File

@@ -40,11 +40,14 @@ public:
bool objectIsIn(Shape &s); bool objectIsIn(Shape &s);
Shape *getObject(int i) { return this->objectList[i]; }; Shape *getObject(int i) { return this->objectList[i]; };
Light *getLight(int i) { return this->lightList[i]; };
Tuple shadeHit(Computation comps);; Tuple shadeHit(Computation comps, uint32_t depthCount = 4);
Tuple colourAt(Ray r); Tuple colourAt(Ray r, uint32_t depthCount = 4);
bool isShadowed(Tuple point); bool isShadowed(Tuple point);
Colour reflectColour(Computation comps, uint32_t depthCount = 4);
Intersect intersect(Ray r); Intersect intersect(Ray r);
}; };

View File

@@ -23,6 +23,7 @@ Computation Intersection::prepareComputation(Ray r)
} }
Tuple overHitP = hitP + normalV * getEpsilon(); Tuple overHitP = hitP + normalV * getEpsilon();
Tuple reflectV = r.direction.reflect(normalV);
return Computation(this->object, return Computation(this->object,
this->t, this->t,
@@ -30,5 +31,6 @@ Computation Intersection::prepareComputation(Ray r)
eyeV, eyeV,
normalV, normalV,
overHitP, overHitP,
inside); inside,
reflectV);
} }

View File

@@ -92,17 +92,21 @@ Intersect World::intersect(Ray r)
return ret; return ret;
} }
Tuple World::shadeHit(Computation comps) Tuple World::shadeHit(Computation comps, uint32_t depthCount)
{ {
/* TODO: Add support for more than one light */ /* TODO: Add support for more than one light */
bool isThereAnObstacle = this->isShadowed(comps.overHitPoint); 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); 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(); Intersection hit = this->intersect(r).hit();
@@ -112,7 +116,7 @@ Tuple World::colourAt(Ray r)
} }
else 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; 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);
}

View File

@@ -44,8 +44,14 @@ target_include_directories(ch10_test PUBLIC ../source/include)
target_sources(ch10_test PRIVATE ch10_test.cpp) target_sources(ch10_test PRIVATE ch10_test.cpp)
target_link_libraries(ch10_test rayonnement) 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 $<TARGET_FILE:ch5_test>) add_test(NAME Chapter05_Test COMMAND $<TARGET_FILE:ch5_test>)
add_test(NAME Chapter06_Test COMMAND $<TARGET_FILE:ch6_test>) add_test(NAME Chapter06_Test COMMAND $<TARGET_FILE:ch6_test>)
add_test(NAME Chapter07_Test COMMAND $<TARGET_FILE:ch7_test>) add_test(NAME Chapter07_Test COMMAND $<TARGET_FILE:ch7_test>)
add_test(NAME Chapter09_Test COMMAND $<TARGET_FILE:ch9_test>) add_test(NAME Chapter09_Test COMMAND $<TARGET_FILE:ch9_test>)
add_test(NAME Chapter10_Test COMMAND $<TARGET_FILE:ch10_test>) add_test(NAME Chapter10_Test COMMAND $<TARGET_FILE:ch10_test>)
add_test(NAME Chapter11_Reflection COMMAND $<TARGET_FILE:ch11_reflection>)

View File

@@ -9,6 +9,7 @@
#include <intersect.h> #include <intersect.h>
#include <intersection.h> #include <intersection.h>
#include <sphere.h> #include <sphere.h>
#include <plane.h>
#include <transformation.h> #include <transformation.h>
#include <gtest/gtest.h> #include <gtest/gtest.h>
@@ -189,4 +190,15 @@ TEST(IntersectTest, The_hit_should_offset_the_point)
ASSERT_LT(comps.overHitPoint.z, -getEpsilon() / 2); ASSERT_LT(comps.overHitPoint.z, -getEpsilon() / 2);
ASSERT_GT(comps.hitPoint.z, comps.overHitPoint.z); 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));
} }

View File

@@ -106,4 +106,11 @@ TEST(MaterialTest, Lighting_with_the_surface_in_shadow)
Colour result = m.lighting(light, position, eyev, normalv, &t, inShadow); Colour result = m.lighting(light, position, eyev, normalv, &t, inShadow);
ASSERT_EQ(result, Colour(0.1, 0.1, 0.1)); ASSERT_EQ(result, Colour(0.1, 0.1, 0.1));
} }
TEST(MaterialTest, Reflectivity_for_the_default_material)
{
Material m = Material();
ASSERT_EQ(m.reflective, 0);
}

View File

@@ -14,6 +14,7 @@
#include <worldbuilder.h> #include <worldbuilder.h>
#include <math.h> #include <math.h>
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include <plane.h>
TEST(WorldTest, Creating_a_world) TEST(WorldTest, Creating_a_world)
@@ -167,4 +168,109 @@ TEST(WorldTest, Shade_hit_is_given_an_intersection_in_shadow)
Tuple c = w.shadeHit(comps); Tuple c = w.shadeHit(comps);
ASSERT_EQ(c, Colour(0.1, 0.1, 0.1)); ASSERT_EQ(c, Colour(0.1, 0.1, 0.1));
}; };
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));
}