From 668a51c4f73859551fa39c7386ead71a52dd8c15 Mon Sep 17 00:00:00 2001 From: NADAL Jean-Baptiste Date: Mon, 11 Mar 2024 18:43:31 +0100 Subject: [PATCH] [WIP] Test 7 of refraction is in wip --- raytracing/src/core/intersection-data.cpp | 15 +- raytracing/src/core/intersection-data.h | 4 + raytracing/src/core/intersection.cpp | 1 + raytracing/src/renderer/world.cpp | 65 +++++- raytracing/src/renderer/world.h | 2 + tests/05_rays.cpp | 40 ++-- tests/11_reflection_refraction.cpp | 230 +++++++++++++++++++++- 7 files changed, 328 insertions(+), 29 deletions(-) diff --git a/raytracing/src/core/intersection-data.cpp b/raytracing/src/core/intersection-data.cpp index 824448d..8ffcc01 100644 --- a/raytracing/src/core/intersection-data.cpp +++ b/raytracing/src/core/intersection-data.cpp @@ -49,7 +49,6 @@ IntersectionData::IntersectionData(void) : IntersectionData::~IntersectionData(void) { - printf("destructor\n"); m_shape = nullptr; } @@ -111,6 +110,20 @@ void IntersectionData::set_over_point(const Tuple &a_point) /* ------------------------------------------------------------------------- */ +const Tuple &IntersectionData::under_point(void) const +{ + return m_under_point; +} + +/* ------------------------------------------------------------------------- */ + +void IntersectionData::set_under_point(const Tuple &a_point) +{ + m_under_point = a_point; +} + +/* ------------------------------------------------------------------------- */ + const Tuple &IntersectionData::eyev(void) const { return m_eyev; diff --git a/raytracing/src/core/intersection-data.h b/raytracing/src/core/intersection-data.h index bee7c63..f8c8d62 100644 --- a/raytracing/src/core/intersection-data.h +++ b/raytracing/src/core/intersection-data.h @@ -54,6 +54,9 @@ namespace Raytracer const Tuple &over_point(void) const; void set_over_point(const Tuple &a_point); + const Tuple &under_point(void) const; + void set_under_point(const Tuple &a_point); + const Tuple &eyev(void) const; void set_eyev(const Tuple &an_eyev); @@ -78,6 +81,7 @@ namespace Raytracer Shape *m_shape; Tuple m_point; Tuple m_over_point; + Tuple m_under_point; Tuple m_eyev; Tuple m_normalv; Tuple m_reflectv; diff --git a/raytracing/src/core/intersection.cpp b/raytracing/src/core/intersection.cpp index 4460643..4948857 100644 --- a/raytracing/src/core/intersection.cpp +++ b/raytracing/src/core/intersection.cpp @@ -190,6 +190,7 @@ IntersectionData Intersection::prepare_computations(const Ray &a_ray, Intersecti the_data.set_eyev(-a_ray.direction()); the_data.set_normalv(m_shape->normal_at(the_data.point())); the_data.set_over_point(the_data.point() + the_data.normalv() * kEpsilon); + the_data.set_under_point(the_data.point() - the_data.normalv() * kEpsilon); the_data.set_inside(); the_data.set_reflectv(a_ray.direction().reflect(the_data.normalv())); diff --git a/raytracing/src/renderer/world.cpp b/raytracing/src/renderer/world.cpp index 7735cd3..77b90f6 100644 --- a/raytracing/src/renderer/world.cpp +++ b/raytracing/src/renderer/world.cpp @@ -28,6 +28,8 @@ /* ------------------------------------------------------------------------- */ +#include + #include "core/common.h" #include "core/matrix.h" @@ -159,21 +161,21 @@ Intersections World::intersect_world(const Ray &a_ray) const /* ------------------------------------------------------------------------- */ -Color World::shade_hit(const IntersectionData &an_intersection_data, uint32_t a_remainging) const +Color World::shade_hit(const IntersectionData &an_intersection_data, uint32_t a_remaining) const { bool the_shadowed = is_shadowed(an_intersection_data.over_point()); Shape *the_object = an_intersection_data.object(); Color the_surface = the_object->material().lighting(the_object, m_light, an_intersection_data.over_point(), an_intersection_data.eyev(), an_intersection_data.normalv(), the_shadowed); - Color the_reflected = reflected_color(an_intersection_data, a_remainging); + Color the_reflected = reflected_color(an_intersection_data, a_remaining); return the_surface + the_reflected; } /* ------------------------------------------------------------------------- */ -Color World::reflected_color(const IntersectionData &a_data, uint32_t a_remainging) const +Color World::reflected_color(const IntersectionData &a_data, uint32_t a_remaining) const { - if (a_remainging <= 0) + if (a_remaining <= 0) { return Color(0, 0, 0); } @@ -183,14 +185,63 @@ Color World::reflected_color(const IntersectionData &a_data, uint32_t a_remaingi return Color(0, 0, 0); } Ray the_reflected_ray(a_data.over_point(), a_data.reflectv()); - Color the_color = color_at(the_reflected_ray, a_remainging - 1); + Color the_color = color_at(the_reflected_ray, a_remaining - 1); return the_color * a_data.object()->material().reflective(); } /* ------------------------------------------------------------------------- */ -Color World::color_at(const Ray &a_ray, uint32_t a_remainging) const +Color World::refracted_color(const IntersectionData &an_intersection_data, uint32_t a_remaining) const +{ + double the_n_ratio, the_cos_i, the_cos_t, the_sin_t; + Tuple the_direction; + Color the_color; + double the_transparency; + + if (a_remaining <= 0) + { + return Color(0, 0, 0); + } + + the_transparency = an_intersection_data.object()->material().transparency(); + if (double_equal(the_transparency, 0)) + { + return Color(0, 0, 0); + } + + // Find the ratio of first index of refraction to the second. + // Yup, this inverted for the definition of the snell's Law + the_n_ratio = an_intersection_data.n1() / an_intersection_data.n2(); + + // Cos(theta_i) is the same as the dot product of the two vectors. + the_cos_i = an_intersection_data.eyev().dot(an_intersection_data.normalv()); + + // Find sin(theta_t)^2 via trigonometric identity + the_sin_t = the_n_ratio * the_n_ratio * (1.0 - the_cos_i * the_cos_i); + if (the_sin_t > 1) + { + return Color(0, 0, 0); + } + + // Find cos(theta_t) via trigonometric identity + the_cos_t = std::sqrt(1.0 - the_sin_t); + + // Compute the direction of the refracted ray + the_direction = an_intersection_data.normalv() * (the_n_ratio * the_cos_i - the_cos_t) - + an_intersection_data.eyev() * the_n_ratio; + Ray the_refracted_ray(an_intersection_data.under_point(), the_direction); + + // Find the color of the refracted ray, making sure to multiply by the transparency value + // to account for any opacity + the_color = color_at(the_refracted_ray, a_remaining - 1) * an_intersection_data.object()->material().transparency(); + + return the_color; +} + +/* ------------------------------------------------------------------------- */ + +Color World::color_at(const Ray &a_ray, uint32_t a_remaining) const { Color the_color = Color::Black(); @@ -208,7 +259,7 @@ Color World::color_at(const Ray &a_ray, uint32_t a_remainging) const IntersectionData the_comps = the_intersec.prepare_computations(a_ray); - the_color = shade_hit(the_comps, a_remainging); + the_color = shade_hit(the_comps, a_remaining); return the_color; } diff --git a/raytracing/src/renderer/world.h b/raytracing/src/renderer/world.h index 3f799ce..f52d0b1 100644 --- a/raytracing/src/renderer/world.h +++ b/raytracing/src/renderer/world.h @@ -67,6 +67,8 @@ namespace Raytracer Intersections intersect_world(const Ray &a_ray) const; Color shade_hit(const IntersectionData &an_intersection_data, uint32_t a_remainging = kRemainingDefaultDepth) const; Color reflected_color(const IntersectionData &an_intersection_data, uint32_t a_remainging = kRemainingDefaultDepth) const; + Color refracted_color(const IntersectionData &an_intersection_data, uint32_t a_remainging = kRemainingDefaultDepth) const; + Color color_at(const Ray &a_ray, uint32_t a_remainging = kRemainingDefaultDepth) const; bool is_shadowed(const Tuple &a_point) const; diff --git a/tests/05_rays.cpp b/tests/05_rays.cpp index 9c86c3c..4f041ab 100644 --- a/tests/05_rays.cpp +++ b/tests/05_rays.cpp @@ -232,7 +232,7 @@ SCENARIO("An intersection encapsulates t and object", "[features/intersections.f GIVEN("s <- sphere()") { Sphere s; - WHEN("intersection(3.5,s)") + WHEN("intersection(3.5, s)") { Intersection i(3.5, &s); @@ -284,7 +284,7 @@ SCENARIO("Intersection could be compared", "[features/intersections.feature]") GIVEN("s <- sphere()") { Sphere s; - AND_GIVEN("i1 <- intersection(3,s) and i2 <- intersection(4,s)") + AND_GIVEN("i1 <- intersection(3, s) and i2 <- intersection(4, s)") { Intersection i1(3.0, &s); Intersection i2(4.0, &s); @@ -324,13 +324,13 @@ SCENARIO("Aggregating intersections", "[features/intersections.feature]") GIVEN("s <- sphere()") { Sphere s; - AND_GIVEN("i1 <- intersection(1,s)") + AND_GIVEN("i1 <- intersection(1, s)") { Intersection i1(1, &s); - AND_GIVEN("i2 <- intersection(2,s)") + AND_GIVEN("i2 <- intersection(2, s)") { Intersection i2(2, &s); - WHEN("xs <- intersections(i1,i2)") + WHEN("xs <- intersections(i1, i2)") { Intersections xs = Intersections({i1, i2}); THEN("xs.count = 2") @@ -394,7 +394,7 @@ SCENARIO("Intersect sets the object on the intersection", "[features/spheres.fea AND_GIVEN("s <- sphere()") { Sphere s; - WHEN("xs <- intersect(s,r)") + WHEN("xs <- intersect(s, r)") { Intersections xs = s.intersect(r); THEN("xs.count = 2") @@ -421,13 +421,13 @@ SCENARIO("The hit, when all intersections have positive t", "[features/intersect GIVEN("s <- sphere()") { Sphere s; - AND_GIVEN("i1 <- intersection(1,s)") + AND_GIVEN("i1 <- intersection(1, s)") { Intersection i1(1, &s); - AND_GIVEN("i2 <- intersection(2,s)") + AND_GIVEN("i2 <- intersection(2, s)") { Intersection i2(2, &s); - AND_GIVEN("xs <- intersections(i1,i2)") + AND_GIVEN("xs <- intersections(i1, i2)") { Intersections xs = Intersections({i2, i1}); WHEN("i <- hit(xs)") @@ -451,13 +451,13 @@ SCENARIO("The hit, when some intersections have negative t", "[features/intersec GIVEN("s <- sphere()") { Sphere s; - AND_GIVEN("i1 <- intersection(-1,s)") + AND_GIVEN("i1 <- intersection(-1, s)") { Intersection i1(-1, &s); - AND_GIVEN("i2 <- intersection(2,s)") + AND_GIVEN("i2 <- intersection(2, s)") { Intersection i2(1, &s); - AND_GIVEN("xs <- intersections(i1,i2)") + AND_GIVEN("xs <- intersections(i1, i2)") { Intersections xs = Intersections({i2, i1}); WHEN("i <- hit(xs)") @@ -481,13 +481,13 @@ SCENARIO("The hit, when all intersections have negative t", "[features/intersect GIVEN("s <- sphere()") { Sphere s; - AND_GIVEN("i1 <- intersection(-2,s)") + AND_GIVEN("i1 <- intersection(-2, s)") { Intersection i1(-2, &s); - AND_GIVEN("i2 <- intersection(-1,s)") + AND_GIVEN("i2 <- intersection(-1, s)") { Intersection i2(-1, &s); - AND_GIVEN("xs <- intersections(i1,i2)") + AND_GIVEN("xs <- intersections(i1, i2)") { Intersections xs = Intersections({i1, i2}); WHEN("i <- hit(xs)") @@ -512,16 +512,16 @@ SCENARIO("The hit is always the lowest nonnegative intersection", "[features/int GIVEN("s <- sphere()") { Sphere s; - AND_GIVEN("i1 <- intersection(5,s)") + AND_GIVEN("i1 <- intersection(5, s)") { Intersection i1(5, &s); - AND_GIVEN("i2 <- intersection(7,s)") + AND_GIVEN("i2 <- intersection(7, s)") { Intersection i2(7, &s); - AND_GIVEN("i3 <- intersection(-3,s)") + AND_GIVEN("i3 <- intersection(-3, s)") { Intersection i3(-3, &s); - AND_GIVEN("i4 <- intersection(2,s)") + AND_GIVEN("i4 <- intersection(2, s)") { Intersection i4(2, &s); AND_GIVEN("xs <- intersections(i1, i2, i3, i4)") @@ -645,7 +645,7 @@ SCENARIO("Intersecting a scaled sphere with a ray", "[features/spheres.feature]" AND_GIVEN("s <- Sphere()") { Sphere s; - WHEN("set_transform(s,scaling(2,2,2))") + WHEN("set_transform(s,scaling(2, 2, 2))") { s.set_transform(Matrix::scaling(2, 2, 2)); AND_WHEN("xs <- intersect(s,r)") diff --git a/tests/11_reflection_refraction.cpp b/tests/11_reflection_refraction.cpp index 70405f9..fb5ebae 100644 --- a/tests/11_reflection_refraction.cpp +++ b/tests/11_reflection_refraction.cpp @@ -33,6 +33,19 @@ using namespace Raytracer; /* ------------------------------------------------------------------------- */ +class TestPattern : public Pattern +{ +public: + TestPattern(void) = default; + + const Color pattern_at(const Tuple &a_point) const override + { + return Color(a_point.x(), a_point.y(), a_point.z()); + } +}; + +/* ------------------------------------------------------------------------- */ + SCENARIO("Reflectivity for the default material", "[features/materials.feature]") { GIVEN("m <- material()") @@ -349,7 +362,7 @@ SCENARIO("Finding n1 and n2 at various intersections", "[features/intersections. B.material().set_refractive_index(2.0); GIVEN("C <- glass_sphere() with:") // | transform | translation(0, 0, 0.25) | - // | material.refractive_index | 2.5 | + // | material.refractive_index | 2.5 | { Sphere C = Sphere::Glass(); C.set_transform(Matrix::translation(0, 0, 0.25)); @@ -405,3 +418,218 @@ SCENARIO("Finding n1 and n2 at various intersections", "[features/intersections. } } } + +/* ------------------------------------------------------------------------- */ + +SCENARIO("The under point is offset below the surface", "[features/intersections.feature]") +{ + GIVEN("r <- ray(point(0, 0, -5), vector(0, 0, 1)") + { + Ray r(Tuple::Point(0, 0, -5), Tuple::Vector(0, 0, 1)); + AND_GIVEN("shape <- glass_sphere() with:") + // | transform | translation(0, 0, 1) | + { + Sphere shape = Sphere::Glass(); + shape.set_transform(Matrix::translation(0, 0, 1)); + AND_GIVEN("i <- intersection(5, shape)") + { + Intersection i(5, &shape); + AND_GIVEN("xs <- intersections(i)") + { + Intersections xs = Intersections({i}); + WHEN("comps <- prepare_computations(i, r, xs)") + { + IntersectionData comps = i.prepare_computations(r, &xs); + THEN("comps.under_point.z > EPSILON / 2") + { + REQUIRE(comps.under_point().z() > kEpsilon / 2); + } + AND_THEN("comps.point.z < comps.under_point.z") + { + REQUIRE(comps.point().z() < comps.under_point().z()); + } + } + } + } + } + } +} + +/* ------------------------------------------------------------------------- */ + +SCENARIO("The refracted color with a, opaque surface", "[features/world.feature]") +{ + GIVEN("w <- default_world()") + { + World w = World::default_world(); + AND_GIVEN("shape <- first object of w") + { + Shape *shape = w.objects(0); + AND_GIVEN("r <- ray(point(0, 0, -5), vector(0, 0, 1))") + { + Ray r(Tuple::Point(0, 0, -5), Tuple::Vector(0, 0, 1)); + AND_GIVEN("xs <- intersections(4:shape, 6:shape)") + { + Intersections xs = Intersections({Intersection(4.0, shape), + Intersection(6.0, shape)}); + WHEN("comps <- prepare_computations(xs[0], r, xs)") + { + IntersectionData comps = xs[0].prepare_computations(r, &xs); + AND_WHEN("c <- refracted_color(w, comps, 5)") + { + Color c = w.refracted_color(comps, 5); + THEN("c = color(0, 0, 0)") + { + REQUIRE(c == Color(0, 0, 0)); + } + } + } + } + } + } + } +} + +/* ------------------------------------------------------------------------- */ + +SCENARIO("The refracted color at the maximum recursive depth", "[features/world.feature]") +{ + GIVEN("w <- default_world()") + { + World w = World::default_world(); + AND_GIVEN("shape <- first object of w") + { + Shape *shape = w.objects(0); + AND_GIVEN("shape has:") + { + // | material.transparency | 1.0 | + // | material.refractive_index | 1.5 | + shape->material().set_transparency(1.0); + shape->material().set_refractive_index(1.5); + AND_GIVEN("r <- ray(point(0, 0, -5), vector(0, 0, 1))") + { + Ray r(Tuple::Point(0, 0, -5), Tuple::Vector(0, 0, 1)); + AND_GIVEN("xs <- intersections(4:shape, 6:shape)") + { + Intersections xs = Intersections({Intersection(4.0, shape), + Intersection(6.0, shape)}); + WHEN("comps <- prepare_computations(xs[0], r, xs)") + { + IntersectionData comps = xs[0].prepare_computations(r, &xs); + AND_WHEN("c <- refracted_color(w, comps, 0)") + { + Color c = w.refracted_color(comps, 0); + THEN("c = color(0, 0, 0)") + { + REQUIRE(c == Color(0, 0, 0)); + } + } + } + } + } + } + } + } +} + +/* ------------------------------------------------------------------------- */ + +SCENARIO("The refracted color under total internal reflection", "[features/world.feature]") +{ + GIVEN("w <- default_world()") + { + World w = World::default_world(); + AND_GIVEN("shape <- first object of w") + { + Shape *shape = w.objects(0); + AND_GIVEN("shape has:") + { + // | material.transparency | 1.0 | + // | material.refractive_index | 1.5 | + shape->material().set_transparency(1.0); + shape->material().set_refractive_index(1.5); + AND_GIVEN("r <- ray(point(0, 0, sqrt(2) / 2), vector(0, 1, 0))") + { + Ray r(Tuple::Point(0, 0, sqrt(2) / 2), Tuple::Vector(0, 1, 0)); + AND_GIVEN("xs <- intersections(-(sqrt(2) / 2):shape, sqrt(2) / 2:shape)") + { + Intersections xs = Intersections({Intersection(-(sqrt(2) / 2), shape), + Intersection(sqrt(2) / 2, shape)}); + // NOTE: this time you're inside the sphere, so you need to look at the + // second intersection, xs[1] and not xs[0] + WHEN("comps <- prepare_computations(xs[1], r, xs)") + { + IntersectionData comps = xs[1].prepare_computations(r, &xs); + AND_WHEN("c <- refracted_color(w, comps, 5)") + { + Color c = w.refracted_color(comps, 5); + THEN("c = color(0, 0, 0)") + { + REQUIRE(c == Color(0, 0, 0)); + } + } + } + } + } + } + } + } +} + +/* ------------------------------------------------------------------------- */ + +SCENARIO("The refracted color with a refracted ray", "[features/world.feature]") +{ + GIVEN("w <- default_world()") + { + World w = World::default_world(); + AND_GIVEN("A <- first object of w") + { + Shape *A = w.objects(0); + AND_GIVEN("A has:") + { + // | material.ambient | 1.0 | + // | material.pattern | test_pattern() | + A->material().set_ambient(1.0); + TestPattern the_pattern; + A->material().set_pattern(&the_pattern); + AND_GIVEN("B <- second object of w") + { + Shape *B = w.objects(1); + AND_GIVEN("shape has:") + { + // | material.transparency | 1.0 | + // | material.refractive_index | 1.5 | + B->material().set_transparency(1.0); + B->material().set_refractive_index(1.5); + AND_GIVEN("r <- ray(point(0, 0, 0.1, vector(0, 1, 0))") + { + Ray r(Tuple::Point(0, 0, 0.1), Tuple::Vector(0, 1, 0)); + AND_GIVEN("xs <- intersections(-0.9899:A, -0.4899:B, 0.4899:B, 0.9899:A)") + { + Intersections xs = Intersections({ + Intersection(-0.9899, A), + Intersection(-0.4899, B), + Intersection(0.4899, B), + Intersection(0.9899, A), + }); + WHEN("comps <- prepare_computations(xs[2], r, xs)") + { + IntersectionData comps = xs[2].prepare_computations(r, &xs); + AND_WHEN("c <- refracted_color(w, comps, 5)") + { + Color c = w.refracted_color(comps, 5); + THEN("c = color(0, 0.99888, 0.04725)") + { + REQUIRE(c == Color(0, 0.99888, 0.04725)); + } + } + } + } + } + } + } + } + } + } +}